您的位置:首页 > 编程语言 > Java开发

分析下为什么spring 整合mybatis后为啥用不上session缓存

2017-02-16 00:00 621 查看
看了@Elim的 Mybatis介绍之缓存

中间有提到

需要注意的是当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例

因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,Guava

所以提出来纠结下

实验下(spring整合mybatis略,网上一堆),先看看mybatis级别的session的缓存

放出打印sql语句

configuration.xml 加入

<settings>
<!-- 打印查询语句 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>


测试源代码如下:

dao类

/**
* 测试spring里的mybatis为啥用不上缓存
*
* @author 何锦彬 2017.02.15
*/
@Component
public class TestDao {

private Logger logger = Logger.getLogger(TestDao.class.getName());

@Autowired
private SqlSessionTemplate sqlSessionTemplate;

@Autowired
private SqlSessionFactory sqlSessionFactory;

/**
* 两次SQL
*
* @param id
* @return
*/
public TestDto selectBySpring(String id) {

TestDto testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
return testDto;
}

/**
* 一次SQL
*
* @param id
* @return
*/
public TestDto selectByMybatis(String id) {

SqlSession session = sqlSessionFactory.openSession();
TestDto testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
return testDto;
}

}


测试service类

@Component
public class TestService {
@Autowired
private TestDao testDao;

/**
* 未开启事务的spring Mybatis查询
*/
public void testSpringCashe() {

//查询了两次SQL
testDao.selectBySpring("1");
}

/**
* 开启事务的spring Mybatis查询
*/
@Transactional
public void testSpringCasheWithTran() {

//spring开启事务后,查询1次SQL
testDao.selectBySpring("1");
}

/**
* mybatis查询
*/
public void testCash4Mybatise() {

//原生态mybatis,查询了1次SQL
testDao.selectByMybatis("1");
}

}


输出结果:

testSpringCashe()方法执行了两次SQL, 其它都是一次

源码追踪:

先看mybatis里的sqlSession

跟踪到最后 调用到 org.apache.ibatis.executor.BaseExecutor的query方法

try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; //先从缓存中取
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); //注意里面的key是CacheKey
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

贴下是怎么取出缓存数据的代码

private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);//从localOutputParameterCache取出缓存对象
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}


发现就是从localOutputParameterCache就是一个PerpetualCache, PerpetualCache维护了个map,就是session的缓存本质了。

重点可以关注下面两个累的逻辑

PerpetualCache , 两个参数, id和map

CacheKey,map中存的key,它有覆盖equas方法,当获取缓存时调用.

这种本地map缓存获取对象的缺点,就我踩坑经验(以前我也用map去实现的本地缓存),就是获取的对象非clone的,返回的两个对象都是一个地址

而在spring中一般都是用sqlSessionTemplate,如下

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:configuration.xml" />
<property name="mapperLocations">
<list>
<value>classpath*:com/hejb/sqlmap/*.xml</value>
</list>
</property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory" />
</bean>


在SqlSessionTemplate中执行SQL的session都是通过sqlSessionProxy来,sqlSessionProxy的生成在构造函数中赋值,如下:

this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());

sqlSessionProxy通过JDK的动态代理方法生成的一个代理类,主要逻辑在InvocationHandler对执行的方法进行了前后拦截,主要逻辑在invoke中,包好了每次执行对sqlsesstion的创建,common,关闭

代码如下:

private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 每次执行前都创建一个新的sqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}

因为每次都进行创建,所以就用不上sqlSession的缓存了.

对于开启了事务为什么可以用上呢, 跟入getSqlSession方法

如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

// 首先从SqlSessionHolder里取出session
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}

if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}

session = sessionFactory.openSession(executorType);

registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

return session;
}


在里面维护了个SqlSessionHolder,关联了事务与session,如果存在则直接取出,否则则新建个session,所以在有事务的里,每个session都是同一个,故能用上缓存了

留下个下小思考

,CacheKey是怎么作为KEY来判断是否执行的是同一条SQL与参数的呢
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: