本文中的部分案例代码来自MyBtais动态sql和特殊符号处理(案例集锦:不定条件查询+模糊查询+批量查询+批量删除…)
缓存(即cache)的作用是为了减去数据库的压力,提高数据库的性能。缓存实现的原理是从数据库中查询出来的对象在使用完后不销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存中获取,不再向数据库执行select语句,减少对数据库的查询次数,提高了数据库的性能。缓存是使用Map集合存储数据。
MyBatis有一级缓存和二级缓存之分。
一级缓存的作用域是同一个SqlSession,在同一个SqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库查询的数据写到缓存(内存),第二次会从缓存中获取数据而不进行数据库查询,大大提高了查询效率。当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。MyBtais默认启动以及缓存。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递的参数也相同时,第一次执行完毕会将数据库中查询到的数据写到缓存(内存),第二次会直接从缓存中获取,从而提高了查询效率。MyBatis默认不开启二级缓存,需要在MyBtais全局配置文件中进行setting配置开启二级缓存。
MyBatis默认开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数个sql完全一致的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次sql,使用SqlSession第一次查询后,MyBatis会将其放在缓存中,之后再查询时若没有缓存失效或超时,SqlSession都会取出当前缓存的数据,不会再发送sql到数据库。
我们做一个查询的测试
/**
* 批量查询缓存
* 缓存:将数据临时存储在(本地硬盘,内存),减少对数据的访问
*
* 一级缓存是SqlSession级别的,MyBatis默认开启
* 在同一个SqlSession中可以将第一次查询的数据缓存到SqlSession
* 第二次查询相同数据时,就可以直接从SqlSession获取
*/
@Test
public void test5() {
try {
Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
//处理,假设从客户端传递过来要删除的多个参数为一个集合
List<Integer> list = new ArrayList<>();
list.add(1);
//查询
System.out.println("同一个sqlSession中第一次查询id=1的数据为"+userDao.findUser(list));
System.out.println("---------------");
//第二次查询
System.out.println("---------------");
System.out.println("同一个sqlSession中第二次查询id=1的数据为"+userDao.findUser(list));
//提交事务
sqlSession.commit();
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
一级缓存的工作过程如图所示
MyBatis开启一个数据库会话时,会创建一个新的SqlSession对象,其中有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象,当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象会一并释放掉。
1.当SqlSession调用close()方法时,SqlSession对象关闭,直接会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
2.如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是SQlSession对象还可以用。
/**
* 缓存失效: close clear 执行新增,修改,删除操作会清空一级缓存
*/
@Test
public void test5() {
try {
Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
//处理,假设从客户端传递过来要删除的多个参数为一个集合
List<Integer> list = new ArrayList<>();
list.add(1);
//查询
System.out.println("同一个sqlSession中第一次查询id=1的数据为"+userDao.findUser(list));
System.out.println("---------------");
//清空SqlSession缓存(一级缓存)
sqlSession.clearCache();
System.out.println("清空一级缓存");
//第二次查询
System.out.println("---------------");
System.out.println("同一个sqlSession中第二次查询id=1的数据为"+userDao.findUser(list));
//提交事务
sqlSession.commit();
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
3.SqlSession中执行了任何一个update(修改)、delete(删除)、insert(新增)操作,都会清空PerpetualCache对象中的数据,但是SqlSession对象仍然可用。
/**
* 缓存失效: close clear 执行新增,修改,删除操作会清空一级缓存
*/
@Test
public void test5() {
try {
Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
//处理,假设从客户端传递过来要删除的多个参数为一个集合
List<Integer> list = new ArrayList<>();
list.add(1);
//查询
System.out.println("同一个sqlSession中第一次查询id=1的数据为"+userDao.findUser(list));
System.out.println("---------------");
//执行修改语句后缓存会失效
User user = new User();
user.setId(1);
user.setAge(20);
userDao.updateUser(user);
System.out.println("执行修改语句缓存失效");
//第二次查询
System.out.println("---------------");
System.out.println("同一个sqlSession中第二次查询id=1的数据为"+userDao.findUser(list));
//提交事务
sqlSession.commit();
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
二级缓存的作用域是SqlSessionFactory级别,整个应用程序只有一个。二级缓存区域是根据mapper的namespace划分的,相同的namespace的mapper查询的数据缓存在同一个区域,如果使用mapper代理方法每一个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper进行划分的。
每次查询都会先从缓存区域查找,如果找不到则从数据库进行查询,并将查询到的数据写入缓存。MyBtais内部缓存使用HashMap,key为hashCode+sqlid+sql语句,value为从查询出来映射生成的java对象。SqlSession执行任何一个update(修改)、delete(删除)、insert(新增)操作commit提交后都会清空缓存区域,防止脏读。
1.在MyBtais全局配置文件中开启二级缓存,具体视版本而言。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadTriggerMethods" value="true"/>
settings>
2.POJO序列化,将所有的POJO类(bean)实现序列化接口java.io.Serializable。
package com.cwd.mybatis.bean;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
import java.util.Date;
@Alias("User")
public class User implements Serializable {
//生成实体类序列化id
private static final long serialVersionUID = -7383964035746655660L;
private Integer id;
private String name;//姓名
private Integer age;//年龄
private Date birthday;//生日
public Integer getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
}
3.配置映射文件
在Mapper.xml映射文件中添加cache标签,表示次mapper开启二级缓存。
<cache>cache>
<select id="findUser" resultType="User">
SELECT * FROM t_user WHERE id in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
foreach>
select>
/**
* 二级缓存是SqlSessionFactory级别的,整个应用程序只有一个
* 以namespace划分缓存区域
*/
@Test
public void test6() {
try {
Reader reader = Resources.getResourceAsReader("MyBatisConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
//SqlSession1,使用SqlSession查询第一次查询数据
SqlSession sqlSession1 = sessionFactory.openSession();
UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
//处理,假设从客户端传递过来要删除的多个参数为一个集合
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
System.out.println("SqlSession1查询id=1,2的数据为"+userDao1.findUser(list1));
sqlSession1.commit();
sqlSession1.close();//SqlSession关闭时会将数据写入二级缓存
System.out.println("----------------------------------");
//SqlSession2,使用SqlSession查询第二次查询数据
SqlSession sqlSession2 = sessionFactory.openSession();
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
//处理,假设从客户端传递过来要删除的多个参数为一个集合
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
System.out.println("SqlSession2查询id=1,2的数据为"+userDao2.findUser(list1));
sqlSession2.commit();
sqlSession2.close();
} catch (IOException e) {
e.printStackTrace();
}
}