文章目录
- 前言
- 一对一
- 一对多
- 多对一
- 多对多
- 遇到的问题
- 总结
- 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;