增加mybatis乐观锁插件,未测试

This commit is contained in:
winLsy
2018-04-11 22:38:07 +08:00
parent 9be56061eb
commit 2c2963de34
19 changed files with 302 additions and 19 deletions
@@ -1,6 +1,5 @@
package com.micro.fast.common.dao;
package com.micro.fast.common.datasource.dynamic;
import com.micro.fast.common.config.DynamicDataSourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -1,6 +1,7 @@
package com.micro.fast.common.config;
package com.micro.fast.common.datasource.dynamic;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.micro.fast.common.datasource.dynamic.condition.MasterDataSourceCondition;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -13,12 +14,12 @@ import javax.sql.DataSource;
* @author lsy
*/
@Configuration
@AutoConfigureBefore(com.micro.fast.common.config.DynamicDataSourceConfig.class)
@AutoConfigureBefore(DynamicDataSourceConfig.class)
public class DruidDataSourceConfig {
@Bean(name = DynamicDataSourceConfig.MASTER_DATA_SOURCE)
@ConfigurationProperties("spring.datasource.druid.master")
@Conditional(com.micro.fast.common.condition.MasterDataSourceCondition.class)
@Conditional(MasterDataSourceCondition.class)
public DataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@@ -1,4 +1,4 @@
package com.micro.fast.common.dao;
package com.micro.fast.common.datasource.dynamic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,7 +18,7 @@ public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
public Object determineCurrentLookupKey() {
log.debug("数据源{}",DataSourceContextHolder.getDatdasourceName());
log.debug("数据源{}", DataSourceContextHolder.getDatdasourceName());
return DataSourceContextHolder.getDatdasourceName();
}
}
@@ -1,7 +1,7 @@
package com.micro.fast.common.config;
package com.micro.fast.common.datasource.dynamic;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.micro.fast.common.dao.DynamicDataSource;
import com.micro.fast.common.config.SqlSessionFactoryConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -1,4 +1,4 @@
package com.micro.fast.common.annotation;
package com.micro.fast.common.datasource.dynamic.annotation;
import java.lang.annotation.*;
@@ -4,4 +4,4 @@
* @version 1.0
* @since 1.0
*/
package com.micro.fast.common.annotation;
package com.micro.fast.common.datasource.dynamic.annotation;
@@ -1,7 +1,7 @@
package com.micro.fast.common.aspect;
package com.micro.fast.common.datasource.dynamic.aspect;
import com.micro.fast.common.annotation.SwitchDataSource;
import com.micro.fast.common.dao.DataSourceContextHolder;
import com.micro.fast.common.datasource.dynamic.annotation.SwitchDataSource;
import com.micro.fast.common.datasource.dynamic.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
@@ -22,13 +22,13 @@ import java.lang.reflect.Method;
@Component
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.micro.fast.common.annotation.SwitchDataSource)")
@Pointcut("@annotation(com.micro.fast.common.datasource.dynamic.annotation.SwitchDataSource)")
public void mybatisExecutionSqlPointcut(){
}
/**
* 在SwitchDataSource注解的方法之前执行
* @see com.micro.fast.common.annotation.SwitchDataSource
* @see SwitchDataSource
*/
@Before("mybatisExecutionSqlPointcut()")
public void beforeMybatisExecutionSql(JoinPoint joinPoint){
@@ -58,7 +58,7 @@ public class DynamicDataSourceAspect {
/**
* 在SwitchDataSource注解的方法之后执行
* @see com.micro.fast.common.annotation.SwitchDataSource
* @see SwitchDataSource
*/
@After("mybatisExecutionSqlPointcut()")
public void afterMybatisExecutionSql(){
@@ -4,4 +4,4 @@
* @version 1.0
* @since 1.0
*/
package com.micro.fast.common.aspect;
package com.micro.fast.common.datasource.dynamic.aspect;
@@ -1,4 +1,4 @@
package com.micro.fast.common.condition;
package com.micro.fast.common.datasource.dynamic.condition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -0,0 +1 @@
package com.micro.fast.common.datasource.dynamic;
@@ -0,0 +1 @@
package com.micro.fast.common.datasource;
@@ -0,0 +1,10 @@
package com.micro.fast.common.plugin;
/**
* @author lsy
*/
public interface MetaObjectKey {
String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement";
String DELEGATE_PARAMETER_HANDLER = "delegate.parameterHandler";
String DELEGATE_BOUND_SQL_SQL = "delegate.boundSql.sql";
}
@@ -0,0 +1,35 @@
package com.micro.fast.common.plugin;
/**
* mybatis插件工具类
* @author lsy
*/
public class PluginUtils {
/**
* 不能被实例化
*/
private PluginUtils(){
}
/**
* 根据语句id获取方法名
* @param statementId
* @return
*/
public static String getMethodName(String statementId){
int i = statementId.lastIndexOf(".");
return statementId.substring(i + 1);
}
/**
* 根据语句id获取全类名
* @param statementId
* @return
*/
public static String getClassName(String statementId){
int i = statementId.lastIndexOf(".");
return statementId.substring(0,i);
}
}
@@ -0,0 +1,18 @@
package com.micro.fast.common.plugin.lock.optimistic;
import lombok.Getter;
import lombok.Setter;
import java.lang.reflect.Field;
/**
* @author lsy
*/
@Getter
@Setter
public class CacheOptimisticLock {
private String fieldName;
private Field field;
}
@@ -0,0 +1,11 @@
package com.micro.fast.common.plugin.lock.optimistic;
/**
* 乐观锁插件运行时异常
* @author lsy
*/
public class OptimisticException extends RuntimeException {
public OptimisticException(String message) {
}
}
@@ -0,0 +1,14 @@
package com.micro.fast.common.plugin.lock.optimistic;
import java.lang.annotation.*;
/**
* @author lsy
* 加在方法上,表示方法使用乐观锁
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface OptimisticLock {
}
@@ -0,0 +1,17 @@
package com.micro.fast.common.plugin.lock.optimistic;
import java.lang.annotation.*;
/**
* @author lsy
* 运行时注解
* 可以加载字段和参数上
* 可以被继承
* 标识实体类哪个字段是乐观锁
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Documented
public @interface Version {
String value() default "";
}
@@ -0,0 +1,172 @@
package com.micro.fast.common.plugin.lock.optimistic;
import com.micro.fast.common.plugin.MetaObjectKey;
import com.micro.fast.common.plugin.PluginUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.Connection;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* mybatis简单的乐观锁插件,不支持级联,不支持批量更新,仅支持单条数据的更新。
* 使用方式:
* 1. 给实体类上版本字段加上@Version
* 2. 给想要使用乐观锁的dao方法上加上@OptimisticLock注解
* 3. 使用dao方法的时候,更新对象要以实体类的对象为参数。
* 拦截StatementHandler类的prepare(Connection.class,Integer.class)方法
* @author lsy
*/
@Intercepts({@Signature(type= StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})})
public class VersionLock implements Interceptor {
/**
* 存储已经获取过的statementId 和 fieldName
*/
private static final ConcurrentHashMap<String,CacheOptimisticLock> cache = new ConcurrentHashMap<>();
/**
* 需要拦截的方法名
*/
public static final String PREPARE = "prepare";
/**
* sql 中的条件查询符号
*/
public static final String WHERE = "where";
/**
* 拦截器主体方法
* @param invocation 被拦截的方法的代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
String interceptMethodName = invocation.getMethod().getName();
if (!PREPARE.equals(interceptMethodName)){
return invocation.proceed();
}
// 获取语句对象
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
// MetaObject是mybatis提供的简化插件开发的类
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
// 获取mapper接口实例,是否指定了乐观锁
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue(MetaObjectKey.DELEGATE_MAPPED_STATEMENT);
String statementId = mappedStatement.getId();
// 是否已经获取过这个语句的对应的实体类的版本对象
StringBuilder fieldName = new StringBuilder();
BoundSql boundSql = statementHandler.getBoundSql();
// 获取原始的sql update tableName set a=123,b=234,c=345 where id = 123
String sql = boundSql.getSql();
// 获取参数对象实例,检查实体类是不是只有一个参数加了@Version注解
ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue(MetaObjectKey.DELEGATE_PARAMETER_HANDLER);
Object params = parameterHandler.getParameterObject();
String versionFieldValue = null;
// 已经获取过,肯定是乐观锁的语句
if(cache.contains(statementId)){
CacheOptimisticLock cacheOptimisticLock = cache.get(statementId);
Field field = cacheOptimisticLock.getField();
fieldName.append(cacheOptimisticLock.getFieldName());
field.setAccessible(true);
versionFieldValue = (String)field.get(params);
// 修改原本的sql
sql += " and" + fieldName.toString() + "=" + versionFieldValue;
int indexOfWhere = sql.indexOf(WHERE);
String begin = sql.substring(0, indexOfWhere);
String end = sql.substring(indexOfWhere);
String optimistVersionSql = begin + " ," + fieldName + "=" + (versionFieldValue+1) + " " + end;
metaObject.setValue(MetaObjectKey.DELEGATE_BOUND_SQL_SQL,optimistVersionSql);
return invocation.proceed();
}else{
String className = PluginUtils.getClassName(statementId);
String methodName = PluginUtils.getMethodName(statementId);
Class<?> aClass = Class.forName(className);
Method[] methods = aClass.getMethods();
// 获取实体类的全类名
String nameDO = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
// 找到mapper接口方法
Annotation[] annotations = method.getAnnotations();
boolean optimisticLock = false;
for (Annotation annotation : annotations) {
if (annotation.getClass() == OptimisticLock.class){
Parameter[] parameters = method.getParameters();
Parameter parameter = parameters[0];
nameDO = parameter.getName();
optimisticLock = true;
}
}
if (!optimisticLock){
return invocation.proceed();
}
break;
}
}
// 获取参数对象实例,检查实体类是不是只有一个参数加了@Version注解
Class<?> aClassDO = Class.forName(nameDO);
Field[] fields = aClassDO.getFields();
int haveOneVersionFlagfield = 0;
Field versionField = null;
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Version.class){
++ haveOneVersionFlagfield;
String name = field.getName();
fieldName.append(name);
versionField = field;
}
}
}
if (haveOneVersionFlagfield != 1){
throw new OptimisticException("请保证实体类有且只有一个属性加乐观锁版本注解");
}
// 突破属性的访问权限
versionField.setAccessible(true);
versionFieldValue = (String)versionField.get(params);
if (StringUtils.isBlank(versionFieldValue)){
throw new OptimisticException("请给版本控制属性赋值");
}
// 存储乐观锁
CacheOptimisticLock cacheOptimisticLock = new CacheOptimisticLock();
cacheOptimisticLock.setField(versionField);
cacheOptimisticLock.setFieldName(fieldName.toString());
cache.put(statementId,cacheOptimisticLock);
// 修改原本的sql
sql += " and" + fieldName.toString() + "=" + versionFieldValue;
int indexOfWhere = sql.indexOf(WHERE);
String begin = sql.substring(0, indexOfWhere);
String end = sql.substring(indexOfWhere);
String optimistVersionSql = begin + " ," + fieldName + "=" + (versionFieldValue+1) + " " + end;
metaObject.setValue(MetaObjectKey.DELEGATE_BOUND_SQL_SQL,optimistVersionSql);
return invocation.proceed();
}
}
@Override
public Object plugin(Object target) {
// 封装拦截器为插件,此拦截器只对StatementHandler和ParameterHandler生效,对执行器和ResultSetHandler不生效
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
// 给插件传递参数
}
}
@@ -0,0 +1,4 @@
/**
* mybatis扩展插件
*/
package com.micro.fast.common.plugin;