Mybatis 系列9-缓存
像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
Mybatis 中缓存分为一级缓存,二级缓存。
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
一级缓存
为了看的更清楚,重新创建个maven工程
1、pom文件:
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2、resource创建log4j.properties、jdbcConfig.properties、SqlMapConfig.xml
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db2
jdbc.username=root
jdbc.password=123456
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置properties-->
<properties resource="jdbcConfig.properties"></properties>
<!--<settings>
<setting name="cacheEnabled" value="true"/>
</settings>-->
<!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<package name="com.mantishell.domain"></package>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置 -->
<mappers>
<package name="com.mantishell.dao"></package>
</mappers>
</configuration>
3、创建持久层的配置文件com/mantishell.dao/IUserDao.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.mantishell.dao.IUserDao">
<!--开启二级缓存-->
<!--<cache />-->
<!-- 查询所有 -->
<select id="findAll" resultType="user">
select * from user
</select>
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
</select>
<!-- 更新用户信息-->
<update id="updateUser" parameterType="user">
update user set username=#{username},address=#{address} where id=#{id}
</update>
</mapper>
4、创建实体
package com.mantishell.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
5、持久层接口:
package com.mantishell.dao;
import com.mantishell.domain.User;
import java.util.List;
public interface IUserDao {
/**
* 查询所有用户,同时获取到用户下所有账户的信息
* @return
*/
List<User> findAll();
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
/**
* 更新用户信息
* @param user
*/
void updateUser(User user);
}
6、测试
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache(){
User user1 = userDao.findById(41);
System.out.println(user1);
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
结果是:
com.mantishell.domain.User@282003e1
2020-03-17 22:16:36,821 554 [ main] DEBUG com.mantishell.dao.IUserDao - Cache Hit Ratio [com.mantishell.dao.IUserDao]: 0.0
com.mantishell.domain.User@282003e1
true
可以看出第二次查询时并没有去数据库中查找,而是从缓存里取的。
当把sqlSession关闭掉或清空缓存:
@Test
public void testFirstLevelCache(){
User user1 = userDao.findById(41);
System.out.println(user1);
sqlSession.close();
//再次获取SqlSession对象
sqlSession = factory.openSession();
//sqlSession.clearCache();//此方法也可以清空缓存
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
结果是:
com.mantishell.domain.User@6e2c9341
2020-03-17 22:26:28,239 619 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e]
2020-03-17 22:26:28,239 619 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 1413378318 to pool.
2020-03-17 22:26:28,239 619 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-03-17 22:26:28,240 620 [ main] DEBUG source.pooled.PooledDataSource - Checked out connection 1413378318 from pool.
2020-03-17 22:26:28,240 620 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e]
2020-03-17 22:26:28,242 622 [ main] DEBUG ntishell.dao.IUserDao.findById - ==> Preparing: select * from user where id = ?
2020-03-17 22:26:28,242 622 [ main] DEBUG ntishell.dao.IUserDao.findById - ==> Parameters: 41(Integer)
2020-03-17 22:26:28,247 627 [ main] DEBUG ntishell.dao.IUserDao.findById - <== Total: 1
com.mantishell.domain.User@4e4aea35
false
显然从数据库查询了两次。
二级缓存
二级缓存是
mapper
映射级别的缓存,多个SqlSession去操作同一个 Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession
的。
二级缓存需要手动开启
sqlSession1查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
此时如果SqlSession3去执行相同mapper映射下的sql语句,执行commit提交,将会清空该mapper映射下的二级缓存区的数据。
SqlSession2去查询与SqlSession1相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存的开启和关闭
1、在SqlMapConfig.xml 文件开启二级缓存
<!--开启二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
cacheEnabled的取值默认是true,所以这一步可以省略。true-表示开启;false-表示关闭
2、持久层配置文件修改
<?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.mantishell.dao.IUserDao">
<!--开启二级缓存-->
<cache />
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
</select>
<select>
标签中设置useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。
注意:如果每次查询都需要最新的数据,那么需要设置为useCache=false,禁用二级缓存。
测试:
@Test
public void testFirstLevelCache(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2);
}
首先关闭了一级缓存,免得影响后面结果。从结果可以看出,两次查询中sql只执行一次,也就是说是从二级缓存中取的值
2020-03-17 22:54:17,508 287 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2020-03-17 22:54:17,904 683 [ main] DEBUG source.pooled.PooledDataSource - Created connection 392781299.
2020-03-17 22:54:17,904 683 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@17695df3]
2020-03-17 22:54:17,912 691 [ main] DEBUG ntishell.dao.IUserDao.findById - ==> Preparing: select * from user where id = ?
2020-03-17 22:54:17,934 713 [ main] DEBUG ntishell.dao.IUserDao.findById - ==> Parameters: 41(Integer)
2020-03-17 22:54:17,959 738 [ main] DEBUG ntishell.dao.IUserDao.findById - <== Total: 1
com.mantishell.domain.User@282003e1
2020-03-17 22:54:17,967 746 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@17695df3]
2020-03-17 22:54:17,968 747 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@17695df3]
2020-03-17 22:54:17,968 747 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 392781299 to pool.
2020-03-17 22:54:17,972 751 [ main] DEBUG com.mantishell.dao.IUserDao - Cache Hit Ratio [com.mantishell.dao.IUserDao]: 0.5
com.mantishell.domain.User@5606c0b