mybatis-plus、...
⽬录
组件介绍
表名处理器
字段填充器
类型处理器
补充
最近有个练⼿的⼩例⼦,⼤概就是配置两个数据源,从⼀个数据源读取数据写到另⼀个数据源,虽然最后做了出来,但是不⽀持事务。。。就当是对mybatis-plus/mybatis组件使⽤⽅式的记录吧,本次例⼦使⽤的仍是mybatis-plus
回忆⼀下mybatis核⼼对象:
Configuration 初始化基础配置,⽐如MyBatis的别名等,⼀些重要的类型对象,如,插件,映射器,Obj
ectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中
SqlSessionFactory SqlSession⼯⼚jfinal增删改查
SqlSession 作为MyBatis⼯作的主要顶层API,表⽰和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执⾏器,是MyBatis 调度的核⼼,负责SQL语句的⽣成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler 负责对⽤户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了⼀条<select|update|delete|insert>节点的封装,
SqlSource 负责根据⽤户传递的parameterObject,动态地⽣成SQL语句,将信息封装到BoundSql对
象中,并返回
BoundSql 表⽰动态⽣成的SQL语句以及相应的参数信息
组件介绍
mybatis可以在执⾏语句的过程中对特定对象进⾏拦截调⽤,主要有四个
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 处理增删改查
ParameterHandler (getParameterObject, setParameters) 设置预编译参数
ResultSetHandler (handleResultSets, handleOutputParameters) 处理结果
StatementHandler (prepare, parameterize, batch, update, query) 处理sql预编译,设置参数
这四个是可以拦截的对象,⼤概的做法是实现mybatis的接⼝并在上⾯添加注解来确定拦截那些⽅法
下⾯是接⼝Interceptor所要实现的⽅法,setPropertites可以⽤来初始化,⽽plugin则包装⽬标对象供拦
截器处理,基于动态代理实现,Plugin类是动态代理类,对实现Interceptor的接⼝的类进⾏处理,⽽实现的会被加⼊到链进⾏处理
Object intercept(Invocation var1) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
}
plugin.warp⽅法
链:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList();
public InterceptorChain() {
}
public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (();
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
this.interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(this.interceptors);
}
}
并在handler⾥⾯添加这些类,执⾏pluginAll⽅法,返回⼀个经过代理链处理的对象
实现该接⼝以后,要添加注解来表明拦截哪些⽅法,⽅法则是上⾯四个对象的拥有的⽅法。下⾯这个注解则是指定了拦截哪些对象的哪个⽅法,args则是被拦截⽅法的参数public @interface Signature {
Class<?> type();
String method();
Class<?>[] args();
}
⽐如这个例⼦
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
Signature注解就对应上⾯的接⼝、⽅法及其参数,然后在添加⼀个@Intercepts,这个注解的内容是Signature注解数组
有了,初步想法是根据⽅法拦截,如果select则使⽤读数据源,增删改则使⽤写数据源,这个其
实原理和之前写的⼀篇代码级别读写分离很相似,也是通过ThreadLocal存放当前线程的数据源,然后通过来判断⽤哪个数据源,交由AbstarctRoutingDataSource来根据ThreadLoacl⾥⾯的值来处理。
但是有个问题,两个数据源转换,表名、字段名不⼀定相等,⽐如从pgsql的⼀个叫user_info表⾥的数据转到mysql叫user表的数据,字段名都不相同
我的处理⽅法是查询对象的⽬标的字段名为准,然后给每个字段⼀个注解指向修改对象的数据源表字段名,如果查询⽬标表没有插⼊⽬标表的字段,便在select的时候默认select null或者⽤代码限定查询的字段。这⾥⾸先先定义了三个注解,分别对应查、改相应的数据源、表名、字段
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TranDB {
DBType from();
DBType to();
Class object();
}
public @interface TranField {
String from() default "";
String to();
String empty = "null";
}
public @interface TranTable {
String from();
String to();
}
User类
@TranTable(from = "user_info", to = "user")
public class User {
@TranField(to = "id")
@TableId
private Integer userId;
@TranField(to = "wx_nickname")
private String userAccount;
@TranField(to = "roles")
private String mobile;
@TranField(pty,to="create_time")
private Date createTime;
@TranField(pty,to="update_time")
private Date updateTime;
@TranField(pty,to="bonus")
private Integer bonus;
@TranField(to="wx_id")
private String[] test;
}
UserMapper
@TranDB(from = DBType.PGSQL,to=DBType.MYSQL,object=User.class)
public interface UserMapper extends BaseMapper<User> {
}
这⾥添加⼀个缓存mapper信息类,⽅便在中调⽤,其中有个成员变量是⽤来存储mapperName对应的TranDB注解信息,通过拦截的⽅法获取mapper名称,再通过这个mapper信息类获取他的TranDB注解,这个注解⾥⾯有对应的实体class,可以⽤来获取字段信息注解及表名信息注解,⽽另⼀个成员变量则是⽤来存放待会说到的表名替换,这⾥⾯实现了两个接⼝,⼀个通过spring容器加载资源的接⼝,另⼀个则是⽤来初始化bean的。
fig;
import sion.parsers.ITableNameHandler;
ansport.annotations.TranDB;
ansport.handler.SelfTableNameHandler;
ansport.util.CamelHumpUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import t.ResourceLoaderAware;
import io.Resource;
import io.ResourceLoader;
import io.support.ResourcePatternResolver;
import io.support.ResourcePatternUtils;
import ype.classreading.CachingMetadataReaderFactory;
import ype.classreading.MetadataReader;
import ype.classreading.MetadataReaderFactory;
import java.util.*;
/**
* Mapper信息缓存类
*/
public class MapperAuxFeatureMap implements ResourceLoaderAware, InitializingBean {
private static ResourceLoader resourceLoader;
@Value("${tran.mapperlocation}")
public String MAPPER_LOCATION ;
public static final String TABLEPREFIX="t_";
//表名处理
public Map<String, ITableNameHandler> tableNameHandlerMap;
//mapper⽂件的注解
public Map<String, TranDB> mapperTranDbMap;
//通过⽅法获取mapper名称
public static String getMapperNameFromMethodName(String source){
int end = source.lastIndexOf(".") + 1;
String mapper = source.substring(0, end - 1);
mapper = mapper.substring(mapper.lastIndexOf(".") + 1);
return mapper;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
}
@Override
public void afterPropertiesSet() throws Exception {
ResourcePatternResolver resolver = ResourcePatternResolver(resourceLoader);
MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
Resource[] resources = Resources("classpath*:"+place(".","/")+"/**/*.class");
mapperTranDbMap = new HashMap<>();
tableNameHandlerMap = new HashMap<>();
for (Resource r : resources) {
MetadataReader reader = MetadataReader(r);
String className = ClassMetadata().getClassName();
Class<?> c = Class.forName(className);
if (c.isAnnotationPresent(TranDB.class)) {
String name = c.getSimpleName();
TranDB tranDB = c.getAnnotation(TranDB.class);
mapperTranDbMap.put(name, tranDB);
String value = tranDB.object().getSimpleName();
tableNameHandlerMap.put(TABLEPREFIX+ CamelHumpUtils.humpToLine(value),new SelfTableNameHandler(tranDB.object()));
}
}
}
}
替换数据源的部分代码,对query和update(即增删改)⽅法进⾏拦截,改⽅法使⽤mysql数据源,查⽅法使⽤pgsql数据源
ansport.dyma;
ansport.annotations.TranDB;
fig.MapperAuxFeatureMap;
import org.utor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Properties;
/**
* @author: lele
* @date: 2019/10/23 下午4:24
*/
@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 DynamicDataSourceInterceptor implements Interceptor {
private MapperAuxFeatureMap mapperAuxFeatureMap;
public DynamicDataSourceInterceptor(MapperAuxFeatureMap mapperAuxFeatureMap) {
this.mapperAuxFeatureMap = mapperAuxFeatureMap;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
//如果读取数据,使⽤From的库,否则使⽤To库
DBType db =null;
Object[] objects = Args();
MappedStatement statement = (MappedStatement) objects[0];
String mapper = Id());
TranDB tranDB = (mapper);
if (SqlCommandType().equals(SqlCommandType.SELECT)) {
db = tranDB.from();
} else {
db = ();
}
DynamicDataSourceHolder.setDbType(db);
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
if (o instanceof Executor) {
return Plugin.wrap(o, this);
} else {
return o;
}
}
@Override
public void setProperties(Properties properties) {
}
}
然后对字段进⾏修改的,这⾥为什么要继承AbstactSqlPaserHandler呢,因为可以复⽤他的⽅法,以及为后来加⼊表名替换的类做准备,这⾥的流程是获取原来字段的名字,并改为TranField的to所存储的内容
ansport.handler;
import lkit.PluginUtils;
import sion.handlers.AbstractSqlParserHandler;
ansport.annotations.TranDB;
ansport.annotations.TranField;
fig.MapperAuxFeatureMap;
ansport.util.CamelHumpUtils;
import org.utor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.flection.MetaObject;
import org.flection.SystemMetaObject;
import flect.Field;
import java.sql.Connection;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
/**
* @author: lele
* @date: 2019/10/23 下午5:12
*/
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
),
@Signature(
type = StatementHandler.class,
method = "update",
args = {Statement.class}
),
@Signature(
type = StatementHandler.class,
method = "batch",
args = {Statement.class}
)
})
public class FieldHandler extends AbstractSqlParserHandler implements Interceptor {
private MapperAuxFeatureMap mapperAuxFeatureMap;
public FieldHandler(MapperAuxFeatureMap mapperAuxFeatureMap) {
this.mapperAuxFeatureMap = mapperAuxFeatureMap;
}
@Override
public Object plugin(Object target) {
return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = Target());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
super.sqlParser(metaObject);
MappedStatement mappedStatement = (MappedStatement) Value("delegate.mappedStatement");
BoundSql boundSql = (BoundSql) Value("delegate.boundSql");
Boolean select = SqlCommandType().equals(SqlCommandType.SELECT);
if (!select) {
//通过获取mapper名称从缓存类中获取对应的注解
String mapperName = Id());
TranDB tranDB = (mapperName);
//获取类的所有属性
Class clazz = tranDB.object();
Map<String, Field> mapField = new HashMap<>(Fields().length);
while (!clazz.equals(Object.class)) {
Field[] fields = DeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
mapField.Name(), field);
}
clazz = Superclass();
}
//替换sql
String sql = Sql();
for (Map.Entry<String, Field> entry : Set()) {
String sqlFieldName = CamelHumpUtils.Key());
if (ains(sqlFieldName)) {
String from = Value().getAnnotation(TranField.class).to();
sql = placeAll(sqlFieldName, from);
}
}
metaObject.setValue("delegate.boundSql.sql", sql);
}
return invocation.proceed();
}
}
现在还有⼀个问题要处理,就是表名替换,但是这个有个⼩坑,这个功能也相当于上⾯替换sql的功能⽐如insert into user(user_info,user_id) values ...,⽐如把user这个表名替换为user_info这个表来执⾏,此时的插⼊语句会把所有user的替换成user_info,这时候官⽅的建议是⽤@TableName这个注解更改表名避免出现这个情况
表名处理器
使⽤:实现ITableNameHandler,并实现接⼝⽅法返回⼀个表名字
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论