`
xwycs
  • 浏览: 1725 次
  • 性别: Icon_minigender_1
  • 来自: 广州
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

mybatis 解决join语句缓存刷新研究

阅读更多
项目中由于需要从ibatis升级到mybatis,因为实现的改变,缓存功能也相应的改变,由于需要实现一个当前mybatis没提供的缓存功能,花了些时间研究了源码,并且实现了相应的功能,现就一些心得与大家分享,由于实现中使用了mybatis的plugin拦截器,有可能会改变mybatis的核心行为,所以不保证我的方法足够的安全,只是一个解决问题的思路,言归正传:
在mybatis下每个mapper的namespace对应一个cache, 也就是说一个cahce的id就是mapper的namespace, 当mapper中select 标签中使用useCache=true,那么该select语句就会被保存到cache中,某一个namespace下的某一个select语句可能会有不同的参数值,所以mybatis会分别把不同参值的sql查询结果保存到cache中去,也就是说同一个namespace下的同一个select语句会对应N个不同的cache, 当前mybatis的缓存flush机制是得到namespace对应的缓存后直接clear, 这个的话,同一个namespace下的所有select语句所对应的缓存都被刷新,这一点与ibatis一样,只要cacheModel声明过的select语句就会被flush,我们如何能做到细粒度的flush某个select呢? 或者是当namespace A 中的SQL语句要join 另一个namespace B中的表(B表),比如: namespace A中有这样一个SQL,当B表作了update操作的时候,那么SQL语句 getInfo所对应的缓存就需要flush, 但是当前mybatis没有提供这个功能,也或许我不知道怎么使用这个功能(如果有人知道麻烦赐教)
<select id="getInfo" useCache=true>
select A.name, B.detail from A left join B.id = A.id
</select>

为了解决这个问题,我使用了mybatis的plugin功能,首先拦截Executor class的query方法来得到以下三个参数MappedStatement.class, Object.class, RowBounds.class, 这个三个参数是为了计算存放到cahce中的key,然后再由Executor.createCacheKey(mappedStatement, parameter, rowBounds)方法计算出cacheKey, 这样就可以得到每个select语句被缓存到cahce中时所对应的key, 顺带说一下这个cacheKey的计算是由几个要素来计算的,1.select标签的id, 可通过MappedStatement.getId得到 2. RowBounds 的getOffset()和getLimit() 3. select的sql语句  4. 最重要的一点,也是决定这key是否相同的一点, sql的参数,由上面三个参数中的第二个提供, 当然cahceKey的不同也可能会由RowBounds的不同而不同。
得到cahceKey之后把它保存到一个Map<String, Set<CacheKey>>类型的map里,String对应一个sqlid, 比如上面提到的sql语句 getInfo, 不过还要加上namesapace那就是 A.getInfo,  Set<CacheKey> 保存某个SQL所对应的不同查询参数的不同结果。当我们得到想要flush的select 的cachekey之后,就可以拦腰Executor class的update方法(包括insert,update,delete), 至于过程很简单,上源码。

在sqlMapConfig.xml中加上:
  <plugins>
          <plugin interceptor="com.nexaweb.bankcore.interceptor.FlushCacheInterceptor">
               <property name="ClientGroup.getClientGroupByClientId" value="Client"/>
   </plugin>

实现Interceptor接口:
@Intercepts( {
		@Signature(type = Executor.class, method = "update", args = {
				MappedStatement.class, Object.class }),
		@Signature(type = Executor.class, method = "query", args = {
				MappedStatement.class, Object.class, RowBounds.class,
				ResultHandler.class }) })
public class FlushCacheInterceptor implements Interceptor {

	private String property;

	private Properties properties;

	private Map<String, Set<CacheKey>> keyMap = new HashMap<String, Set<CacheKey>>();

	public Object intercept(Invocation invocation) throws Throwable {

		MappedStatement mappedStatement = (MappedStatement) invocation
				.getArgs()[0];

		if (!mappedStatement.getConfiguration().isCacheEnabled())
			return invocation.proceed();

		String sqlId = mappedStatement.getId();
		String nameSpace = sqlId.substring(0, sqlId.indexOf('.'));
		Executor exe = (Executor) invocation.getTarget();
		String methodName = invocation.getMethod().getName();
		if (methodName.equals("query")) {
			for (Object key : properties.keySet()) {
				if (key.equals(sqlId)) {
					Object parameter = invocation.getArgs()[1];
					RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
					Cache cache = mappedStatement.getConfiguration().getCache(nameSpace);
					cache.getReadWriteLock().readLock().lock();
					CacheKey cacheKey = exe.createCacheKey(mappedStatement, parameter, rowBounds);
					try {
						if (cache.getObject(cacheKey) == null) {
							if (keyMap.get(sqlId) == null) {
								Set<CacheKey> cacheSet = new HashSet<CacheKey>();
								cacheSet.add(cacheKey);
								keyMap.put(sqlId, cacheSet);
							} else {
								keyMap.get(sqlId).add(cacheKey);
							}
						}
					} finally {
						cache.getReadWriteLock().readLock().unlock();
					}
					break;
				}
			}
		} else if (methodName.equals("update")) {
			for (Enumeration e = properties.propertyNames(); e.hasMoreElements();) {
				String cacheSqlId = (String) e.nextElement();
				String updateNameSpace = properties.getProperty(cacheSqlId);
				if (updateNameSpace.equals(nameSpace)) {
					String cacheNamespace = cacheSqlId.substring(0, cacheSqlId.indexOf('.'));
					Cache cache = mappedStatement.getConfiguration().getCache(cacheNamespace);
					Set<CacheKey> cacheSet = keyMap.get(cacheSqlId);
					cache.getReadWriteLock().writeLock().lock();
					try {					
						for (Iterator it = cacheSet.iterator(); it.hasNext();) {
							cache.removeObject(it.next());
						}
					} finally {
							cache.getReadWriteLock().writeLock().unlock();
							keyMap.remove(cacheSqlId);
					}	
					
				}
			}
		}

		return invocation.proceed();
	}

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

	public void setProperties(Properties properties) {
		this.properties = properties;
	}
}

    



分享到:
评论

相关推荐

    Mybatis第四天

    通过注解方式,可以在@Select注解中写入复杂的JOIN语句,或者通过动态SQL(使用、、、等标签)来构建灵活的查询条件。 总的来说,Mybatis第四天的学习内容主要集中在提高数据访问性能和简化开发流程上,包括延迟...

    mybatis,mybatis+mysql

    10. **性能优化**:理解MyBatis缓存机制,使用一级缓存和二级缓存提高性能。另外,合理设计数据库索引,避免全表扫描,减少JOIN操作等也是提高数据库性能的关键。 通过以上内容,你将能够掌握MyBatis的基本使用和...

    mybatis项目

    MyBatis支持JOIN操作,可以在Mapper XML文件中编写JOIN语句,或者使用`&lt;association&gt;`、`&lt;collection&gt;`等标签进行复杂关联映射。 7. **事务管理**:MyBatis提供了基于编程和基于配置两种事务管理方式。编程式事务...

    MyBatis3.2.4完全自学手册

    ### MyBatis3.2.4完全自学手册 #### 一、快速入门 **1、MyBatis简介** ...总结起来,MyBatis是一个功能强大、易于使用的持久层框架,通过其丰富的特性和灵活的配置选项,可以有效地解决各种数据库访问问题。

    mybatis-3.4.5

    - **缓存一致性**:如果启用缓存,需要注意数据更新后的缓存同步问题,以保证数据的一致性。 - **事务管理**:合理配置事务边界,避免因事务管理不当导致的数据不一致。 总的来说,MyBatis 3.4.5 提供了一个强大且...

    MyBatis-CRUD

    MyBatis-CRUD 是一个围绕MyBatis框架展开的主题,它主要涵盖了数据库的基本操作,包括创建(Create)、读取(Read)、更新(Update)和删除(Delete)等CRUD操作。MyBatis是一个优秀的持久层框架,它支持定制化SQL、...

    支持连表查询的mybatis-plus

    在描述中提到的“支持连表查询的mybatis-plus”,意味着MP提供了方便的联接查询(JOIN)功能,使得开发者在进行数据库查询时能够更加高效和简洁。 1. **连表查询基础**:在SQL中,连表查询是将两个或多个表的数据...

    Mybatis高级映射查询

    10. 缓存:Mybatis 内置了缓存机制,分为一级缓存(SqlSession 级别)和二级缓存(Mapper 级别),可以有效提高查询效率,减少对数据库的访问。 以上就是 Mybatis 高级映射查询的相关知识点,这些特性使得 Mybatis ...

    mybatis之多对多

    8. **连接查询(Join Queries)**:MyBatis允许我们编写SQL查询来同时获取多方的数据,这通常通过`&lt;join&gt;`标签或者在`&lt;select&gt;`标签内编写JOIN语句实现。 9. **延迟加载(Lazy Loading)**:为了提高性能,MyBatis...

    Mybatis优化

    2. **缓存机制:** 合理地使用MyBatis提供的二级缓存机制,可以显著减少数据库访问次数,提高性能。需要注意的是,二级缓存需要正确地配置和使用,以避免数据不一致的问题。 3. **SQL语句优化:** 优化SQL语句本身...

    mybatis多表查询.zip

    在MyBatis的Mapper XML文件中,可以通过`&lt;select&gt;`标签来定义查询语句,使用`&lt;join&gt;`或嵌套的`&lt;association&gt;`和`&lt;collection&gt;`标签来实现多表联查。例如,`&lt;association&gt;`用于表示一对多或一对一的关系,`...

    mybatis关联映射源码

    MyBatis是一个强大的Java持久层框架,它简化了数据库操作,允许开发者将SQL语句直接集成到Java代码中。在MyBatis中,关联映射是处理对象关系映射(ORM)的重要部分,用于描述实体类之间的关联关系,如一对一...

    MyBatis一对多映射

    3. 缓存机制:利用MyBatis的一级缓存和二级缓存,减少对数据库的访问次数。 综上所述,MyBatis的一对多映射是通过XML映射文件或注解实现的,它能有效地处理主表和从表之间的关联关系,简化了数据操作。在实际开发中...

    mybatis数据操作(增删改查+批量操作)

    3. **更新操作(Update)**:更新数据涉及到修改已有记录,MyBatis允许你在Mapper接口中定义一个更新方法,在XML配置文件中写好SQL更新语句,使用`&lt;update&gt;`标签。更新操作通常需要指定更新条件,确保只修改特定的...

    MyBatis3pdf

    例如,使用foreach可以方便地处理多对一或一对多的关系,生成in语句或者join查询。 在映射对象的设计上,MyBatis支持自动映射和手动映射。自动映射适用于字段名与列名完全一致的情况,而手动映射则允许我们自定义...

    一个MyBatis的学习成果,仅作个人保存

    通过配置和自定义实现,可以灵活地控制缓存的开启、大小、刷新策略等。 这些是MyBatis核心知识点的概览,实际开发中还需要深入理解并灵活运用。学习MyBatis不仅能够提升数据库操作的效率,还能帮助开发者更好地管理...

    MyBatis高级映射(一对一查询)

    但请注意,MyBatis默认不支持懒加载,需要使用MyBatis的二级缓存或者第三方插件如MyBatis-Plus才能实现。 总结来说,MyBatis的一对一查询提供了灵活且高效的解决方案,通过配置或者注解的方式轻松处理对象间的关联...

    mybatis自关联查询

    在 MyBatis 的 XML 映射文件中,我们需要编写对应的 SQL 查询语句。对于一对多的自关联查询,我们可以使用 `&lt;resultMap&gt;` 来定义映射规则,并通过 `&lt;association&gt;` 标签来处理自关联的字段。以下是一个示例: ```...

    mybatis文章所需资源资料.zip

    4. 缓存机制:MyBatis内置了本地缓存和二级缓存,可以提高查询效率,减少数据库负载。 5. 动态SQL:通过使用if、choose、when、otherwise等标签,可以实现非常复杂的动态SQL逻辑,使得SQL更具可读性和可维护性。 6...

Global site tag (gtag.js) - Google Analytics