08. 一級緩存和二級緩存
1、一級緩存
Mybatis的一級緩存是指Session緩存。一級緩存的作用域默認是一個SqlSession。Mybatis默認開啟一級緩存。
也就是在同一個SqlSession中,執(zhí)行相同的查詢SQL,第一次會去數(shù)據(jù)庫進行查詢,并寫到緩存中;
第二次以后是直接去緩存中取。
當執(zhí)行SQL查詢中間發(fā)生了增刪改的操作,MyBatis會把SqlSession的緩存清空。
一級緩存的范圍有SESSION和STATEMENT兩種,默認是SESSION,如果不想使用一級緩存,可以把一級緩存的范圍指定為STATEMENT,這樣每次執(zhí)行完一個Mapper中的語句后都會將一級緩存清除。
如果需要更改一級緩存的范圍,可以在Mybatis的配置文件中,在下通過localCacheScope指定。
<setting name="localCacheScope" value="STATEMENT"/>

一級緩存的生命周期
a、MyBatis在開啟一個數(shù)據(jù)庫會話時,會 創(chuàng)建一個新的SqlSession對象,SqlSession對象中會有一個新的Executor對象。Executor對象中持有一個新的PerpetualCache對象;當會話結束時,SqlSession對象及其內部的Executor對象還有PerpetualCache對象也一并釋放掉。
b、如果SqlSession調用了close()方法,會釋放掉一級緩存PerpetualCache對象,一級緩存將不可用。
c、如果SqlSession調用了clearCache(),會清空PerpetualCache對象中的數(shù)據(jù),但是該對象仍可使用。
d、SqlSession中執(zhí)行了任何一個update操作(update()、delete()、insert()) ,都會清空PerpetualCache對象的數(shù)據(jù),但是該對象可以繼續(xù)使用
示例:
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.company.project.dao.UserDao">
<sql id="selectResult">
id,user_name,password,reg_time
</sql>
<resultMap type="UserPo" id="userResult">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="password" column="password"/>
<result property="regTime" column="reg_time"/>
</resultMap>
<select id="findById" parameterType="int" resultMap="userResult">
select
<include refid="selectResult"></include>
from
t_user
where id = #{id}
</select>
</mapper>
測試類:
package com.company.project.test;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import com.company.project.Po.UserPo;
import com.company.project.dao.UserDao;
import com.company.project.util.MyBatisUtil;
import junit.framework.TestCase;
public class UserTest1 extends TestCase{
Logger logger = Logger.getLogger(UserTest1.class);
public void testFindById() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtil.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
UserPo userPo1 = userDao.findById(1);
logger.debug(userPo1);
//一級緩存,遇到相同的sql語句會把結果緩存在sqlSession中,
//除非數(shù)據(jù)庫遇到了delete,update,insert,數(shù)據(jù)庫數(shù)據(jù)發(fā)生了改變,一級緩存就會清除
//或者sqlSession調用了close()或者clearCache()方法,一級緩存也會清除
//sqlSession.clearCache();
UserPo userPo2 = userDao.findById(1);
logger.debug(userPo2);
}catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
結果:
[DEBUG] 2020-06-22 20:28:25,419 ==> Preparing: select id,user_name,password,reg_time from t_user where id = ?
[DEBUG] 2020-06-22 20:28:25,449 ==> Parameters: 1(Integer)
[DEBUG] 2020-06-22 20:28:25,472 <== Total: 1
[DEBUG] 2020-06-22 20:28:25,475 UserPo [id=1, userName=張三, password=123, regTime=2020-06-18]
[DEBUG] 2020-06-22 20:28:25,475 UserPo [id=1, userName=張三, password=123, regTime=2020-06-18]
根據(jù)結果:會發(fā)現(xiàn)開啟一級緩存時,在一個sqlSession對象中,相同的sql語句,只會執(zhí)行一次,然后還緩存下來,后面直接調用結果。
但是,如果數(shù)據(jù)庫遇到了delete,update,insert,數(shù)據(jù)庫數(shù)據(jù)發(fā)生了改變,一級緩存就會清除,或者sqlSession調用了close()或者clearCache()方法,一級緩存也會清除。
2、二級緩存
Mybatis的二級緩存是指mapper映射文件。二級緩存的作用域是同一個namespace下的mapper映射文件內容,多個SqlSession共享。Mybatis需要手動設置啟動二級緩存。
二級緩存是默認啟用的(要生效需要對每個Mapper進行配置),如想取消,則可以通過Mybatis配置文件中的元素下的子元素來指定cacheEnabled為false。
<settings>
<setting name="cacheEnabled" value="false" />
</settings>
要使用二級緩存除了上面一個配置外,我們還需要在我們每個DAO對應的Mapper.xml文件中定義需要使用的cache。
<mapper namespace="com.company.project.dao.UserDao">
...
<cache />
...
</mapper>
二級緩存的原理:

示例:
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.company.project.dao.UserDao">
<cache />
<sql id="selectResult">
id,user_name,password,reg_time
</sql>
<resultMap type="UserPo" id="userResult">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="password" column="password"/>
<result property="regTime" column="reg_time"/>
</resultMap>
<select id="findById" parameterType="int" resultMap="userResult">
select
<include refid="selectResult"></include>
from
t_user
where id = #{id}
</select>
</mapper>
測試:UserTest2.java
package com.company.project.test;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import com.company.project.Po.UserPo;
import com.company.project.dao.UserDao;
import com.company.project.util.MyBatisUtil;
import junit.framework.TestCase;
public class UserTest2 extends TestCase {
Logger logger = Logger.getLogger(UserTest1.class);
public void testFindById() {
SqlSession sqlSession1 = null;
SqlSession sqlSession2 = null;
try {
sqlSession1 = MyBatisUtil.getSqlSession();
UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
UserPo userPo1 = userDao1.findById(1);
logger.debug(userPo1);
sqlSession2 = MyBatisUtil.getSqlSession();
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
UserPo userPo2 = userDao2.findById(1);
logger.debug(userPo2);
}catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession1.close();
sqlSession2.close();
}
}
}
結果:
[DEBUG] 2020-06-22 20:37:50,141 ==> Preparing: select id,user_name,password,reg_time from t_user where id = ?
[DEBUG] 2020-06-22 20:37:50,142 ==> Parameters: 1(Integer)
[DEBUG] 2020-06-22 20:37:50,143 <== Total: 1
[DEBUG] 2020-06-22 20:37:50,144 UserPo [id=1, userName=張三, password=123, regTime=2020-06-18]
通過觀察,我們發(fā)現(xiàn)開啟了二級緩存,在整個應用中,兩次會話,調用同一句sql,sql只執(zhí)行了一次。