理解我们目前已经讨论过的不同作用域和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题。
我们可以先画一个流程图,分析一下Mybatis的执行过程!
作用域理解
-
SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
-
SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
-
由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
-
因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域。
-
如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。
-
所以 SqlSession 的最佳的作用域是请求或方法作用域。
- 假设有两个表,一个Teacher表,一个Student表,一个老师会教多个学生,所以老师跟学生的关系是一对多,学生跟老师的关系是多对一
实体类
@Data //GET,SET,ToString,有参,无参构造
// 老师实体类
public class Teacher {
private int id;
private String name;
}
@Data
// 学生实体类
public class Student {
private int id;
private String name;
//多个学生可以是同一个老师,即多对一
private Teacher teacher;
}
Mapper接口对应的Mapper.xml文件
select * from student
注意点说明
这种查询方式等价于嵌套查询
select s.*, (select t.name from teacher t where t.id = s.tid) as tname from student s;
当然,也可以通过表连接的方式查询
2、一对多select s.id sid, s.name sname , t.name tname from student s,teacher t where s.tid = t.id
实体类
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher {
private int id;
private String name;
//一个老师多个学生
private List students;
}
Mapper接口对应的Mapper.xml配置文件
三、Mybatis缓存select s.id sid, s.name sname , t.name tname, t.id tid from student s,teacher t where s.tid = t.id and t.id=#{id}
简介
1、什么是缓存 [ Cache ]?
-
存在内存中的临时数据。
-
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
2、为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
3、什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。
Mybaits缓存
-
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
-
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
-
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
-
-
为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存
一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 一个SqlSession对应一个一级缓存
- 相同的查询条件,会直接从缓存中获取数据,不会查询数据库
- SqlSession关闭或提交后,一级缓存会被清除
- 一级缓存失效的情况
- SqlSession不同
- SqlSeesion相同,查询sql不同
- SqlSeesion相同,两次查询之间执行了增删改操作
- SqlSeesion相同,手动清除了缓存
二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
-
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
-
如果当前会话关闭或提交了,这个会话对应的一级缓存就没了;如果开启了二级缓存,一级缓存中的数据会被保存到二级缓存中;
-
同样的,同一个命名空间下
-
一个命名空间(mapper类)对应一个二级缓存
-
一级缓存测试
@Test
public void test11() {
// 声明一个SqlSession
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 同一个sqlSession下,声明三个mapper
UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
// sqlSession中的userMapper1查询
List users1 = userMapper1.selectUserList();
// sqlSession2中的userMapper2查询
List users2 = userMapper2.selectUserList();
// 结果为true,命中此sqlSession的一级缓存
System.out.println(users1 == users2);
// 新增一个teacher
Teacher teacher = new Teacher();
teacher.setId(3);
teacher.setName("hahaha");
teacherMapper.addTeacher(teacher);
// sqlSession2中的userMapper2查询
List users3 = userMapper2.selectUserList();
// 结果为false,说明此sqlSession的任何增删改操作都会影响此sqlSession的一级缓存
System.out.println(users1 == users3);
}
二级缓存测试
// 声明两个SqlSession
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
// 声明三个mapper
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
TeacherMapper teacherMapper2 = sqlSession2.getMapper(TeacherMapper.class);
// sqlSession1中的userMapper1查询
List users1 = userMapper1.selectUserList();
// 查询后提交,一级缓存清除,保存到UserMapper的二级缓存中
sqlSession1.commit();
// sqlSession2中的userMapper2查询
List users20 = userMapper2.selectUserList();
// 结果为true,命中UserMapper的二级缓存
System.out.println(users1 == users20);
// 通过sqlsession2中的teacherMapper2新增一个user
User user = new User();
user.setId(8);
user.setUsername("hahahaha");
user.setPassword("123456");
teacherMapper2.addUser(user);
sqlSession2.commit();
// sqlSession2中的userMapper2查询
List users21 = userMapper2.selectUserList();
// 结果为true,说明其他命名空间的增删改操作不会影响当前命名空间的缓存,是个需要注意的地方
System.out.println(users1 == users21);
总结
- 对于一级缓存,同一个SqlSeesion中,任何增删改操作都会影响此SqlSession的缓存
- 对于二级缓存,只有同一个命名空间的增删改操作才会影响此命名空间的缓存
- 当然,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存



