文章目录

  • 前言
  • 一对一
  • 一对多
  • 多对一
  • 多对多
  • 遇到的问题
  • 总结
  • mysql脚本


前言

MyBatis关联查询(级联查询)分为一对一,一对多,多对一,多对多

而每种实现方式又可以分类嵌套查询(查询两次),嵌套结果(查询一次),连接查询(写法跟嵌套结果类似,只是采用另外的POJO存储结果)

一对一

一对一级联关系在现实生活中是十分常见的,例如一个大学生只有一张一卡通,一张一卡通只属于一个学生。再如人与身份证的关系也是一对一的级联关系。

MyBatis 如何处理一对一级联查询呢?在 MyBatis 中,通过 < resultMap> 元素的子元素 < association> 处理这种一对一级联关系。

在 < association> 元素中通常使用以下属性。

  • property:指定映射到实体类的对象属性。
  • column:指定表中对应的字段(即查询返回的列名)。
  • javaType:指定映射到实体对象属性的类型。
  • select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
  • 标签里面<id property="id" column="id"/>好像可以写成<result property="id" column="id"/>

个人与身份证:在个人上关联身份证(通过card_id)

Person和Card

@Data
public class Person {
    private int id;
    private String name;
    private Card card;
}

@Data
public class Card {
    private int id;
    private String code;
}

CardMapper

public interface CardMapper {
    public Idcard selectCodeById(Integer i);
}

PersonMapper

public interface PersonMapper{
    public Person selectPersonById1(Integer id);
    public Person selectPersonById2(Integer id);
    public SelectPersonById selectPersonById3(Integer id);
}

PersonMapper.xml

<!-- 一对一根据id查询个人信息:级联查询的第一种方法(嵌套查询,执行两个SQL语句)-->
    <resultMap type="com.apple.entity.Person" id="cardAndPerson1">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- 一对一级联查询-->
        <association property="card" column="card_id" javaType="com.apple.entity.Card"
                     select="com.apple.mapper.CardMapper.selectCodeById"/>
    </resultMap>
    <select id="selectPersonById1" parameterType="Integer" resultMap=
            "cardAndPerson1">
        select * from t_person where id=#{id}
    </select>


    <!--对一根据id查询个人信息:级联查询的第二种方法(嵌套结果,执行一个SQL语句)-->
    <resultMap type="com.apple.entity.Person" id="cardAndPerson2">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- 一对一级联查询-->
        <association property="card" javaType="com.apple.entity.Card">
            <id property="id" column="card_id"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>
    <select id="selectPersonById2" parameterType="Integer" resultMap= "cardAndPerson2">
        select p.*,c.code
        from t_person p, t_card c
        where p.card_id=c.id and p.id=#{id}
    </select>


    <!-- 一对一根据id查询个人信息:连接查询(使用POJO存储结果)-->
    <select id="selectPersonById3" parameterType="Integer" resultType= "com.apple.entity.SelectPersonById">
        select p.*,c.code
        from t_person p, t_card c
        where p.card_id=c.id and p.id=#{id}
    </select>

注意:

  • 如果是嵌套查询的方式,association标签要写上4个属性,column相当于将card_id(外键)传递给selectCodeByld
    <association property="card" column="idcard_id" javaType="com.po.Idcard" select="com.dao.IdCardDao.selectCodeByld"/>
  • 如果是嵌套结果的方式,只要2个属性
    <association property="card" javaType="com.po.Idcard">

CardMapper.xml

<select id="selectCodeById" parameterType="Integer" resultType="card">
        select * from t_card where id=#{id}
    </select>

SelectPersonById相当于重写一个实体类合并属性

@Data
public class SelectPersonById {
    private Integer id;
    private String name;
    private String code;
}

测试:

@RequestMapping("/testOneToOne")
    public Person testOneToOne() {
        System.out.println(personMapper.selectPersonById2(1));
        System.out.println(personMapper.selectPersonById3(1));
        return personMapper.selectPersonById1(1);
    }

结果:

{
  "id" : 1,
  "name" : "aa",
  "card" : {
    "id" : 1,
    "code" : "350821199612093158"
  }
}

一对多

MyBatis 又是如何处理一对多级联查询的呢?在实际生活中一对多级联关系有许多,例如一个班级可以有很多学生,而一个学生只属于一个班级。

在 MyBatis 中,通过 < resultMap> 元素的子元素 < collection> 处理这种一对一级联关系。

在 < collection> 元素中通常使用以下属性。

  • property:指定映射到实体类的对象属性。
  • column:指定表中对应的字段(即查询返回的列名)。
  • ofType:表示集合中的元素类型。
  • select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。

Clazz和Student

@Data
public class Clazz {
    private int cid;
    private String cname;
    /**
     * 一对多级联查询,班级关联的学生
     */
    private List<Student> studentList;
}

@Data
public class Student {
    private int id;
    private String name;
}

SelectStudentById实体POJO

@Data
public class SelectStudentById {
    private Integer cid;
    private String cname;
    private Integer id;
    private String name;
}

ClazzMapper

public interface ClazzMapper {
    Clazz selectStudentById1(Integer id);
    Clazz selectStudentById2(Integer id);
    List<SelectStudentById> selectStudentById3(Integer id);
}

StudentMapper

public interface StudentMapper {
    List<Student> selectStudentById(Integer cid);
}

ClazzMapper.xml

<!-- 一对多 根据id查询班级及其关联的学生信息:级联查询的第一种方法(嵌套查询) -->
    <resultMap type="com.apple.entity.Clazz" id="studentAndClazz1">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!-- 一对多级联查询,ofType表示集合中的元素类型,将class的主键id传递给selectStudentById -->
        <collection property="studentList" ofType="com.apple.entity.Student"
                    column="cid" select="com.apple.mapper.StudentMapper.selectStudentById"/>
    </resultMap>
    <select id="selectStudentById1" parameterType="Integer"
            resultMap="studentAndClazz1">
        select * from t_class where cid = #{id}
    </select>


    <!-- 一对多根据id查询班级及其关联的学生信息:级联查询的第二种方法(嵌套结果) -->
    <resultMap type="com.apple.entity.Clazz" id="studentAndClazz2">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!-- 一对多级联查询,ofType表示集合中的元素类型 -->
        <collection property="studentList" ofType="com.apple.entity.Student">
            <result property="id" column="id"/>
            <!--可以用id,也可以用result?-->
            <!--<id property="id" column="id"/>-->
            <result property="name" column="name"/>
        </collection>
    </resultMap>
    <select id="selectStudentById2" parameterType="Integer"
            resultMap="studentAndClazz2">
        select s.id,s.name,c.* from t_class c,t_student s   where c.cid=s.class_id
        and c.cid=#{id}
    </select>


    <!--一对多 根据uid查询用户及其关联的订单信息:连接查询(使用POJO存储结果)-->
    <select id="selectStudentById3" parameterType="Integer"
            resultType="com.apple.entity.SelectStudentById">
      select s.id,s.name,c.* from t_class c,t_student s   where c.cid=s.class_id
      and c.cid=#{id}
    </select>

StudentMapper.xml

<select id="selectStudentById" parameterType="Integer" resultType="com.apple.entity.Student">
        select id,name from t_student where class_id=#{cid}
    </select>

测试:

@RequestMapping("/testOneToMany")
    public Clazz testOneToMany() {
        Clazz clazz = clazzMapper.selectStudentById1(1);
        System.out.println(clazz);
        System.out.println(clazzMapper.selectStudentById3(1));
        return clazzMapper.selectStudentById2(1);
    }

结果:

{
  "cid" : 1,
  "cname" : "java",
  "studentList" : [ {
    "id" : 1,
    "name" : "xiaoming"
  }, {
    "id" : 2,
    "name" : "xiao"
  } ]
}

多对一

一个班级有多个学生,反过来多个学生对应同一个班级,就是多对一

Clazz1和Student1

@Data
public class Clazz1 {
    private int cid;
    private String cname;
}

@Data
public class Student1 {
    private int id;
    private String name;
    private Clazz1 clazz1;
}

ClazzMapper

public interface ClazzMapper {
    Clazz1 selectClazzById1(Integer id);
}

StudentMapper

public interface StudentMapper {
	List<Student1> selectClazzById1(Integer cid);
    List<Student1> selectClazzById2(Integer cid);
}

ClazzMapper.xml

<select id="selectClazzById1" parameterType="Integer"
            resultType="com.apple.entity.Clazz1">
        select * from t_class where cid = #{id}
</select>

StudentMapper.xml

<!--嵌套查询  2次查询-->
    <resultMap type="com.apple.entity.Student1" id="studentAndClazz1">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--使用association关联-->
        <association property="clazz1" javaType="com.apple.entity.Clazz1"
                     column="class_id" select="com.apple.mapper.ClazzMapper.selectClazzById1"/>
    </resultMap>
    <select id="selectClazzById1" resultMap="studentAndClazz1">
    	select * from t_student  where class_id=#{cid}
    </select>


    <!--嵌套结果  1次查询-->
    <resultMap type="com.apple.entity.Student1" id="studentAndClazz2">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--使用association关联-->
        <association property="clazz1" javaType="com.apple.entity.Clazz1">
            <id property="cid" column="cid"/>
            <result property="cname" column="cname"/>
        </association>
    </resultMap>

    <select id="selectClazzById2" resultMap="studentAndClazz2">
   		 select s.id,s.name,c.cid,c.cname 
   		 from t_student s,t_class c   where
    	 s.class_id=c.cid   and   s.class_id=#{cid}
    </select>

测试:

@RequestMapping("/testManyToOne")
    public List<Student1> testManyToOne() {
        System.out.println(studentMapper.selectClazzById2(1));
        return studentMapper.selectClazzById1(1);
    }

结果:

[ {
  "id" : 1,
  "name" : "xiaoming",
  "clazz1" : {
    "cid" : 1,
    "cname" : "java"
  }
}, {
  "id" : 2,
  "name" : "xiao",
  "clazz1" : {
    "cid" : 1,
    "cname" : "java"
  }
} ]

多对多

MyBatis 没有实现多对多级联,这是因为多对多级联可以通过两个一对多级联进行替换。

例如,一个订单可以有多种商品,一种商品可以对应多个订单,订单与商品就是多对多的级联关系,使用一个中间表(订单记录表)就可以将多对多级联转换成两个一对多的关系;一个用户可以有多个角色,一个角色也可以被多个用户所拥有,使用中间表(用户角色表)拆分为两个一对多

User和Role

注意:

  • 这里可以采用jackson的@JsonIgnore标记,返回的json数据即不包含该属性。
  • 再顺便说一下@JsonProperty将属性的名称序列化为另外一个名称,如把trueName属性序列化为name,@JsonProperty(“name”)。
@Data
public class User {
    private int uid;
    private String username;
    private String password;
    //@JsonIgnore
    private List<Role> roleList;
}

@Data
public class Role {
    private int id;
    private String roleName;
    //@JsonIgnore
    private List<User> userList;
}

UserMapper

public interface UserMapper {
    /**
     * 根据userID获取用户对应的所有角色
     *
     * @param uid
     * @return
     */
    User getUserRoleById(@Param("uid") int uid);

    /**
     * 获取所有用户对应的所有角色
     *
     * @return
     */
    List<User> getUserRoleList();

}

RoleMapper

public interface RoleMapper {
    List<Role> getRoleUserList();
}

UserMapper.xml

<resultMap id="userMap" type="com.apple.entity.User">
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <!--一对多配置:一个用户可以对用多应角色-->
        <collection property="roleList" ofType="com.apple.entity.Role">
            <id property="id" column="id"/>
            <result property="roleName" column="roleName"/>
        </collection>
    </resultMap>


    <!--根据userID获取用户对应的所有角色-->
    <select id="getUserRoleById" parameterType="int" resultMap="userMap">
        SELECT
          u.*,r.role_name roleName,r.id
        FROM
           t_user u,t_user_role ur,t_role r
        where  u.uid=ur.user_id and ur.role_id=r.id  and u.uid=#{uid}
    </select>
    
     <!--获取所有用户对应的所有角色-->
    <select id="getUserRoleList" resultMap="userMap">
         SELECT
          u.*,r.role_name roleName,r.id
        FROM
           t_user u,t_user_role ur,t_role r
        where  u.uid=ur.user_id and ur.role_id=r.id
    </select>

RoleMapper.xml

<resultMap id="roleMap" type="com.apple.entity.Role">
        <id property="id" column="id"/>
        <result property="roleName" column="role_name"/>
        <!--一对多配置:一个角色可以被多个用户所拥有-->
        <collection property="userList" ofType="com.apple.entity.User">
            <id property="uid" column="uid"/>
            <result property="username" column="username"/>
            <result property="password" column="password"/>
        </collection>
    </resultMap>

    <select id="getRoleUserList" resultMap="roleMap">
        select * from
        t_role r,t_user_role ur,t_user u
        where r.id=ur.role_id  and u.uid=ur.user_id
    </select>

测试:

一个用户对应多个角色

@RequestMapping("/testManyToMany")
    public User testManyToMany() {
        System.out.println(userMapper.getUserRoleById(2));
        return userMapper.getUserRoleById(1);
    }
{
  "uid" : 1,
  "username" : "aa",
  "password" : "123",
  "roleList" : [ {
    "id" : 2,
    "roleName" : "团员"
  }, {
    "id" : 1,
    "roleName" : "群众"
  } ]
}

所有用户对应的所有角色

@RequestMapping("/testManyToMany2")
    public List<User> testManyToMany2() {
        return userMapper.getUserRoleList();
    }
[ {
  "uid" : 1,
  "username" : "aa",
  "password" : "123",
  "roleList" : [ {
    "id" : 2,
    "roleName" : "团员"
  }, {
    "id" : 1,
    "roleName" : "群众"
  } ]
}, {
  "uid" : 2,
  "username" : "bb",
  "password" : "321",
  "roleList" : [ {
    "id" : 2,
    "roleName" : "团员"
  } ]
}, {
  "uid" : 3,
  "username" : "cc",
  "password" : "2121",
  "roleList" : [ {
    "id" : 2,
    "roleName" : "团员"
  }, {
    "id" : 3,
    "roleName" : "党员"
  } ]
} ]

一个角色被多个用户所拥有

@RequestMapping("/testManyToMany3")
    public List<Role> testManyToMany3() {
        return roleMapper.getRoleUserList();
    }
[ {
  "id" : 2,
  "roleName" : "团员",
  "userList" : [ {
    "uid" : 1,
    "username" : "aa",
    "password" : "123"
  }, {
    "uid" : 2,
    "username" : "bb",
    "password" : "321"
  }, {
    "uid" : 3,
    "username" : "cc",
    "password" : "2121"
  } ]
}, {
  "id" : 1,
  "roleName" : "群众",
  "userList" : [ {
    "uid" : 1,
    "username" : "aa",
    "password" : "123"
  } ]
}, {
  "id" : 3,
  "roleName" : "党员",
  "userList" : [ {
    "uid" : 3,
    "username" : "cc",
    "password" : "2121"
  } ]
} ]

遇到的问题

nested exception is org.apache.ibatis.exceptions.TooManyResultsException:
Expected one result (or null) to be returned by selectOne(), but found: 2

sql顺序问题导致,将c.*放到后面,就没错了,猜测是由于两个id一致造成的?经验证确实是的!!!

select c.*,s.id,s.name from t_class c,t_student s   where c.id=s.class_id and c.id=#{id}
==>  Preparing: select c.*,s.id,s.name from t_class c,t_student s where c.id=s.class_id and c.id=? 
==> Parameters: 1(Integer)
<==    Columns: id, cname, id, name
<==        Row: 1, java, 1, xiaoming
<==        Row: 1, java, 2, xiao
<==      Total: 2

所以,数据库表字段最好在使用的时候不要一致,不然有时候会有莫名的bug!!

总结

1、关联-association 集合-collection

2、所以association是用于一对一和多对一(Student只会关联一个班级,Person只能关联一张身份证,所以班级和身份证用association),而collection是用于一对多的关系(Clazz会有很多学生,所以学生用collection)

3、JavaType和ofType都是用来指定对象类型的

  • JavaType是用来指定pojo中属性的类型
  • ofType指定的是映射到list集合属性中pojo的类型。
  • JavaType是在association 标签中的,ofType是在collection标签

mysql脚本

-- ----------------------------
-- Table structure for t_card
-- ----------------------------
DROP TABLE IF EXISTS `t_card`;
CREATE TABLE `t_card`  (
  `id` int(11) NOT NULL,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of t_card
-- ----------------------------
INSERT INTO `t_card` VALUES (1, '350821199612093158');
INSERT INTO `t_card` VALUES (2, '351831216545664813');

-- ----------------------------
-- Table structure for t_class
-- ----------------------------
DROP TABLE IF EXISTS `t_class`;
CREATE TABLE `t_class`  (
  `cid` int(11) NOT NULL AUTO_INCREMENT,
  `cname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`cid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of t_class
-- ----------------------------
INSERT INTO `t_class` VALUES (1, 'java');
INSERT INTO `t_class` VALUES (2, '大数据');

-- ----------------------------
-- Table structure for t_person
-- ----------------------------
DROP TABLE IF EXISTS `t_person`;
CREATE TABLE `t_person`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `card_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of t_person
-- ----------------------------
INSERT INTO `t_person` VALUES (1, 'aa', 1);
INSERT INTO `t_person` VALUES (2, 'bb', 2);

-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (
  `id` int(11) NOT NULL,
  `role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES (1, '群众');
INSERT INTO `t_role` VALUES (2, '团员');
INSERT INTO `t_role` VALUES (3, '党员');

-- ----------------------------
-- Table structure for t_student
-- ----------------------------
DROP TABLE IF EXISTS `t_student`;
CREATE TABLE `t_student`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `class_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of t_student
-- ----------------------------
INSERT INTO `t_student` VALUES (1, 'xiaoming', 1);
INSERT INTO `t_student` VALUES (2, 'xiao', 1);
INSERT INTO `t_student` VALUES (3, 'ss', 2);

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'aa', '123');
INSERT INTO `t_user` VALUES (2, 'bb', '321');
INSERT INTO `t_user` VALUES (3, 'cc', '2121');

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES (1, 1, 2);
INSERT INTO `t_user_role` VALUES (2, 1, 1);
INSERT INTO `t_user_role` VALUES (3, 2, 2);
INSERT INTO `t_user_role` VALUES (4, 3, 2);
INSERT INTO `t_user_role` VALUES (5, 3, 3);

SET FOREIGN_KEY_CHECKS = 1;