mybats redis二级缓存扩展

mybatis实现自定义二级缓存,最简单的做法,实现一个org.apache.ibatis.cache.Cache接口就可以了

然后就是在sql xml文件里使用,<cache eviction="LRU" type="com.mark.demo.shiro.mybatis.cache.MyBatisRedisCache" ></cache>

这种简单实现有更新操作时会过期同一个配置文件里的查询缓存,但是跨配置文件就不好处理了。

为了处理跨配置文件更新过期,需要自己实现RedisCachingExecutor implements Interceptor 

下面贴代码:

package com.mark.demo.shiro.mybatis.cache;

import java.io.Serializable;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.CacheKey;

import com.mark.demo.shiro.utils.JedisUtils;
import com.mark.demo.shiro.utils.ObjectUtils;

/*
*hxp(hxpwangyi@126.com)
*2017年9月8日
*
*/
public class MyBatisRedisCache implements Cache,Serializable{
	public static final String mybatis_cache_prefix="mybatis_cache";

	private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

	private String id;

	public MyBatisRedisCache(final String id) {
	    if (id == null) {
	        throw new IllegalArgumentException("缓存没有初始化id");
	    }
	    this.id = id;
	}

	@Override
	public String getId() {
	    return this.id;
	}

	@Override
	public int getSize() {
		
	    return JedisUtils.getMapLen(mybatis_cache_prefix);
	}

	@Override
	public void putObject(Object key, Object value) {
		CacheKey cacheKey=(CacheKey)key;
		String [] keyAry=cacheKey.toString().split(":");
		String myKey=keyAry[2];
	    JedisUtils.setMapField(mybatis_cache_prefix, ObjectUtils.serialize(myKey), value);
	}
	@Override
	public Object getObject(Object key) {
		CacheKey cacheKey=(CacheKey)key;
		String [] keyAry=cacheKey.toString().split(":");
		String myKey=keyAry[2];
	    return JedisUtils.getMapFiled(mybatis_cache_prefix, ObjectUtils.serialize(myKey));
	    
	}

	@Override
	public Object removeObject(Object key) {
	    Object ret=JedisUtils.getMapFiled(mybatis_cache_prefix, ObjectUtils.serialize(key));
		JedisUtils.removeMapField(mybatis_cache_prefix, ObjectUtils.serialize(key));
		return ret;
	}

	@Override
	public void clear() {
	    JedisUtils.del(mybatis_cache_prefix);
	}

	@Override
	public ReadWriteLock getReadWriteLock() {
	    return readWriteLock;
	}

}
package com.mark.demo.shiro.mybatis.cache;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

/*
*hxp(hxpwangyi@126.com)
*2017年9月8日
*
*/
@Intercepts(value = {
		@Signature(args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class}, method = "query", type = Executor.class),
		@Signature(args = {MappedStatement.class, Object.class}, method = "update", type = Executor.class),
		@Signature(args = {boolean.class}, method = "commit", type = Executor.class),
		@Signature(args = {boolean.class}, method = "rollback", type = Executor.class),
		@Signature(args = {boolean.class}, method = "close", type = Executor.class)
})
public  class RedisCachingExecutor implements Interceptor {
	private Set<String>   updateStatementOnCommit = new HashSet<String>();
	RedisCachingManager cachingManager = RedisCachingManagerImpl.getInstance();
	
	public Object intercept(Invocation invocation) throws Throwable {
		String name = invocation.getMethod().getName();
		Object result =null;
		if("query".equals(name))
		{
			result = this.processQuery(invocation);
		}
		else if("update".equals(name))
		{
			result = this.processUpdate(invocation);
		}
		else if("commit".equals(name))
		{
			result = this.processCommit(invocation);
		}
		else if("rollback".equals(name))
		{
			result = this.processRollback(invocation);
		}
		else if("close".equals(name))
		{
			result = this.processClose(invocation);
		}
		return result;
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	/**
	 * when executing a query operation
	 * 1. record this statement's id and it's corresponding Cache Object into Global Caching Manager;
	 * 2. record this statement's id and  
	 * @param invocation
	 * @return
	 * @throws Throwable
	 */
	protected Object processQuery(Invocation invocation) throws Throwable {
		Object result = invocation.proceed();
		return result;
	}
	
	protected Object processUpdate(Invocation invocation) throws Throwable {
	
		Object result = invocation.proceed();
		MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
		updateStatementOnCommit.add(mappedStatement.getId());
		return result;
	}

	protected Object processCommit(Invocation invocation) throws Throwable {
		Object result  = invocation.proceed();
		refreshCache();
		return result;
	}

	protected Object processRollback(Invocation invocation) throws Throwable {
	    Object result = invocation.proceed();
	    clearSessionData();
		return result;
	}

	protected Object processClose(Invocation invocation) throws Throwable {
		Object result = invocation.proceed();
		boolean forceRollback = (Boolean) invocation.getArgs()[0];
		if(forceRollback)
		{
			clearSessionData();
		}
		else
		{
			refreshCache();
		}
		return result;
	}
	
	
	
	/**
	 * when the sqlSession has been committed,rollbacked,or closed,
	 * session buffer query CacheKeys and update Statement collections should be cleared.
	 * 
	 * 当SqlSession 执行了commit()、rollback()、close()方法,
	 * Session级别的查询语句产生的CacheKey集合以及  执行的更新语句集合应该被清空
	 */
	private synchronized void clearSessionData()
	{
	    updateStatementOnCommit.clear();
	}
	
	
	
	/**
	 * refresh the session cache,there are two things have to do:
	 * 1. add this session scope query logs to global cache Manager 
	 * 2. clear the related caches according to the update statements as configured in "dependency" file
	 * 3. clear the session data
	 */
	private synchronized void refreshCache()
	{
		cachingManager.clearRelatedCaches(updateStatementOnCommit);
		clearSessionData();
	}
	
	/**
	 * 
	 * 
	 * Executor插件配置信息加载点
	 * properties中有 "dependency" 属性来指示 配置的缓存依赖配置信息,读取文件,初始化EnhancedCacheManager
	 */
	public void setProperties(Properties properties) {
		
		if(!cachingManager.isInitialized())
		{
			cachingManager.initialize(properties);
		}
	}
}

package com.mark.demo.shiro.mybatis.cache;

import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.cache.Cache;



public interface RedisCachingManager {
	
	
	public boolean isInitialized();
	
	/**
	 * MyBatis是否开启了二级缓存,即 <setting name="cacheEnabled" value="true"/> 
	 * @return
	 */
	public boolean isCacheEnabled();

	/**
	 * 
	 * 初始化 缓存管理器,应该只被调用一次;
	 * 
	 * @param properties
	 * 	properties 中至少包含两个属性:
	 * 		dependency 	: 该值表示着缓存依赖配置文件的位置
	 * 		cacheEnbled	: "true" or "false",该配置必须要与<setting name="cacheEnabled">的值保持一致
	 */
	public void initialize(Properties properties);
	
	
	/**
	 * 整个插件的核心,根据指定的statementId更新与之关联的二级缓存
	 * 传入的StatementId集合是由会话级别的update语句对应的StatementId,
	 * EnhancedCachingManager将通过查询相应的StatementId去查看是否配置了依赖关系,如果有,则将依赖关系中的statementId的查询缓存全部清空
	 * @param set
	 */
	public void clearRelatedCaches(Set<String> set);
	
	

}

package com.mark.demo.shiro.mybatis.cache;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;

import com.mark.demo.shiro.utils.JedisUtils;
import com.mark.demo.shiro.utils.PropertiesLoader;

public class RedisCachingManagerImpl implements RedisCachingManager{
	
	//每一个statementId 更新依赖的statementId集合
	private Map<String,Set<String>> observers=new ConcurrentHashMap<String,Set<String>>();
	
	private boolean initialized = false;
	private boolean cacheEnabled = false;
	
	private volatile static RedisCachingManagerImpl enhancedCacheManager;

	private RedisCachingManagerImpl(){}
	public static RedisCachingManagerImpl getInstance()
	{
		if(enhancedCacheManager==null){
			synchronized (RedisCachingManagerImpl.class) {
				if(enhancedCacheManager==null){
					enhancedCacheManager=new RedisCachingManagerImpl();
				}
			}
		}
		return enhancedCacheManager;
	}
	
	public void clearRelatedCaches(final Set<String> set) {
		for(String observable:set)
		{
			Set<String> relatedStatements = observers.get(observable);
			for(String statementId:relatedStatements)
			{
				JedisUtils.removeMapField(MyBatisRedisCache.mybatis_cache_prefix, statementId);
			}
		}
	}
	public boolean isInitialized() {
		return initialized;
	}
	
	public void initialize(Properties properties)
	{
		PropertiesLoader loader=new PropertiesLoader("mybatis.properties");
		String dependency = loader.getProperty("dependencys");
		if(!("".equals(dependency) || dependency==null))
		{
			InputStream inputStream;
			try 
			{
					inputStream = Resources.getResourceAsStream(dependency);
					XPathParser parser = new XPathParser(inputStream);
					List<XNode> statements = parser.evalNodes("/dependencies/statements/statement");
					for(XNode node :statements)
					{
						Set<String> temp = new HashSet<String>();
						List<XNode> obs = node.evalNodes("observer");
						for(XNode observer:obs)
						{
							temp.add(observer.getStringAttribute("id"));
						}
						this.observers.put(node.getStringAttribute("id"), temp);
					}
					initialized = true;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//cacheEnabled
		String cacheEnabled = properties.getProperty("cacheEnabled", "true");
		if("true".equals(cacheEnabled))
		{
			this.cacheEnabled = true;
		}
	}
	
	public boolean isCacheEnabled() {
		return cacheEnabled;
	}
}


<?xml version="1.0" encoding="UTF-8"?>
<dependencies>
    <statements>
        <!-- 角色授权后刷新角色缓存 -->
        <statement id="com.mark.demo.shiro.mapper.MenuMapper.updateMenu">
        	<observer id="com.mark.demo.shiro.mapper.UserMapper.getMenuTopLever"/>
        </statement>

    </statements>
</dependencies>


<?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>

    <!-- 全局参数 -->
    <settings>
        <!-- 使全局的映射器启用或禁用缓存。 -->
        <setting name="cacheEnabled" value="true"/>

        <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
        <setting name="lazyLoadingEnabled" value="false"/>

        <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
        <setting name="aggressiveLazyLoading" value="true"/>

        <!-- 是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true -->
        <setting name="multipleResultSetsEnabled" value="true"/>

        <!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
        <setting name="useColumnLabel" value="true"/>

        <!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false  -->
        <setting name="useGeneratedKeys" value="false"/>

        <!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分  FULL:全部  -->
        <setting name="autoMappingBehavior" value="PARTIAL"/>

        <!-- 这是默认的执行类型  (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新)  -->
        <setting name="defaultExecutorType" value="REUSE"/>

        <!-- 使用驼峰命名法转换字段。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>

        <!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
        <setting name="localCacheScope" value="STATEMENT"/>

        <!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 -->
        <setting name="jdbcTypeForNull" value="NULL"/>

    </settings>
	
	<typeAliases>
	     <package name="com.mark.demo.shiro.entity" />
	</typeAliases>
	
    <!-- 插件配置 -->
    <plugins>
        <plugin interceptor="com.mark.demo.shiro.mybatis.PaginationInterceptor"/>
        <plugin interceptor="com.mark.demo.shiro.mybatis.cache.RedisCachingExecutor">
            <property name="dependency" value="dependencys.xml"/>
            <property name="cacheEnabled" value="true"/>
        </plugin>
    </plugins>

</configuration>
<?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.mark.demo.shiro.mapper.UserMapper">
	<cache eviction="LRU" type="com.mark.demo.shiro.mybatis.cache.MyBatisRedisCache" ></cache>
    <resultMap type="User" id="userMap">
    	<result column="userId" property="id" />
    	<result column="userName" property="userName" />
    	<result column="password" property="password" />
    	<result column="age" property="age" />
    	<result column="sex" property="sex" />
    	<result column="phone" property="phone" />
    </resultMap>
    
    <select id="getUserByUserName" parameterType="String" resultMap="userMap">
    	select * from user where userName=#{userName}
    </select>
    
    <resultMap type="Menu" id="menuMap">
    	<result column="menuId" property="id"/>
    	<result column="menuName" property="menuName"/>
    	<result column="menuDesc" property="menuDesc"/>
    	<result column="link" property="link"/>
    	<result column="icon" property="icon"/>
    </resultMap>
    
    <select id="getMenuTopLever"  resultMap="menuMap" useCache="true">
    	select * from menu where pid=-1
    </select>
</mapper>


demo地址:https://github.com/13567436138/shiro-demo.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hxpjava1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值