MyBatis源码及资料: https://github.com/coderZYGui/MyBatis-Study
MyBatis系列
- MyBatis — ORM思想、MyBatis概述、日志框架、OGNL
- MyBaits — MyBatis的CRUD操作、别名配置、属性配置、查询结果映射、Mapper组件、参数处理、注解开发
- MyBatis — 动态SQL、if、where、set、foreach、sql片段
- MyBatis — 对象关系映射、延迟加载、关联对象的配置选择
- MyBatis — 缓存机制、EhCache第三方缓存
- MyBatis — MyBatis Generator插件使用(配置详解)
方便了解 关联关系的各种关系,请先参考: UML一一 类图关系 (泛化、实现、依赖、关联、聚合、组合)
跳转到目录
CREATE TABLE `employee1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`dept_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
CREATE TABLE `department1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8;
跳转到目录
EmployeeMapper
// 保存员工信息
void save(Employee emp);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO employee1(name, dept_id) VALUES (#{name}, #{dept.id})
insert>
DepartmentMapper
// 保存一个部门
void save(Department dept);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO department1(name) VALUES (#{name})
insert>
/**
* 测试保存操作
*/
@Test
public void test() {
// 创建部门对象
Department dept = new Department();
dept.setName("开发部");
// 创建员工对象
Employee emp = new Employee();
emp.setName("张三");
emp.setDept(dept); // 将张三分配到开发部中
Employee emp2 = new Employee();
emp2.setName("李四");
emp2.setDept(dept); // 将李四分配到开发部中
SqlSession sqlSession = MybatisUtils.getSqlSession();
DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 保存操作:先保存部门(one方)因为,员工表中需要依赖于部门ID,先保存员工信息,部门信息可能为空
departmentMapper.save(dept);
employeeMapper.save(emp);
employeeMapper.save(emp2);
sqlSession.commit();
sqlSession.close();
}
跳转到目录
通过额外SQL
EmployeeMapper
// 根据id来查询员工信息
Employee getUserById(long empId);
<resultMap id="BaseResultMap" type="Employee">
<result column="id" property="id" />
<result column="name" property="name" />
<result column="dept_id" property="dept.id"/>
resultMap>
<select id="getUserById" resultMap="BaseResultMap">
SELECT id, name, dept_id FROM employee1 WHERE id = #{id}
select>
DepartmentMapper
// 根据部门的id查询出部门对象
Department getDeptById(Long deptId);
<select id="getDeptById" resultType="Department">
SELECT id, name FROM department1 WHERE id = #{id}
select>
因为获取的结果集中的列名和对象的属性名不匹配,设置resultMap解决不匹配问题,
但是结果集中的id列,不能和对象中的dept对象匹配,只能和对象dept的属性id匹配
所以,能获取到部门的id,但是期望得到的是Department对象
解决方案: 根据部门的id,再 额外 发送一条SQL语句,查询出 部门对象 ,把部门对象设置给员工即可!
/**
* 根据id来查询员工信息
*/
@Test
public void test1() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
Employee employee = employeeMapper.getUserById(2L);
// -------------------------------------
/*
因为获取的结果集中的列名和对象的属性名不匹配,设置resultMap解决不匹配问题,
但是结果集中的id列,不能和对象中的dept对象匹配,只能和对象dept的属性id匹配
所以,能获取到部门的id,但是期望得到的是Department对象
解决方案: 根据部门的id,再 额外 发送一条SQL语句,查询出 部门对象 ,把部门对象设置给员工即可!
*/
// 下面三行代码让,MyBatis完成,需要在resultMap中配置
Long deptId = employee.getDept().getId();
// 根据部门id来查询出,部门对象
Department department = departmentMapper.getDeptById(deptId);
employee.setDept(department);
System.out.println(employee);
System.out.println(employee.getDept());
}
在测试方法中不想自己手动发额外的SQL怎么办呢?MyBatis可以帮我们完成,这就需要在resultMap
中配置一下
额外SQL的配置方式
<resultMap id="BaseResultMap" type="Employee">
<result column="id" property="id" />
<result column="name" property="name" />
<result column="dept_id" property="dept.id"/>
<association property="dept" javaType="Department"
select="com.sunny.mapper.StudentMapper.getDeptById"
column="dept_id"
/>
resultMap>
这样以来,就不用了在测试方法中不用自己去写了,实际操作是一样的,只是把额外发送的SQL封装配置起来了.
跳转到目录
假如每一个员工的部门ID都是不同的, 查询所有员工信息, 此时就会发生N+1
问题, 其实就是发送了N+1条SQL语句
1 : SELECT * FROM employee
N: SELECT * FROM department WHERE id = ?
当查询所有员工时,如下图:
解决方案: 使用多表查询(JSON), 一条SQL语句就搞定
EmployeeMapper
//查询所有用户信息(包括部门)
List<Employee> getAllUser();
<resultMap id="BaseResultMap" type="Employee">
<result column="id" property="id"/>
<result column="name" property="name"/>
<association property="dept" javaType="Department">
<result column="d_id" property="id"/>
<result column="d_name" property="name"/>
association>
resultMap>
<select id="getAllUser" resultMap="BaseResultMap">
SELECT e.id, e.name, d.id AS d_id, d.name AS d_name FROM employee1 e JOIN
department1 d ON e.dept_id = d.id
select>
DepartmentMapper 不需要写
/**
* 查询所有用户
*/
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> emps = mapper.getAllUser();
for (Employee emp : emps) {
System.out.println(emp);
}
sqlSession.close();
}
跳转到目录
association元素
,配置单一对象属性
如果需要在查询出来的信息包含关联对象
(这就是上面的在Employee类中,关联的属性–> Department dept)的数据, 就需要使用 内联映射
, 否则就会出现N+1问题;
跳转到目录
CREATE TABLE `employee2` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`dept_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
CREATE TABLE `department2` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
跳转到目录
EmployeeMapper
// 保存员工信息
void save(Employee emp);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO employee2(name, dept_id) VALUES (#{name}, #{deptId})
insert>
DepartmentMapper
// 保存一个部门
void save(Department dept);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO department2(name) VALUES (#{name})
insert>
/**
* 测试保存操作
*/
@Test
public void test() {
// 创建部门对象
Department dept = new Department();
dept.setName("开发部");
// 创建员工对象
Employee emp = new Employee();
emp.setName("张三");
Employee emp2 = new Employee();
emp2.setName("李四");
// 维护对象关系
dept.getEmps().add(emp);
dept.getEmps().add(emp2);
SqlSession sqlSession = MybatisUtils.getSqlSession();
DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
departmentMapper.save(dept);
// 当保存部门信息后,再获取部门ID
emp.setDeptId(dept.getId());
emp2.setDeptId(dept.getId());
employeeMapper.save(emp);
employeeMapper.save(emp2);
sqlSession.commit();
sqlSession.close();
}
跳转到目录
通过额外SQL
EmployeeMapper
// 根据部门id来查询部门的员工信息
List<Employee> getUsersByDeptId(Long deptId);
<select id="getUsersByDeptId" resultType="Employee">
SELECT id, name, dept_id AS deptId FROM employee2 WHERE dept_id = #{deptId}
select>
DepartmentMapper
<resultMap id="BaseResultMap" type="Department">
<result column="id" property="id"/>
<result column="name" property="name"/>
<collection property="emps"
select="com.sunny.mapper.DepartmentMapperMapper.getUsersByDeptId"
column="id"
/>
resultMap>
<select id="getDeptById" resultMap="BaseResultMap">
SELECT id, name FROM department2 WHERE id = #{id}
select>
注意: 这里关联属性是集合类型的,所以要使用collection
元素
/**
* 查询10号ID的部门信息(包括部门的员工)
*/
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
Department dept = departmentMapper.getDeptById(10L);
System.out.println(dept);
System.out.println(dept.getEmps());
/*
* 手动完成 额外SQL查询
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Long deptId = dept.getId();
List emps = employeeMapper.getUsersByDeptId(deptId);
dept.setEmps(emps);
List emps1 = dept.getEmps();
System.out.println(emps1);
*/
sqlSession.close();
}
DepartmentMapper
// 根据id查询部门信息
Department getDeptById(long l);
<resultMap id="BaseResultMap" type="Department">
<result column="id" property="id"/>
<result column="name" property="name"/>
<collection property="emps" ofType="Employee">
<result column="e_id" property="id"/>
<result column="e_name" property="name"/>
collection>
resultMap>
<select id="getDeptById" resultMap="BaseResultMap">
SELECT d.id, d.name,
e.id AS e_id, e.name AS e_name
FROM department2 d JOIN employee2 e ON d.id = e.dept_id
WHERE d.id = #{id}
select>
EmployeeMapper 不需要写
跳转到目录
为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才会发出sql语询进行查询该数据。
MyBatis运行时期的属性配置,放在主配置文件中的
元素中。
MyBatis中直接查询出来的many方对象其实就已经是一个代理对象,当使用many方对象的任意一个属性时,立刻很积极的把关联对象也查询出来,此时性能不会太好。
①MyBatis 缺省情况下,禁用了延迟加载。
②MyBatis 会很积极的去查询关联对象。
③MyBatis 中缺省情况下调用equals. clone. hashCode、 toString 都会触发延迟加载
一般的我们保留clone就可以了,也就是说调用many方对象的toString. hashCode. equals 方法依然不会去发送查询one方的SQL.
在MaBatis的主配置文件中
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="lazyLoadTriggerMethods" value="clone"/>
settings>
这样以来就不会发送额外SQL
了
跳转到目录
单属性
对象, 使用association
元素,通常直接使用多表查询操作,也就是使用内联查询
集合属性
对象,使用collection
元素,通常使用延迟加载
, 也就是额外SQL
处理跳转到目录
CREATE TABLE `student` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `teacher` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `student_teacher` (
`student_id` bigint(20) NOT NULL,
`teacher_id` bigint(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
跳转到目录
StudentMapper
// 保存学生信息
void save(Student stu);
// 因为关系在学生这边.(因为传递的是两个参数,所以要做参数处理,使用注解)
void insertRelationWithTeacher(@Param("studentId") Long studentId, @Param("teacherId") Long teacherId);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO student (name) VALUES (#{name})
insert>
<insert id="insertRelationWithTeacher">
INSERT INTO student_teacher (student_id, teacher_id) VALUES (#{studentId}, #{teacherId})
insert>
TeacherMapper
// 保存老师信息
void save(Teacher teacher);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO teacher(name) VALUES (#{name})
insert>
/**
* 将学生、老师信息存储到各自的表中
* 并将学生、老师的ID插入到中间表中
*/
@Test
public void testSave() {
Teacher t1 = new Teacher();
t1.setName("老师1");
Teacher t2 = new Teacher();
t2.setName("老师2");
Student s1 = new Student();
s1.setName("张三");
Student s2 = new Student();
s2.setName("李四");
// 维护关系(学生的老师们)
s1.getTeachers().add(t1);
s1.getTeachers().add(t2);
s2.getTeachers().add(t1);
s2.getTeachers().add(t2);
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
studentMapper.save(s1);
studentMapper.save(s2);
teacherMapper.save(t1);
teacherMapper.save(t2);
// 当学生和老师信息都保存之后,再维护中间表的数据
for (Teacher teacher1 : s1.getTeachers()) {
studentMapper.insertRelationWithTeacher(s1.getId(), teacher1.getId());
}
for (Teacher teacher2 : s2.getTeachers()) {
studentMapper.insertRelationWithTeacher(s2.getId(), teacher2.getId());
}
sqlSession.commit();
sqlSession.close();
}
跳转到目录
StudentMapper
// 根据id删除学生记录
void deleteStuById(long l);
// 当删除学生的id时,中间表中学生的id也应该删除
void deleteRelationTeacherById(long l);@Param("teacherId") Long teacherId);
<delete id="deleteRelationTeacherById">
DELETE FROM student_teacher WHERE student_id = #{id}
delete>
<delete id="deleteStuById">
DELETE FROM student WHERE id = #{id}
delete>
测试类
/**
* 根据ID删除
*/
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 因为多对多关系, 中间表中列,是两表中的主键,当删除一个学生时,中间表也必然发生改变
// 在删除学生之前,应该先删除中间表中的数据
studentMapper.deleteRelationTeacherById(1L);
studentMapper.deleteStuById(1L);
sqlSession.commit();
sqlSession.close();
}
跳转到目录
使用额外SQL
StudentMapper
// 根据id来查询学生信息
Student get(Long l);
<resultMap id="BaseResultMap" type="Student">
<result column="id" property="id"/>
<result column="name" property="name"/>
<collection property="teachers" ofType="Teacher"
select="com.sunny.mapper.TeacherMapper.selectTeachersByStuId"
column="id"
/>
resultMap>
<select id="get" resultMap="BaseResultMap">
SELECT id,name FROM student WHERE id = #{id}
select>
TeacherMapper
<select id="selectTeachersByStuId" resultType="Teacher">
SELECT t.id, t.name
FROM teacher t JOIN student_teacher st on t.id = st.teacher_id
WHERE st.student_id = #{studentId}
select>
测试类
/**
* 根据Id查询学生信息(包括该学生的老师们)
*/
@Test
public void testQueryByStuId(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.get(1L);
System.out.println(student);
System.out.println(student.getTeachers());
}