http://blog.csdn.net/ecsoftcn/archive/2007/09/09/1777904.aspx
前几天和朋友讨论数据库Cache的时候,谈到了iBatis框架不支持一级缓存,后来尝试作了一些扩展来支持一级缓存,放出来大家探讨一下。
首先让我们简单回顾一下缓存的概念。直白的说,缓存就是把从数据库里查出来的数据放到内存中,供后续操作使用。例如,某个应用需要让业务人员查询前日的数据报表,可能同时有很多人在查询该数据,并且数据没有实时的要求,则可以在第一次查询的时候把结果放到缓存中,以提高后续操作的效率。缓存分为一级缓存和二级缓存,所谓一级缓存通常是指在一次数据库事物范围内的缓存,当数据库事物提交,连接被释放后,该缓存就被销毁了;所谓二级缓存通常指存在时间比较长,例如1天等。一级缓存通常在事物开启的时候建立,在事物提交或回滚的时候销毁。Hibernate对一级缓存和二级缓存都支持的很好,而ibatis则只对应用二级缓存提供了内置的支持。因此本文重点讨论如何为ibatis提供一级缓存功能。
先介绍一下我的环境:Spring1.2.6 + iBatis2.1.3 + MySql5.0。Spring已经成为事实上的标准,对iBatis提供了非常好的支持。因此我们在此讨论如何在Spring环境下提供对iBatis的一级缓存支持。Spring对iBatis的封装主要是通过SqlMapClientTemplate来实现的,而事物方面这是通过TransactionTemplate来提供支持,因此要提供一级缓存的功能,就要在这两个类上做一些文章。 当前事物的缓存可以放在当前的线程(ThreadLocal)中,看下面的代码:
public class CacheModel {
private static final Log logger = LogFactory
.getLog(CacheModel.class);
private static final ThreadLocal<Map<Object, Object>> cacheModel = new ThreadLocal<Map<Object, Object>>();
synchronized static boolean exist() {
return cacheModel.get() != null;
}
public synchronized static void add(Object key, Object value) {
if (exist()) {
cacheModel.get().put(key, value);
}
}
public synchronized static Object get(Object key) {
if (exist()) {
return cacheModel.get().get(key);
}
return null;
}
public synchronized static void initial() {
if (!exist()) {
cacheModel.set(new HashMap<Object, Object>());
logger.info("Initial current transaction's Cache.");
} else {
throw new RuntimeException("Current transaction's CacheModel allready initial!");
}
}
public synchronized static void destroy() {
cacheModel.set(null);
logger.info("Destroy current transaction's Cache.");
}
}
接下来扩展TransactionTemplate,实现在开启和结束事物的时候创建和销毁CacheModel:
public class CacheTransactionTemplate extends TransactionTemplate {
private static final long serialVersionUID = 489621858441822688L;
private boolean cacheable = true;
private boolean isInitialized = false;
/* (non-Javadoc)
* @see org.springframework.transaction.support.TransactionTemplate#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
isInitialized = true;
}
/* (non-Javadoc)
* @see org.springframework.transaction.support.TransactionTemplate#execute(org.springframework.transaction.support.TransactionCallback)
*/
@Override
public Object execute(TransactionCallback action) throws TransactionException {
TransactionStatus status = getTransactionManager().getTransaction(this);
initialCacheModel(status);
Object result = null;
try {
result = action.doInTransaction(status);
logger.debug(action);
} catch (RuntimeException ex) {
// transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
} catch (Error err) {
// transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
try {
getTransactionManager().commit(status);
} finally {
destoryCacheModel(status);
}
return result;
}
/**
* Perform a rollback, handling rollback exceptions properly.
*
* @param status object representing the transaction
*
* @param ex the thrown application exception or error
*
* @throws TransactionException in case of a rollback error
*/
private void rollbackOnException(TransactionStatus status, Throwable ex)
throws TransactionException {
logger.debug("Initiating transaction rollback on application exception", ex);
try {
getTransactionManager().rollback(status);
} catch (RuntimeException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
} catch (Error err) {
logger.error("Application exception overridden by rollback error", ex);
throw err;
} finally {
destoryCacheModel(status);
}
}
/**
* Initail current transaction's CacheModel.
*
* <p>
* Must be a new Transaction.
*
* Current transaction's CacheModel must have not been initialized.
* </p>
*
* @param status
*/
private void initialCacheModel(TransactionStatus status) {
if (cacheable && status != null && status.isNewTransaction()) {
if (!CacheModel.exist()) {
CacheModel.initial();
}
}
}
/**
* Destroy current transaction's CacheModel.
*
* <p>
* Must be a new Transaction.
*
* Current transaction's CacheModel must have been initialized.
* </p>
*
* @param status
*/
private void destoryCacheModel(TransactionStatus status) {
if (cacheable && status != null && status.isNewTransaction()) {
if (CacheModel.exist()) {
CacheModel.destroy();
}
}
}
/**
*
* @param cacheable
*/
public void setCacheable(boolean cacheable) {
if (!isInitialized) {
this.cacheable = cacheable;
}
}
}
上面的代码重写了TransactionTemplate的 execute方法,加入了对CacheModel的支持。接下来再扩展SqlMapClientTemplate,并重写query方法:
public class CacheSqlMapClientTemplate extends SqlMapClientTemplate {
private static final Log logger = LogFactory
.getLog(CacheSqlMapClientTemplate.class);
private static final String DEFAULT_SPARATOR = "_";
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForList(java.lang.String, java.lang.Object, int, int)
*/
@Override
public List queryForList(String statementName, Object parameterObject, int skipResults,
int maxResults) throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject)
+ skipResults + maxResults;
List result = queryDataFromCacheModel(key, List.class);
if (result != null) {
return result;
}
result = super.queryForList(statementName, parameterObject, skipResults, maxResults);
addDataToCacheModel(key, result);
return result;
}
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForList(java.lang.String, java.lang.Object)
*/
@Override
public List queryForList(String statementName, Object parameterObject)
throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject);
List result = queryDataFromCacheModel(key, List.class);
if (result != null) {
return result;
}
result = super.queryForList(statementName, parameterObject);
addDataToCacheModel(key, result);
return result;
}
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForObject(java.lang.String, java.lang.Object, java.lang.Object)
*/
@Override
public Object queryForObject(String statementName, Object parameterObject, Object resultObject)
throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject)
+ DEFAULT_SPARATOR + generateKeyPart(resultObject);
Object result = queryDataFromCacheModel(key, Object.class);
if (result != null) {
return result;
}
result = super.queryForObject(statementName, parameterObject, resultObject);
addDataToCacheModel(key, result);
return result;
}
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForObject(java.lang.String, java.lang.Object)
*/
@Override
public Object queryForObject(String statementName, Object parameterObject)
throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject);
Object result = queryDataFromCacheModel(key, Object.class);
if (result != null) {
return result;
}
result = super.queryForObject(statementName, parameterObject);
addDataToCacheModel(key, result);
return result;
}
private <T> T queryDataFromCacheModel(Object key, Class<T> type) {
if (CacheModel.exist()) {
logger.info("Look up data from current transaction's CacheModel , KEY={" + key + "}");
Object value = CacheModel.get(key);
if (value != null) {
return type.cast(value);
} else {
logger
.info("Current transaction's CacheModel dosen't contain any data relate with {"
+ key + "}");
}
}
return null;
}
private <T> void addDataToCacheModel(Object key, T value) {
if (null == value) {
return;
}
if (CacheModel.exist()) {
CacheModel.add(key, value);
logger.info("Add data to current transaction's CacheModel , KEY={" + key + "}");
}
}
private String generateKeyPart(Object source) {
if (source == null) {
return "";
} else if (source instanceof String) {
return (String) source;
} else if (source instanceof Map) {
return generate((Map) source);
} else {
try {
return generate(BeanUtils.describe(source));
} catch (Exception e) {
return source.toString();
}
}
}
private String generate(Map props) {
try {
Iterator iProps = props.keySet().iterator();
StringBuffer retBuffer = new StringBuffer();
while (iProps.hasNext()) {
String key = (String) iProps.next();
if ("class".equals(key)) {
continue;
}
retBuffer.append(key).append("=[").append(props.get(key)).append("]");
if (iProps.hasNext()) {
retBuffer.append(", ");
}
}
return retBuffer.toString();
} catch (Exception e) {
return "";
}
}
}
类扩展写好了,下面我们看一下如何配置:
<beans default-autowire="byName">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/batchtest"/>
<property name="username" value="root"/>
<property name="password" value="foxdesign"/>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>
<bean id="transactionTemplate"
class="com.company.CacheTransactionTemplate">
<property name="cacheable">
<value>true</value>
</property>
</bean>
<bean id="customSqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="SqlMapConfig.xml"/>
</bean>
<bean id="sqlMapClientTemplate"
class="com.company.CacheSqlMapClientTemplate">
<property name="sqlMapClient" ref="customSqlMapClient"/>
</bean>
<bean id="userCacheDAO" class="com.company.imp.UserCacheDAOImpl">
</bean>
</beans>
经过上面的扩展,执行queryForObject和queryForList方法的时候,查询出来的数据将在当前事物内缓存起来,可以看一下下面的测试代码:
public void testWithTrans() {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
userCacheDAO.getUserByName("Tony");
userCacheDAO.getUserByName("Tony");
userCacheDAO.getUserByName("Aroma");
return null;
}
});
}
该方案只是抛砖引玉的提供了一些想法,大家有什么更好的方法可以讨论一下。
前几天和朋友讨论数据库Cache的时候,谈到了iBatis框架不支持一级缓存,后来尝试作了一些扩展来支持一级缓存,放出来大家探讨一下。
首先让我们简单回顾一下缓存的概念。直白的说,缓存就是把从数据库里查出来的数据放到内存中,供后续操作使用。例如,某个应用需要让业务人员查询前日的数据报表,可能同时有很多人在查询该数据,并且数据没有实时的要求,则可以在第一次查询的时候把结果放到缓存中,以提高后续操作的效率。缓存分为一级缓存和二级缓存,所谓一级缓存通常是指在一次数据库事物范围内的缓存,当数据库事物提交,连接被释放后,该缓存就被销毁了;所谓二级缓存通常指存在时间比较长,例如1天等。一级缓存通常在事物开启的时候建立,在事物提交或回滚的时候销毁。Hibernate对一级缓存和二级缓存都支持的很好,而ibatis则只对应用二级缓存提供了内置的支持。因此本文重点讨论如何为ibatis提供一级缓存功能。
先介绍一下我的环境:Spring1.2.6 + iBatis2.1.3 + MySql5.0。Spring已经成为事实上的标准,对iBatis提供了非常好的支持。因此我们在此讨论如何在Spring环境下提供对iBatis的一级缓存支持。Spring对iBatis的封装主要是通过SqlMapClientTemplate来实现的,而事物方面这是通过TransactionTemplate来提供支持,因此要提供一级缓存的功能,就要在这两个类上做一些文章。 当前事物的缓存可以放在当前的线程(ThreadLocal)中,看下面的代码:
public class CacheModel {
private static final Log logger = LogFactory
.getLog(CacheModel.class);
private static final ThreadLocal<Map<Object, Object>> cacheModel = new ThreadLocal<Map<Object, Object>>();
synchronized static boolean exist() {
return cacheModel.get() != null;
}
public synchronized static void add(Object key, Object value) {
if (exist()) {
cacheModel.get().put(key, value);
}
}
public synchronized static Object get(Object key) {
if (exist()) {
return cacheModel.get().get(key);
}
return null;
}
public synchronized static void initial() {
if (!exist()) {
cacheModel.set(new HashMap<Object, Object>());
logger.info("Initial current transaction's Cache.");
} else {
throw new RuntimeException("Current transaction's CacheModel allready initial!");
}
}
public synchronized static void destroy() {
cacheModel.set(null);
logger.info("Destroy current transaction's Cache.");
}
}
接下来扩展TransactionTemplate,实现在开启和结束事物的时候创建和销毁CacheModel:
public class CacheTransactionTemplate extends TransactionTemplate {
private static final long serialVersionUID = 489621858441822688L;
private boolean cacheable = true;
private boolean isInitialized = false;
/* (non-Javadoc)
* @see org.springframework.transaction.support.TransactionTemplate#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
isInitialized = true;
}
/* (non-Javadoc)
* @see org.springframework.transaction.support.TransactionTemplate#execute(org.springframework.transaction.support.TransactionCallback)
*/
@Override
public Object execute(TransactionCallback action) throws TransactionException {
TransactionStatus status = getTransactionManager().getTransaction(this);
initialCacheModel(status);
Object result = null;
try {
result = action.doInTransaction(status);
logger.debug(action);
} catch (RuntimeException ex) {
// transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
} catch (Error err) {
// transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
try {
getTransactionManager().commit(status);
} finally {
destoryCacheModel(status);
}
return result;
}
/**
* Perform a rollback, handling rollback exceptions properly.
*
* @param status object representing the transaction
*
* @param ex the thrown application exception or error
*
* @throws TransactionException in case of a rollback error
*/
private void rollbackOnException(TransactionStatus status, Throwable ex)
throws TransactionException {
logger.debug("Initiating transaction rollback on application exception", ex);
try {
getTransactionManager().rollback(status);
} catch (RuntimeException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
} catch (Error err) {
logger.error("Application exception overridden by rollback error", ex);
throw err;
} finally {
destoryCacheModel(status);
}
}
/**
* Initail current transaction's CacheModel.
*
* <p>
* Must be a new Transaction.
*
* Current transaction's CacheModel must have not been initialized.
* </p>
*
* @param status
*/
private void initialCacheModel(TransactionStatus status) {
if (cacheable && status != null && status.isNewTransaction()) {
if (!CacheModel.exist()) {
CacheModel.initial();
}
}
}
/**
* Destroy current transaction's CacheModel.
*
* <p>
* Must be a new Transaction.
*
* Current transaction's CacheModel must have been initialized.
* </p>
*
* @param status
*/
private void destoryCacheModel(TransactionStatus status) {
if (cacheable && status != null && status.isNewTransaction()) {
if (CacheModel.exist()) {
CacheModel.destroy();
}
}
}
/**
*
* @param cacheable
*/
public void setCacheable(boolean cacheable) {
if (!isInitialized) {
this.cacheable = cacheable;
}
}
}
上面的代码重写了TransactionTemplate的 execute方法,加入了对CacheModel的支持。接下来再扩展SqlMapClientTemplate,并重写query方法:
public class CacheSqlMapClientTemplate extends SqlMapClientTemplate {
private static final Log logger = LogFactory
.getLog(CacheSqlMapClientTemplate.class);
private static final String DEFAULT_SPARATOR = "_";
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForList(java.lang.String, java.lang.Object, int, int)
*/
@Override
public List queryForList(String statementName, Object parameterObject, int skipResults,
int maxResults) throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject)
+ skipResults + maxResults;
List result = queryDataFromCacheModel(key, List.class);
if (result != null) {
return result;
}
result = super.queryForList(statementName, parameterObject, skipResults, maxResults);
addDataToCacheModel(key, result);
return result;
}
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForList(java.lang.String, java.lang.Object)
*/
@Override
public List queryForList(String statementName, Object parameterObject)
throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject);
List result = queryDataFromCacheModel(key, List.class);
if (result != null) {
return result;
}
result = super.queryForList(statementName, parameterObject);
addDataToCacheModel(key, result);
return result;
}
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForObject(java.lang.String, java.lang.Object, java.lang.Object)
*/
@Override
public Object queryForObject(String statementName, Object parameterObject, Object resultObject)
throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject)
+ DEFAULT_SPARATOR + generateKeyPart(resultObject);
Object result = queryDataFromCacheModel(key, Object.class);
if (result != null) {
return result;
}
result = super.queryForObject(statementName, parameterObject, resultObject);
addDataToCacheModel(key, result);
return result;
}
/* (non-Javadoc)
* @see org.springframework.orm.ibatis.SqlMapClientTemplate#queryForObject(java.lang.String, java.lang.Object)
*/
@Override
public Object queryForObject(String statementName, Object parameterObject)
throws DataAccessException {
String key = statementName + DEFAULT_SPARATOR + generateKeyPart(parameterObject);
Object result = queryDataFromCacheModel(key, Object.class);
if (result != null) {
return result;
}
result = super.queryForObject(statementName, parameterObject);
addDataToCacheModel(key, result);
return result;
}
private <T> T queryDataFromCacheModel(Object key, Class<T> type) {
if (CacheModel.exist()) {
logger.info("Look up data from current transaction's CacheModel , KEY={" + key + "}");
Object value = CacheModel.get(key);
if (value != null) {
return type.cast(value);
} else {
logger
.info("Current transaction's CacheModel dosen't contain any data relate with {"
+ key + "}");
}
}
return null;
}
private <T> void addDataToCacheModel(Object key, T value) {
if (null == value) {
return;
}
if (CacheModel.exist()) {
CacheModel.add(key, value);
logger.info("Add data to current transaction's CacheModel , KEY={" + key + "}");
}
}
private String generateKeyPart(Object source) {
if (source == null) {
return "";
} else if (source instanceof String) {
return (String) source;
} else if (source instanceof Map) {
return generate((Map) source);
} else {
try {
return generate(BeanUtils.describe(source));
} catch (Exception e) {
return source.toString();
}
}
}
private String generate(Map props) {
try {
Iterator iProps = props.keySet().iterator();
StringBuffer retBuffer = new StringBuffer();
while (iProps.hasNext()) {
String key = (String) iProps.next();
if ("class".equals(key)) {
continue;
}
retBuffer.append(key).append("=[").append(props.get(key)).append("]");
if (iProps.hasNext()) {
retBuffer.append(", ");
}
}
return retBuffer.toString();
} catch (Exception e) {
return "";
}
}
}
类扩展写好了,下面我们看一下如何配置:
<beans default-autowire="byName">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/batchtest"/>
<property name="username" value="root"/>
<property name="password" value="foxdesign"/>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>
<bean id="transactionTemplate"
class="com.company.CacheTransactionTemplate">
<property name="cacheable">
<value>true</value>
</property>
</bean>
<bean id="customSqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="SqlMapConfig.xml"/>
</bean>
<bean id="sqlMapClientTemplate"
class="com.company.CacheSqlMapClientTemplate">
<property name="sqlMapClient" ref="customSqlMapClient"/>
</bean>
<bean id="userCacheDAO" class="com.company.imp.UserCacheDAOImpl">
</bean>
</beans>
经过上面的扩展,执行queryForObject和queryForList方法的时候,查询出来的数据将在当前事物内缓存起来,可以看一下下面的测试代码:
public void testWithTrans() {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
userCacheDAO.getUserByName("Tony");
userCacheDAO.getUserByName("Tony");
userCacheDAO.getUserByName("Aroma");
return null;
}
});
}
该方案只是抛砖引玉的提供了一些想法,大家有什么更好的方法可以讨论一下。
相关推荐
ibatis 数据缓存,帮你了解ibatis的数据缓存机制。
iBATIS缓存介绍 iBATIS二级缓存 iBATIS缓存配置
iBATIS缓存的使用方法
ibatis缓存介绍 - 勇泽 - 博客园ibatis缓存介绍 - 勇泽 - 博客园ibatis缓存介绍 - 勇泽 - 博客园ibatis缓存介绍 - 勇泽 - 博客园
ibatis 数据缓存,讨论了ibatis 数据缓存方面的概念,即用法,用到ibatis 数据缓存的可以参考一下
Java_ibatis缓存技术
如何解决动态数据表名,动态字段名情况下,由ibatis缓存select字段而引起的字段找不到的情况?以下是最简单的解决办法! 本文中内容真实可靠,保证用户很快掌握
NULL 博文链接:https://sunfish.iteye.com/blog/1493410
高性能是J2EE应用程序追求的目标,在特定硬件基础上,数据...在对 iBATIS的缓存支持情况进行了介绍和研究的基础上,并结合Spring框架和iBATIS,使用Memcached对iBATIS二级缓存进行了新的实现,使应用的性能得到了很大的提升.
Java ibatis缓存技术,ibatis缓存的详细解释 值得学习!
spring+ibatis+oracle分页缓存源码
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd"> <sqlMap namespace="Account"> <typeAlias alias="Account" type="Account"/> ...
ibatis 缓存配置策略,学习篇 • iBatis对查询结果集进行本地缓存。 • Cache的key由haskcode、checksum、查询参数、sqlmap Id、sql语句、调用方法名等构成。由此可以看出,不同的参数会有不同的Key。注意,他不是以...
NULL 博文链接:https://mov-webhobo.iteye.com/blog/1672240
ibatis学习 ibatis总结 ibatis ibatis ibatis
Ibatis一对一映射提示,需要学习的同学请关注,谢谢。
ibatis 缓存 - 24小时学习网ibatis 缓存 - 24小时学习网ibatis 缓存 - 24小时学习网ibatis 缓存 - 24小时学习网ibatis 缓存 - 24小时学习网
ibatis高级特性 需要的可以免费下载