myBatis缓存日志 --SqlSessionTemplate 缓存使用
1. 一级缓存默认是开启的,
MyBatis 默认开启了一级缓存,一级缓存是在SqlSession 层面进行缓存的。即,同一个SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数,只会进行一次数据库查询,然后把数据缓存到缓冲中,以后直接先从缓存中取出数据,不会直接去查数据库。
但是不同的SqlSession对象,因为不用的SqlSession都是相互隔离的,所以相同的Mapper、参数和方法,他还是会再次发送到SQL到数据库去执行,返回结果。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:/spring.xml", "classpath*:/spring-servlet_.xml"})
public class test {
@Resource
SqlSessionTemplate sqlSession;
@Resource
SqlSessionFactoryBean factoryBean;
@Test
public void testWithManeulSession() throws Exception {
//同一个ManulSession下, 会开启一级缓存,
//此处直接使用注入的factoryBean 获取sqlsession(DefaultSqlSession) ,
//请于SqlSessionTemplate类区分开
SqlSessionFactory factory = factoryBean.getObject();
SqlSession manulSqlSession = factory.openSession(true);
UserMapper mapper = manulSqlSession.getMapper(UserMapper.class);
User u = mapper.selectByPrimaryKey(1);
User u_ = mapper.selectByPrimaryKey(1);
UserMapper mapper2 = manulSqlSession.getMapper(UserMapper.class);
User u2 = mapper2.selectByPrimaryKey(1);
System.out.println("同一个ManulSession下, 会开启一级缓存");
/*
运行结果
JDBC Connection [com.mysql.jdbc.JDBC4Connection@68577ba8] will not be managed by Spring
==> Preparing: select id, name, age, sex from User where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, age, sex
<== Row: 1, name1, 20, 0
<== Total: 1
同一个ManulSession下, 会开启一级缓存
* */
}
}
SqlSessionTemplate 特例
注入的单例 SqlSessionTemplate 内部包含SqlSessionProxy对象, 其是对SqlSession的代理. 构造函数如下:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property \'sqlSessionFactory\' is required");
Assert.notNull(executorType, "Property \'executorType\' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
//此处为对sqlsession的一次代理, 代码运行中,由内部类SqlSessionInterceptor 进行拦截
this.sqlSessionProxy =
new Class[]{SqlSession.class},
new SqlSessionTemplate.SqlSessionInterceptor()
);
}
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object t = method.invoke(sqlSession, args);
if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = t;
} catch (Throwable var11) {
......
return unwrapped;
}
}
}
//org.mybatis.spring.SqlSessionUtils#getSqlSession
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
if(holder != null && holder.isSynchronizedWithTransaction()) {
......
return holder.getSqlSession();
}
} else {
...
//最终对每次SqlsessionTemplate的调用被代理为新创建的sqlsession的调用
//所以sqlsessiontemplate每次调用都会有一个新的session生成.
SqlSession session = sessionFactory.openSession(executorType);
....
}
return session;
}
}
使用sqlsessiontemplate模拟缓存
//验证注入的SqlSessionTemplate支持一级缓存
@Test
public void testWithAutoWireSqlsession() {
UserMapper mapper = sqlSessiontemplate.getMapper(UserMapper.class);
User u = mapper.selectByPrimaryKey(1);
User u_ = mapper.selectByPrimaryKey(1);
//注意: 注入的SqlSessionTemplate每次返回Mapper时会生成一个新的SqlSession
System.out.println("注入的SqlSessionTemplate每次返回Mapper时会生成一个新的SqlSession,此处不会用到一级缓存");
//直接使用defaultsqlsession 连接数据库,此处会用到一级缓存.
SqlSession originalSqlSession = sqlSessiontemplate.getSqlSessionFactory().openSession();
User ou = originalSqlSession.getMapper(UserMapper.class).selectByPrimaryKey(1);
User ou_2 = originalSqlSession.getMapper(UserMapper.class).selectByPrimaryKey(1);
System.out.println("同一个Session会用到一级缓存");
运行结果如下:
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4f071df8] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.jdbc.JDBC4Connection@42f3156d] will not be managed by Spring
==> Preparing: select id, name, age, sex from User where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, age, sex
<== Row: 1, name1, 20, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4f071df8]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6722db6e] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.jdbc.JDBC4Connection@42f3156d] will not be managed by Spring
==> Preparing: select id, name, age, sex from User where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, age, sex
<== Row: 1, name1, 20, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6722db6e]
注入的SqlSessionTemplate每次返回Mapper时会生成一个新的SqlSession,此处不会用到一级缓存
JDBC Connection [com.mysql.jdbc.JDBC4Connection@42f3156d] will not be managed by Spring
==> Preparing: select id, name, age, sex from User where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, age, sex
<== Row: 1, name1, 20, 0
<== Total: 1
同一个Session会用到一级缓存
注意使用sqlsessiontemplate调用Mapper时会”Creating a new SqlSession” 两次, 并两次连接数据库,
底部直接使用defaultsqlsession 连接数据库,此处会用到一级缓存. 所有只有一次数据库连接.
2. 二级缓存
默认二级缓存是不开启的,需要手动进行配置。
开启方式:
1. 全局开关设置为true (默认为true)
<configuration>
<settings>
<!-- 全局映射器启用缓存, 默认为true -->
<setting name="cacheEnabled" value="true" />
<setting name="logImpl" value="STDOUT_LOGGING" ></setting>
</settings>
</configuration>
2 二级缓存的scope是mapper, 所以需要开启指定mapper的缓存
<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true"/>
注意: readOnly:是否只读,如果为true,则所有相同的sql语句返回的是同一个对象(有助于提高性能,但并发操作同一条数据时,可能不安全),如果设置为false,则相同的sql,后面访问的是cache的clone副本。
readOnly为true时, pojo对象需要事项Serializable接口
3 commit后才生效.
当使用二级缓存时候,sqlSession需要使用commit时候才会生效, 设置autoCommit=true无效
使用damo:
@Test
public void test1() throws Exception {
SqlSessionFactory factory = factoryBean.getObject();
SqlSession session = factory.openSession(true);
UserMapper mapper = (UserMapper) session.getMapper(UserMapper.class);
User u = mapper.selectByPrimaryKey(1);
User u_ = mapper.selectByPrimaryKey(1);
//注意; 使用二级缓存必须先commit
session.commit();
SqlSession session2 = factory.openSession(true);
UserMapper mapper2 = (UserMapper) session2.getMapper(UserMapper.class);
User u2 = mapper2.selectByPrimaryKey(1);
SqlSession session3 = factory.openSession();
UserMapper mapper3 = (UserMapper) session3.getMapper(UserMapper.class);
User u3 = mapper3.selectByPrimaryKey(1);
SqlSession session4 = factory.openSession();
UserMapper mapper4 = (UserMapper) session4.getMapper(UserMapper.class);
User u4 = mapper4.selectByPrimaryKey(1);
System.out.println(u4.getAge());
}
运行结果:
Cache Hit Ratio [com.autohome.jiajiamall.model.dao.UserMapper]: 0.0
JDBC Connection [com.mysql.jdbc.JDBC4Connection@74e47444] will not be managed by Spring
==> Preparing: select id, name, age, sex from User where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, age, sex
<== Row: 1, name1, 20, 0
<== Total: 1
Cache Hit Ratio [com.autohome.jiajiamall.model.dao.UserMapper]: 0.0
Cache Hit Ratio [com.autohome.jiajiamall.model.dao.UserMapper]: 0.3333333333333333
Cache Hit Ratio [com.autohome.jiajiamall.model.dao.UserMapper]: 0.5
Cache Hit Ratio [com.autohome.jiajiamall.model.dao.UserMapper]: 0.6
20
使用二级缓存后,
映射文件所有的select 语句会被缓存
映射文件的所有的insert、update和delete语句会刷新缓存
缓存会使用默认的Least Recently Used(LRU,最近最少使用原则)的算法来回收缓存空间
根据时间表,比如No Flush Interval,(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新
缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以很安全的被调用者修改,不干扰其他调用者或县城所作的潜在修改