1. 高级映射以及延迟加载
1.1 多对一映射
注意:谁在前谁就是主表
Student.java
public class Student {//多的一方
private String id;
private String name;
private Teacher tutor;
}
Teacher.java
public class Teacher {//一的一方
private String id;
private String name;
private Set<Student> students;
}
级联属性映射
<resultMap id="StudentResult" type="cn.edu.scau.cmi.zhangtingjie.orm.domain.Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
<result property="tutor.id" column="c_id"/>
</resultMap>
<select id="selectById" resultMap = "StudentResult">
select s.s_id,s.s_name,t.c_id
from s left join t on s.c_id = t.c_id
where s.s_id=#{sid}
</select>
association
<resultMap id="StudentResult" type="cn.edu.scau.cmi.zhangtingjie.orm.domain.Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
<!--一个stu关联一个教师-->
<!--property:映射的pojo类的属性名-->
<!--javaType:指定要映射的java类型-->
<association property="tutor" javaType="cn.edu.scau.cmi.zhangtingjie.orm.domain.Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
</resultMap>
<select id="findStudent" parameterType="java.lang.String" resultMap="StudentResult">
select student.id as s_id,
student.name as s_name,
teacher.id as t_id,
teacher.name as t_name
from student left join teacher on student.tutor = teacher.id
where student.id = #{id};
</select>
分步查询
优点:可复用,支持懒加载
问题在于,怎么关联呢?
<select id="findStudentStep1" resultMap="StudentResult">
select student.id as s_id,
student.name as s_name,
student.tutor as tutor
from orm.student
where student.id = #{id}
</select>
<select id="findTeacher" resultType="cn.edu.scau.cmi.zhangtingjie.orm.domain.Teacher">
select * from orm.teacher where id = #{id};
</select>
- 关联的办法
<resultMap id="StudentResult" type="cn.edu.scau.cmi.zhangtingjie.orm.domain.Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
<!--一个stu关联一个教师-->
<!--select中指定你这个关联是从哪个来的-->
<association property="tutor" select="cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO.findTeacher" column="tutor">
<id property="id" column="id"/>
<result property="name" column="name"/>
</association>
</resultMap>
其中column
是你要传进去的参数
延迟加载
延迟加载的核心原理是:
用到的时候再执行查询,不用的时候不查询
,为了提高性能
<association property="tutor" fetchType="lazy">
<!--开启延迟加载,用到的时候再进行查询-->
</association>
它是局部的设置,只对当前的association
中的关联的sql语句有用
可以改成全局的,但凡有关联对象的,都可以使用延迟加载的形式
<settings>
<setting name="logImpl" value="SLF4J"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
注意,如果想要有特殊的需求的话,比如说某个association
不需要懒加载,那么就将fetchType=eager
即可,局部的状态是大于全局的状态的
1.2 一对多的映射原理
public class Teacher {//一个老师对应多个学生
private String id;
private String name;
private Set<Student> students;
}
public class Student {//一个学生对应一个老师
private String id;
private String name;
private Teacher tutor;
}
collection
<select id = "selectByCollection" resultMap="TeacherResult">
select teacher.id as id,
teacher.name as name,
s.id as s_id,
s.name as s_name
from orm.teacher left join student s on teacher.id = s.tutor
where teacher.id = #{id}
</select>
<resultMap id="TeacherResult" type="cn.edu.scau.cmi.zhangtingjie.orm.domain.Teacher">
<result property="name" column="name"/>
<result property="id" column="id"/>
<!--ofType:指明你集合中的每一个元素的类型-->
<collection property="students" ofType="cn.edu.scau.cmi.zhangtingjie.orm.domain.Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
</collection>
</resultMap>
分步查询
<resultMap id="TeacherResult" type="cn.edu.scau.cmi.zhangtingjie.orm.domain.Teacher">
<result property="name" column="name"/>
<result property="id" column="id"/>
<!--ofType:指明你集合中的每一个元素的类型-->
<collection property="students" select="cn.edu.scau.cmi.zhangtingjie.orm.dao.StudentDAO.findStudentStep1" column="sid">
<id property= "id"/>
<result property="name"/>
</collection>
</resultMap>
<resultMap id="TeacherResult" type="cn.edu.scau.cmi.zhangtingjie.orm.domain.Teacher">
<result property="name" column="name"/>
<result property="id" column="id"/>
<!--ofType:指明你集合中的每一个元素的类型-->
<collection property="students" select="cn.edu.scau.cmi.zhangtingjie.orm.dao.StudentDAO.findStudentStep1" column="sid">
<id property= "id"/>
<result property="name"/>
</collection>
</resultMap>
同样的,此处也可以开启延迟加载,开启的方法如上
2. Mybatis缓存
2.1 对缓存的理解
执行DQL语句的时候,将查询结果放到缓存当中,如果下一次还是执行完全相同的DQL语句,直接从缓存中取数据即可,不查询数据库了I/O
,提高执行效率,避免I/O
,其核心的目的还是减少I/O
既然是缓存,那么必须要解决缓存一致性的问题。其解决思路还是清空缓存,当执行了DML的时候,就会将缓存给清除,避免数据的不一致性。
mybatis缓存包括
:
- 一级缓存:将查询到的数据存储到
SqlSession
中,针对于一次会话 - 二级缓存:将查询到的数据存储到
SqlSessionFactory
,代表的是整个数据库 - 或者集成第三方的缓存
缓存只针对于DQL语句,也就是说缓存机制只对应于
select
语句
2.2 一级缓存
一级缓存是默认开启的,其作用域是会话域,不需要做任何配置,只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存
什么时候不走缓存?
SqlSession不是同一个,不走缓存,Sql语句不一致,不走缓存
一级缓存什么时候会失效?
第一次DQL和第二次DQL之间有如下的情况发生,就发生缓存的清空:
- 执行SqlSession的clearCache()方法,手动清除缓存
- 执行了Insert delete update语句,无论是操作哪张表,都会清空缓存
一级缓存是如何执行的?
首先对于每一个SqlSession对象,其内部有一个
Executor
对象,这个对象的功能是操作LocalCache
,这个LocalCache
就是缓存的实体数据,Executor
对象就通过写入
和查询
两种指令来对Cache
进行操作
注意:一级缓存是可能存在脏读的问题,比如说一个会话从始至终都没有执行DML
,因此无论如何都不会清空缓存,而另外一个会话在不断修改数据,这时候就会引发脏读的问题了。
比如说,看下面的执行代码
@Test
public void testCache(){
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(getClass().getClassLoader().getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = build.openSession();
TeacherDAO mapper1 = sqlSession1.getMapper(TeacherDAO.class);
SqlSession sqlSession2 = build.openSession();
TeacherDAO mapper2 = sqlSession2.getMapper(TeacherDAO.class);
for(int i = 0;i<5;i++){
//sql2不断执行更新
if(i%2 ==0){
mapper2.updateTeacher(new Teacher("7","刘二"));
}else{
mapper2.updateTeacher(new Teacher("7","刘三"));
}
sqlSession2.commit();
//sql1不断执行查询
Teacher teacher = mapper1.findTeacher("7");
System.out.println(teacher);
}
sqlSession1.close();
sqlSession2.close();
}
执行结果是
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Preparing: update orm.teacher set name = ? where id = ?;
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Parameters: 刘二(String), 7(String)
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - <== Updates: 1
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3ad83a66]
[main] DEBUG cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO - Cache Hit Ratio [cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO]: 0.0
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
[main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 548554586.
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@20b2475a]
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.findTeacher - ==> Preparing: select * from orm.teacher where id = ?;
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.findTeacher - ==> Parameters: 7(String)
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.findTeacher - <== Total: 1
Teacher{id='7', name='刘二'}
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Preparing: update orm.teacher set name = ? where id = ?;
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Parameters: 刘三(String), 7(String)
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - <== Updates: 1
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3ad83a66]
[main] DEBUG cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO - Cache Hit Ratio [cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO]: 0.0
Teacher{id='7', name='刘二'}
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Preparing: update orm.teacher set name = ? where id = ?;
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Parameters: 刘二(String), 7(String)
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - <== Updates: 1
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3ad83a66]
[main] DEBUG cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO - Cache Hit Ratio [cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO]: 0.0
Teacher{id='7', name='刘二'}
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Preparing: update orm.teacher set name = ? where id = ?;
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Parameters: 刘三(String), 7(String)
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - <== Updates: 1
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3ad83a66]
[main] DEBUG cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO - Cache Hit Ratio [cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO]: 0.0
Teacher{id='7', name='刘二'}
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Preparing: update orm.teacher set name = ? where id = ?;
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - ==> Parameters: 刘二(String), 7(String)
[main] DEBUG c.e.s.c.z.orm.dao.TeacherDAO.updateTeacher - <== Updates: 1
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3ad83a66]
[main] DEBUG cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO - Cache Hit Ratio [cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO]: 0.0
Teacher{id='7', name='刘二'}
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@20b2475a]
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@20b2475a]
[main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 548554586 to pool.
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3ad83a66]
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3ad83a66]
[main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 987249254 to pool.
我们发现每次的查询都是刘二,这样就引发了问题了
2.3 二级缓存
二级缓存的范围是SqlSessionFacory
,使用二级缓存需要:
<setting name = "cacheEnabled" value = "true">
全局性的开启或者关闭所有映射器配置文件中已配置的任何缓存,默认就是true,不需要配置- 在需要使用二级缓存的
SqlMapper.xml
文件中添加配置<cache/>
- 使用二级缓存的实体类对象必须是可序列化的,必须实现
java.io.Serialiable
接口 - SqlSession对象关闭或者提交之后,一级缓存中的数据才会被写入到二级缓存中,此时二级缓存才是可用的
<mapper namespace="cn.edu.scau.cmi.zhangtingjie.orm.dao.TeacherDAO">
<cache/>
</mapper>
它实际上是对一级中的Executor
做了一次装饰,变成了CachingExecutor
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启二级缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
二级缓存的相关配置
eviction
淘汰算法
- LRU:最近最少使用算法,优先淘汰在间隔时间内使用频率最低的对象
- FIFO:先进先出
- SOFT:软引用,淘汰软引用指向的对象
- WEAK:弱引用,淘汰弱引用所指向的对象
flushInterval
- 二级缓存的刷新时间间隔,单位毫秒,如果没有设置,就代表不刷新缓存,只要内存足够大,一直就会向二级缓存中缓存数据,除非执行了增删改
readOnly
- true:多条sql语句执行之后返回的对象是同一个,可能存在性能安全的问题
- false:执行了一次深拷贝,每一次都会返回新的对象
size
- 设置二级缓存中最多可存储的java对象的数量,默认为1024
3. 分页插件
3.1 分页原理
mysql
的limit
后面的两个数字
- startIndex:起始下标,下标从0开始
- pageSize:每页所显示的记录条数
在分页请求发送方的时候,至少都需要页码每页所需要的记录条数
分页sql
limit 开始的下标,记录条数
#如果使用上前端给的数据,那么
select *
from t_car
limit 0,3;
#从0开始,取3条数据
select *
from t_car
limit pageSize*pageNum,pageNum
#如果只写一个数字:那么就是默认起始下标为0
根据前端的约定不同,需要适当对pageSize
进行偏移量的加减
3.2 pageHelper使用
- 引入坐标
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
- 配置插件
<plugins>
<!--添加拦截器-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
SqlSession sqlSession = MybatisUtils.openSession();
StudentDAO mapper = sqlSession.getMapper(StudentDAO.class);
//在执行之前开启分页功能,pageNum就是页码,pageSize就是页的大小,不需要人为计算了
PageHelper.startPage(2,3);
List<Student> students = mapper.selectAll();
students.forEach(System.out::println);
sqlSession.close();
可以获取分页结束之后的对象
List<Student> students = mapper.selectAll();
//用来封装分页信息的对象
PageInfo<Student> pageInfo = new PageInfo<>(students,2);
Mybatis是如何进行分页的?
逻辑分页:先将数据查询存储到内存中,如何根据条件过滤出想要的记录
物理分页:通过数据库所支持的分页语法进行数据的分页展示,如limit
- 直接写SQL分页的语句
- 使用
RowBounds
一次性加载所有符合查询条件的目标数据,根据分页参数的值在内存中实现分页,在JDBC中,这一步是做了优化的,它不会一次性加载太多的数据到内存中,而是先加载一部分数据,然后根据需求在数据库中进行滚动- 通过
Interceptor
拦截器进行实现,通过通过拦截需要select
的语句,然后进行动态拼接,pageHelper就是通过拦截器进行扩展的