ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

使用mybatis拦截器,解决信创改造中数据库不支持自定义函数问题。

使用mybatis拦截器,解决信创改造中数据库不支持自定义函数问题。 大家好好长一段时间没有来写博客了前一段时间刚刚把账号找回来丢了好久的账号申述了好几次才找回来。好了废话不多说进入今天的正题随着我国技术国产化的要求相信很多码友们都经历过信创改造我也是正在经历中。。。。。 我们是要将DB2的数据库迁移到goldenDB然而goldenDB分布式模式是不支持自定义函数的。后来将项目中用到函数的地方都进行了整理大部分的函数要么作用在入参上要么作用在出参上并且好多自定义函数使用的频次非常高函数的复杂度也比较高。针对这个问题其实我心里是有一个比较好的方案的也就是使用mybatis的拦截器那么下面我就来介绍一下如何实现。我的方案是mybatis拦截器自定义注解首先我们看一下我的项目结构首先我们来看一下自定注解package com.example.demo.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface InnerProcessor { String interceptor() default ; //具体需要调用的处理器名称可以对应自定义函数名 String attribute() default ; //要拦截入参的名称 }自定义注解这里大家可以通过自己的需求进行定义属性我这里就比较简单的写了一下。然后我们来看一下dao层InnerProcessor(attribute loginIds) UserInfo getUserInfoById1(HashMapString,Object param);我这里没有添加具体的处理器有点懒了下面看一下对应的xmlselect idgetUserInfoById1 parameterTypejava.util.HashMap resultMapUserInfoMap SELECT include refidallColumn/ FROM USER_INFO WHERE USER_ID #{userId} if testloginIds ! null and loginIds.size()0 and LOGIN_ID in foreach collectionloginIds open( close) separator, itemloginId #{loginId} /foreach /if /select下面看一下写的入参拦截器package com.example.demo.inter; import com.example.demo.annotation.InnerProcessor; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.Connection; import java.util.ArrayList; import java.util.HashMap; Intercepts(Signature( type StatementHandler.class, method prepare, args {Connection.class, Integer.class} )) Component Slf4j public class BasicParameterInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler (StatementHandler) invocation.getTarget(); BoundSql boundSql statementHandler.getBoundSql(); // 获取MappedStatement和BoundSql MetaObject metaStatementHandler SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement (MappedStatement) metaStatementHandler.getValue(delegate.mappedStatement); // 获取原始参数 Object parameterObject boundSql.getParameterObject(); //获取被调用的dao层的方法 String sqlId mappedStatement.getId(); Method mapperMethod getMapperMethod(sqlId); //判断是否被自定义注解标识 InnerProcessor annotation mapperMethod.getAnnotation(InnerProcessor.class); if(null annotation){ return invocation.proceed(); } String attribute annotation.attribute(); //获取入参被拦截的字段 //此段代码为修改对应的参数应该单独拿出来 // 对应注解上的处理器可以通过工厂模板或策略者模式进行设计我这里为了做试验就没有对其进行抽离设计 HashMapString,Object map (HashMap)parameterObject; ArrayListString list new ArrayList(); list.add(chengyu123); list.add(chengyu12345); list.add(chengyu12354); map.put(attribute, list); //重新生成BoundSql BoundSql newBoundSql regenerateBoundSql(mappedStatement, statementHandler, map); //更新statementHander和metaStatementHandler将新生成的BoundSql替换掉原有的sql updateStatementHandlerBoundSql(statementHandler, newBoundSql, metaStatementHandler); // 执行方法 return invocation.proceed(); } private BoundSql regenerateBoundSql(MappedStatement mappedStatement, StatementHandler statementHandler, Object newParameter) { try { // 方法1通过 MappedStatement 重新获取 BoundSql // BoundSql newBoundSql mappedStatement.getBoundSql(newParameter); // 方法2通过 SqlSource 重新生成更底层 SqlSource sqlSource mappedStatement.getSqlSource(); BoundSql regeneratedBoundSql sqlSource.getBoundSql(newParameter); // 复制原始的 parameterMappings如果需要 // copyParameterMappings(statementHandler.getBoundSql(), regeneratedBoundSql); return regeneratedBoundSql; } catch (Exception e) { log.error(重新生成 BoundSql 失败, e); throw new RuntimeException(重新生成 SQL 失败, e); } } private void updateStatementHandlerBoundSql(StatementHandler handler, BoundSql newBoundSql, MetaObject metaHandler) { try { // 方式1通过 MetaObject 更新 // metaHandler.setValue(delegate.boundSql, newBoundSql); // 方式2直接设置如果知道具体类型 if (handler instanceof RoutingStatementHandler) { // 获取委托的 StatementHandler StatementHandler delegate (StatementHandler) metaHandler.getValue(delegate); ParameterHandler parameterHandler delegate.getParameterHandler(); MetaObject metaObject SystemMetaObject.forObject(parameterHandler); metaObject.setValue(boundSql,newBoundSql); // if(parameterHandler instanceof MybatisParameterHandler){ // MybatisParameterHandler mybatisParameterHandler (MybatisParameterHandler) parameterHandler; // Field boundSql mybatisParameterHandler.getClass().getDeclaredField(boundSql); // boundSql.setAccessible(true); // boundSql.set(mybatisParameterHandler,newBoundSql); // } setBoundSqlToHandler(delegate, newBoundSql); } else { setBoundSqlToHandler(handler, newBoundSql); } } catch (Exception e) { log.error(更新 StatementHandler BoundSql 失败, e); throw new RuntimeException(更新 BoundSql 失败, e); } } private void setBoundSqlToHandler(StatementHandler handler, BoundSql boundSql) { Field boundSqlField null; try { try { boundSqlField handler.getClass().getDeclaredField(boundSql); } catch (Exception e) { boundSqlField handler.getClass().getSuperclass().getDeclaredField(boundSql); } boundSqlField.setAccessible(true); boundSqlField.set(handler, boundSql); }catch (Exception e){ } } private Method getMapperMethod(String mappedStatementId) { try { int lastDotIndex mappedStatementId.lastIndexOf(.); String className mappedStatementId.substring(0, lastDotIndex); String methodName mappedStatementId.substring(lastDotIndex 1); Class? mapperClass Class.forName(className); for (Method method : mapperClass.getDeclaredMethods()) { if (method.getName().equals(methodName)) { return method; } } } catch (Exception e) { } return null; } }此拦截器是拦截StatementHandler的prepare方法是预处理阶段虽然此阶段已生成sql语句但是还没有到预编译阶段所以此阶段的sql是可以更改的。还有这里我没有把对应的处理器抽离出来代码中我也进行了标识。我们打一个断点来看一下运行过程。首先看一下未修改参数的sql修改完参数重新生成的sql如下最后执行的结果如下由此来看我们已经将参数修改了并且重新动态的生成了sql这就是入参拦截器。下面我们来看一下出参拦截器首先来看一下自定义注解package com.example.demo.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface ResultProcessor { SelectInterceptor[] selector(); }package com.example.demo.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface SelectInterceptor { String interceptor() default ; //具体需要调用的处理器名称可以对应自定义函数名 String attribute() default ; //要拦截的字段 Class? type(); //拦截字段类型我这里没有用上 String code() default ; //其它默认参数这里可以根据情况自定义我这里没有用上 }然后我们来看一下dao层ResultProcessor(selector {SelectInterceptor(interceptor cc,attribute provinceCode,type String.class), SelectInterceptor(interceptor bb,attribute countryCode,type String.class)}) UserInfo getUserInfoById1(HashMapString,Object param);对应的xml同上面入参拦截器的xml相同我这里定义了两个处理器分别来看一下package com.example.demo.interceptor; public interface AbstractInterceptor { void interceptor(Object objec,String attribute,String code); String register(); }package com.example.demo.interceptor.impl; import com.example.demo.dao.CityDao; import com.example.demo.domain.entity.City; import com.example.demo.interceptor.AbstractInterceptor; import org.springframework.beans.BeansException; import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; Component public class CountryInterceptor implements AbstractInterceptor, ApplicationContextAware,CommandLineRunner { private static final HashMapString,String COUNTRY_MAP new HashMap(); private ApplicationContext applicationContext; Override public void interceptor(Object objec, String attribute, String code) { try { Class? aClass objec.getClass(); Field field aClass.getDeclaredField(attribute); field.setAccessible(true); Object o field.get(objec); String s COUNTRY_MAP.get(String.valueOf(o)); field.set(objec, s); Object o1 field.get(objec); }catch (Exception e){ } } Override public String register() { return bb; } Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext applicationContext; } Override public void run(String... args) throws Exception { CityDao cityDao applicationContext.getBean(CityDao.class); ListCity cityListAll cityDao.getCityListAll(); for(City city:cityListAll){ COUNTRY_MAP.put(city.getCode(),city.getName()); } } }package com.example.demo.interceptor.impl; import com.example.demo.dao.CityDao; import com.example.demo.domain.entity.City; import com.example.demo.interceptor.AbstractInterceptor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; Component public class ProvinceInterceptor implements AbstractInterceptor, ApplicationContextAware,CommandLineRunner { private static final HashMapString,String CITY_MAP new HashMap(); private ApplicationContext applicationContext; Override public void interceptor(Object objec, String attribute, String code) { try { Class? aClass objec.getClass(); Field field aClass.getDeclaredField(attribute); field.setAccessible(true); Object o field.get(objec); String s CITY_MAP.get(String.valueOf(o)); field.set(objec, s); }catch (Exception e){ System.out.println(e); } } Override public String register() { return cc; } Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext applicationContext; } Override public void run(String... args) throws Exception { CityDao cityDao applicationContext.getBean(CityDao.class); ListCity cityListAll cityDao.getCityListAll(); for(City city:cityListAll){ CITY_MAP.put(city.getCode(),city.getName()); } } }处理器的工厂package com.example.demo.interceptor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.List; Component Order(Integer.MAX_VALUE) public class FactoryInterceptor { Autowired private ListAbstractInterceptor interceptorList; private static final HashMapString,AbstractInterceptor MAP_INTERCEPTOR new HashMap(); PostConstruct public void init(){ for(AbstractInterceptor abstractInterceptor : interceptorList) { MAP_INTERCEPTOR.put(abstractInterceptor.register(),abstractInterceptor); } } public void interceptor(String interceptor,Object object,String ab,String code){ AbstractInterceptor abstractInterceptor MAP_INTERCEPTOR.get(interceptor); abstractInterceptor.interceptor(object,ab,code); } }对应的拦截器如下package com.example.demo.inter; import com.example.demo.annotation.ResultProcessor; import com.example.demo.annotation.SelectInterceptor; import com.example.demo.interceptor.FactoryInterceptor; import org.apache.ibatis.executor.resultset.ResultSetHandler; 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.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.sql.Statement; import java.util.ArrayList; import java.util.List; Intercepts({ Signature(type ResultSetHandler.class, method handleResultSets, args {Statement.class}) }) Component Order(Integer.MAX_VALUE) public class SpecificMethodInterceptor implements Interceptor, ApplicationContextAware { private FactoryInterceptor factoryInterceptor; Override public Object intercept(Invocation invocation) throws Throwable { Object proceed invocation.proceed(); // 获取 MappedStatement MetaObject metaStatementHandler SystemMetaObject.forObject(invocation.getTarget()); MappedStatement mappedStatement (MappedStatement) metaStatementHandler.getValue(mappedStatement); // 获取对应的Mapper方法 String mappedStatementId mappedStatement.getId(); Method mapperMethod getMapperMethod(mappedStatementId); ResultProcessor resultProcessor mapperMethod.getAnnotation(ResultProcessor.class); if(null resultProcessor){ return proceed; } SelectInterceptor[] selector resultProcessor.selector(); if(proceed instanceof List){ ArrayList list (ArrayList) proceed; ArrayList arrayList new ArrayList(list.size()); for(Object obj : list){ for(SelectInterceptor selectInterceptor : selector){ String interceptor selectInterceptor.interceptor(); String attribute selectInterceptor.attribute(); String code selectInterceptor.code(); factoryInterceptor.interceptor(interceptor, obj, attribute, code); //obj o; } //arrayList.add(obj); } //return arrayList; }else { for(SelectInterceptor selectInterceptor : selector){ String interceptor selectInterceptor.interceptor(); String attribute selectInterceptor.attribute(); String code selectInterceptor.code(); factoryInterceptor.interceptor(interceptor,proceed,attribute,code); } } return proceed; } private Method getMapperMethod(String mappedStatementId) { try { int lastDotIndex mappedStatementId.lastIndexOf(.); String className mappedStatementId.substring(0, lastDotIndex); String methodName mappedStatementId.substring(lastDotIndex 1); Class? mapperClass Class.forName(className); for (Method method : mapperClass.getDeclaredMethods()) { if (method.getName().equals(methodName)) { return method; } } } catch (Exception e) { } return null; } Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { factoryInterceptor applicationContext.getBean(FactoryInterceptor.class); } }此拦截器拦截的是ResultSetHandler的handleResultSets方法主要就是处理结果集的我这个示例是将provinceCode和countryCode字段不在显示码值而是显示对应的翻译我们来看一下执行后的结果我这里只是做了一个简单的示例如果有对应的需求可以做的很复杂这个就是一个方案mybatis对应的拦截器能够做的事情非常多对应不同阶段也有不同的拦截器所以值得大家去探索好了今天的内容就到这里了希望能够帮助有需要的码友们。以后我会陆续的更新博客。
返回列表