mybatis-plus攔截器、字段填充器、類型處理器、表名替換、SqlInjector(聯(lián)合主鍵處理)
最近有個練手的小例子,大概就是配置兩個數(shù)據(jù)源,從一個數(shù)據(jù)源讀取數(shù)據(jù)寫到另一個數(shù)據(jù)源,雖然最后做了出來,但是不支持事務(wù)。。。就當是對mybatis-plus/mybatis組件使用方式的記錄吧,本次例子使用的仍是mybatis-plus
回憶一下mybatis核心對象:
- Configuration 初始化基礎(chǔ)配置,比如MyBatis的別名等,一些重要的類型對象,如,插件,映射器,ObjectFactory和typeHandler對象,MyBatis所有的配置信息都維持在Configuration對象之中
- SqlSessionFactory SqlSession工廠
- SqlSession 作為MyBatis工作的主要頂層API,表示和數(shù)據(jù)庫交互的會話,完成必要數(shù)據(jù)庫增刪改查功能
- Executor MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心,負責(zé)SQL語句的生成和查詢緩存的維護
- StatementHandler 封裝了JDBC Statement操作,負責(zé)對JDBC statement 的操作,如設(shè)置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換成List集合。
- ParameterHandler 負責(zé)對用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù),
- ResultSetHandler 負責(zé)將JDBC返回的ResultSet結(jié)果集對象轉(zhuǎn)換成List類型的集合;
- TypeHandler 負責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換
- MappedStatement MappedStatement維護了一條<select|update|delete|insert>節(jié)點的封裝,
- SqlSource 負責(zé)根據(jù)用戶傳遞的parameterObject,動態(tài)地生成SQL語句,將信息封裝到BoundSql對象中,并返回
- BoundSql 表示動態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息
組件介紹
mybatis可以在執(zhí)行語句的過程中對特定對象進行攔截調(diào)用,主要有四個
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 處理增刪改查
- ParameterHandler (getParameterObject, setParameters) 設(shè)置預(yù)編譯參數(shù)
- ResultSetHandler (handleResultSets, handleOutputParameters) 處理結(jié)果
- StatementHandler (prepare, parameterize, batch, update, query) 處理sql預(yù)編譯,設(shè)置參數(shù)
這四個是可以攔截的對象,大概的做法是實現(xiàn)mybatis攔截器的接口并在上面添加注解來確定攔截那些方法
下面是接口Interceptor所要實現(xiàn)的方法,setPropertites可以用來初始化,而plugin則包裝目標對象供攔截器處理,基于動態(tài)代理實現(xiàn),Plugin類是動態(tài)代理類,對實現(xiàn)Interceptor的接口的類進行處理,而實現(xiàn)的攔截器會被加入到攔截器鏈進行處理
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 = (Interceptor)var2.next(); } return target; } public void addInterceptor(Interceptor interceptor) { this.interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(this.interceptors); } }
并在handler里面添加這些攔截器類,執(zhí)行pluginAll方法,返回一個經(jīng)過代理鏈處理的對象
實現(xiàn)該接口以后,要添加注解來表明攔截哪些方法,方法則是上面四個對象的擁有的方法。下面這個注解則是指定了攔截哪些對象的哪個方法,args則是被攔截方法的參數(shù)
public @interface Signature { Class<?> type(); String method(); Class<?>[] args(); }
比如這個例子
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
Signature注解就對應(yīng)上面的接口、方法及其參數(shù),然后在攔截器添加一個@Intercepts,這個注解的內(nèi)容是Signature注解數(shù)組
有了攔截器,初步想法是根據(jù)方法攔截,如果select則使用讀數(shù)據(jù)源,增刪改則使用寫數(shù)據(jù)源,這個其實原理和之前寫的一篇代碼級別讀寫分離很相似,也是通過ThreadLocal存放當前線程的數(shù)據(jù)源,然后通過攔截器來判斷用哪個數(shù)據(jù)源,交由AbstarctRoutingDataSource來根據(jù)ThreadLoacl里面的值來處理。
但是有個問題,兩個數(shù)據(jù)源轉(zhuǎn)換,表名、字段名不一定相等,比如從pgsql的一個叫user_info表里的數(shù)據(jù)轉(zhuǎn)到mysql叫user表的數(shù)據(jù),字段名都不相同
我的處理方法是查詢對象的目標的字段名為準,然后給每個字段一個注解指向修改對象的數(shù)據(jù)源表字段名,如果查詢目標表沒有插入目標表的字段,便在select的時候默認select null或者用代碼限定查詢的字段。這里首先先定義了三個注解,分別對應(yīng)查、改相應(yīng)的數(shù)據(jù)源、表名、字段
@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(from=TranField.empty,to="create_time") private Date createTime; @TranField(from=TranField.empty,to="update_time") private Date updateTime; @TranField(from=TranField.empty,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信息類,方便在攔截器中調(diào)用,其中有個成員變量是用來存儲mapperName對應(yīng)的TranDB注解信息,攔截器通過攔截的方法獲取mapper名稱,再通過這個mapper信息類獲取他的TranDB注解,這個注解里面有對應(yīng)的實體class,可以用來獲取字段信息注解及表名信息注解,而另一個成員變量則是用來存放待會說到的表名替換,這里面實現(xiàn)了兩個接口,一個通過spring容器加載資源的接口,另一個則是用來初始化bean的。
package com.trendy.task.transport.config; import com.baomidou.mybatisplus.extension.parsers.ITableNameHandler; import com.trendy.task.transport.annotations.TranDB; import com.trendy.task.transport.handler.SelfTableNameHandler; import com.trendy.task.transport.util.CamelHumpUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.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) { MapperAuxFeatureMap.resourceLoader=resourceLoader; } @Override public void afterPropertiesSet() throws Exception { ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader); Resource[] resources = resolver.getResources("classpath*:"+MAPPER_LOCATION.replace(".","/")+"/**/*.class"); mapperTranDbMap = new HashMap<>(); tableNameHandlerMap = new HashMap<>(); for (Resource r : resources) { MetadataReader reader = metaReader.getMetadataReader(r); String className = reader.getClassMetadata().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())); } } } }
替換數(shù)據(jù)源的部分代碼,對query和update(即增刪改)方法進行攔截,改方法使用mysql數(shù)據(jù)源,查方法使用pgsql數(shù)據(jù)源
package com.trendy.task.transport.dyma; import com.trendy.task.transport.annotations.TranDB; import com.trendy.task.transport.config.MapperAuxFeatureMap; import org.apache.ibatis.executor.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 { //如果讀取數(shù)據(jù),使用From的庫,否則使用To庫 DBType db =null; Object[] objects = invocation.getArgs(); MappedStatement statement = (MappedStatement) objects[0]; String mapper = MapperAuxFeatureMap.getMapperNameFromMethodName(statement.getId()); TranDB tranDB = mapperAuxFeatureMap.mapperTranDbMap.get(mapper); if (statement.getSqlCommandType().equals(SqlCommandType.SELECT)) { db = tranDB.from(); } else { db = tranDB.to(); } 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呢,因為可以復(fù)用他的方法,以及為后來加入表名替換的類做準備,這里的流程是獲取原來字段的名字,并改為TranField的to所存儲的內(nèi)容
package com.trendy.task.transport.handler; import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; import com.trendy.task.transport.annotations.TranDB; import com.trendy.task.transport.annotations.TranField; import com.trendy.task.transport.config.MapperAuxFeatureMap; import com.trendy.task.transport.util.CamelHumpUtils; import org.apache.ibatis.executor.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.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.lang.reflect.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 = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); super.sqlParser(metaObject); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); Boolean select = mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT); if (!select) { //通過獲取mapper名稱從緩存類中獲取對應(yīng)的注解 String mapperName = MapperAuxFeatureMap.getMapperNameFromMethodName(mappedStatement.getId()); TranDB tranDB = mapperAuxFeatureMap.mapperTranDbMap.get(mapperName); //獲取類的所有屬性 Class clazz = tranDB.object(); Map<String, Field> mapField = new HashMap<>(clazz.getFields().length); while (!clazz.equals(Object.class)) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); mapField.put(field.getName(), field); } clazz = clazz.getSuperclass(); } //替換sql String sql = boundSql.getSql(); for (Map.Entry<String, Field> entry : mapField.entrySet()) { String sqlFieldName = CamelHumpUtils.humpToLine(entry.getKey()); if (sql.contains(sqlFieldName)) { String from = entry.getValue().getAnnotation(TranField.class).to(); sql = sql.replaceAll(sqlFieldName, from); } } metaObject.setValue("delegate.boundSql.sql", sql); } return invocation.proceed(); } }
現(xiàn)在還有一個問題要處理,就是表名替換,但是這個有個小坑,這個功能也相當于上面替換sql的功能比如insert into user(user_info,user_id) values ...,比如把user這個表名替換為user_info這個表來執(zhí)行,此時的插入語句會把所有user的替換成user_info,這時候官方的建議是用@TableName這個注解更改表名避免出現(xiàn)這個情況
表名處理器
使用:實現(xiàn)ITableNameHandler,并實現(xiàn)接口方法返回一個表名字
package com.trendy.task.transport.handler; import com.baomidou.mybatisplus.extension.parsers.ITableNameHandler; import com.trendy.task.transport.annotations.TranTable; import org.apache.ibatis.reflection.MetaObject; /** * @author: lele * @date: 2019/10/24 下午2:39 * 表名替換的handler */ public class SelfTableNameHandler implements ITableNameHandler { private final Class clazz; public SelfTableNameHandler(Class clazz) { this.clazz = clazz; } @Override public String dynamicTableName(MetaObject metaObject, String sql, String tableName) { TranTable t = (TranTable) clazz.getAnnotation(TranTable.class); if (sql.toLowerCase().startsWith("select")) { return t.from(); } else { return t.to(); } } }
也可以注入到mp自帶的分頁那個攔截器中
@Bean public FieldHandler fieldHandler() { FieldHandler f = new FieldHandler(mapperAuxFeatureMap()); DynamicTableNameParser t = new DynamicTableNameParser(); t.setTableNameHandlerMap(mapperAuxFeatureMap().tableNameHandlerMap); f.setSqlParserList(Collections.singletonList(t)); return f; }
字段填充器
mysql的表里有創(chuàng)建時間、修改時間、積分,對于這些字段,而pgsql表里面沒有,這時候想在插入時使用一個默認的值,這時候可以使用字段填充器
做法:實現(xiàn)MetaObjectHandler接口,并重寫里面的方法,然后在字段的@TableField的fill類型里面說明需要填充時情況
例子,對createTime,updateTime,bouns進行默認填充,使用getFieldValueByName和setFieldValByName方法進行賦值
package com.trendy.task.transport.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import java.util.Date; /** * @author lulu * @Date 2019/10/24 23:15 * 自動填充的handler */ public class DefaultFieldValueHandler implements MetaObjectHandler { public static final String CREATETIME = "createTime"; public static final String UPDATETIME = "updateTime"; public static final String BOUNS = "bonus"; private void handle(String name, MetaObject metaObject, Object target) { Object o = getFieldValByName(name, metaObject); if (o == null) { setFieldValByName(name, target, metaObject); } } @Override public void insertFill(MetaObject metaObject) { handle(CREATETIME, metaObject, new Date()); handle(UPDATETIME, metaObject, new Date()); handle(BOUNS, metaObject, 500); } @Override public void updateFill(MetaObject metaObject) { handle(UPDATETIME, metaObject, new Date()); } }
然后為user添加注解@TableField的注解
@TranField(from=TranField.empty,to="create_time") @TableField(fill = FieldFill.INSERT) private Date createTime; @TranField(from=TranField.empty,to="update_time") @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; @TranField(from=TranField.empty,to="bonus") @TableField(fill= FieldFill.INSERT) private Integer bonus;
然后在全局配置中加入這個字段填充器類
@Bean public GlobalConfig globalConfig() { GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setBanner(false); globalConfig.setMetaObjectHandler(defaultFieldValueHandler()); return globalConfig; }
工廠類配置上全局配置 sqlSessionFactory.setGlobalConfig(globalConfig());
類型處理器
類型處理器,用于 JavaType 與 JdbcType 之間的轉(zhuǎn)換,用于 PreparedStatement 設(shè)置參數(shù)值和從 ResultSet 或 CallableStatement 中取出一個值,比如把String、Integer、Long、Double放到數(shù)據(jù)庫里面用逗號分隔形式存儲,此時可以
自定義類型處理器,這里針對上面四個對象數(shù)組實現(xiàn)類型轉(zhuǎn)換處理,setxxx方法主要是對參數(shù)進行處理,然后把處理后的結(jié)果放入數(shù)據(jù)庫中,而get方法則處理從數(shù)據(jù)庫取出來的數(shù)據(jù)該如何處理,這里接受一個lambda函數(shù)作為方法轉(zhuǎn)換,即字符串-》目標類型,這里定義一個抽象類統(tǒng)一處理方法,然后具體轉(zhuǎn)換方法由子類實現(xiàn)
package com.trendy.task.transport.handler; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.function.Function; /** * @author lulu * @Date 2019/10/25 21:46 */ //@MappedJdbcTypes({})表明處理哪種jdbc類型 @MappedTypes(Object[].class)//表明處理哪種javatype public abstract class AbstractArrayTypeHandler<T> extends BaseTypeHandler<Object[]> { //這里接受一個lambdah函數(shù)做轉(zhuǎn)換處理 private final Function<String,T> method; public AbstractArrayTypeHandler(Function<String,T> method){ this.method=method; } @Override public void setNonNullParameter(PreparedStatement preparedStatement, int i, Object[] objects, JdbcType jdbcType) throws SQLException { StringBuilder sb=new StringBuilder(); for(Object o:objects){ sb.append(o.toString()+","); } sb.deleteCharAt(sb.length()-1); preparedStatement.setString(i,sb.toString()); } @Override public T[] getNullableResult(ResultSet resultSet, String s) throws SQLException { return getArray(resultSet.getString(s)); } @Override public T[] getNullableResult(ResultSet resultSet, int i) throws SQLException { return getArray(resultSet.getString(i)); } @Override public Object[] getNullableResult(CallableStatement callableStatement, int i) throws SQLException { return getArray(callableStatement.getString(i)); } protected T[] getArray(String source){ if(source==null){ return null; } String[] resString=source.split(","); if(this.method==null){ return (T[])resString; } T[] resArray= (T[]) new Object[resString.length]; for (int i = 0; i < resString.length; i++) { resArray[i]=method.apply(resString[i]); } return resArray; } }
然后定義一個工廠存放子類
package com.trendy.task.transport.handler; import java.util.function.Function; /** * @author lulu * @Date 2019/10/25 22:33 */ public interface ArrayTypeHandlerFactory { class IntegerArrayTypeHandler extends AbstractArrayTypeHandler<Integer>{ public IntegerArrayTypeHandler() { super(Integer::parseInt); } } class DoubleArrayTypeHandler extends AbstractArrayTypeHandler<Double>{ public DoubleArrayTypeHandler(){ super(Double::parseDouble); } } class LongArrayTypeHandler extends AbstractArrayTypeHandler<Long>{ public LongArrayTypeHandler(){ super(Long::parseLong); } } class StringArrayTypeHandler extends AbstractArrayTypeHandler<String>{ public StringArrayTypeHandler(){ super(null); } } }
指定處理類型
@TableField(typeHandler = ArrayTypeHandlerFactory.StringArrayTypeHandler.class)
好了,現(xiàn)在來測試下,
@Test public void selectById() { List<User> userList = userService.list(new LambdaQueryWrapper<User>().select(User::getUserId,User::getMobile,User::getUserAccount,User::getTest)); userService.saveBatch(userList); }
大概就到這里,代碼完整版的github地址:GitHub - 97lele/transport
但是這個有個缺陷,就是不支持事務(wù),還有saveOrUpdate方法也不支持,因為兩個數(shù)據(jù)源都不一樣,他是先查,看是否有再做更新或者插入操作,這些問題仍需解決,且當作一個使用方法記錄的小例子吧
補充
mybatis-plus默認的baseMapper不支持批量的插入和更新,有時候會為了這個批量的方法去繼承serviceImpl類,而這個類有沒有其他的業(yè)務(wù)代碼在里面,繼承僅僅是為了獲取批量操作的方法,會顯得這個類有點”貧血“
對于這種情況,偶然看到一個開源的項目onemall對BaseMapper做了擴展,使它支持了批量插入的做法,在此基礎(chǔ)下,我也做了一定的擴展
根據(jù)自己的見解,簡單描述下流程:
核心就是自定義一個sql自動注入器,mybatis-plus會掃描mapper類 ,為每個mapper類構(gòu)造一個MappedStatement(相當于mapper里的一個sql)
添加到配置類里面(具體在下面(2)),只會在初始化時候執(zhí)行一次,而我們要做的,就是把某個特定mapper,注入我們想要的模板方法
具體注入在MybatisMapperAnnotationBuilder,注入動態(tài)sql,getSqlInjector獲取sql注入器,并為當前的MapperBuilderAssistant對象(類似于mapper)注入mappedStatement(sql)
(1)注入開始,里面的inspectInject就是
inspectInject就會把abstractMethod一一注入進去
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { Class<?> modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { List<AbstractMethod> methodList = this.getMethodList(mapperClass); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循環(huán)注入自定義方法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } }
((2)抽象方法注入類,最終放到configuration類緩存,形成一個sql
具體看看AbstractMethod的實現(xiàn)類,而默認的DefaultSqlInject類就有我們baseMapper所包含的默認方法,我們要添加多兩個方法,批量更新和批量插入
下面開始編碼
首先繼承AbstarctMethod類,實現(xiàn)我們想注入的sql模板
對于批量插入,使用 insert into table columns values (values),(values),(values)
對于批量更新,使用 update table set xx=xx,xx=xx where xx=xx;update table set xx=xx,xx=xx where xx=xx;update table set xx=xx,xx=xx where xx=xx;進行分號分割
因為要用到標簽解析的功能,需要用script包裹起來。
具體初始化模板如下, 對其進行填充
public enum CustomSqlMethodEnum { /** * 批量插入 */ INSERT_BATCH("insertBatch", "批量插入", "<script>\n" + "INSERT INTO %s %s VALUES \n" + "<foreach collection=\"collection\" item=\"item\" separator=\",\"> %s\n </foreach>\n" + "</script>"), /** * 批量更新 */ UPDATE_BATCH("updateBatchByIds", "批量更新", "<script>\n" + "<foreach collection=\"collection\" item=\"item\" separator=\";\"> update %s set %s where %s </foreach>\n" + "</script>" ), /** * 根據(jù)聯(lián)合鍵查詢 */ SELECT_BY_COMPOSEKEYS("selectByComposeKeys", "聯(lián)合主鍵查詢", "<script>" + " select <choose> <when test=\"ew!=null and ew.sqlSelect != null and ew.sqlSelect != ''\"> ${ew.sqlSelect} </when> <otherwise> * </otherwise> </choose> from %s " + "<where> <foreach collection=\"collection\" item=\"item\" open=\"(\" close=\")\" separator=\"or\"> ( %s ) </foreach> <if test=\"ew!=null and ew.sqlSegment != null and ew.sqlSegment != ''\">\n" + "AND ${ew.sqlSegment}\n" + "</if> </where> </script>" ), /** * 根據(jù)聯(lián)合鍵查詢 */ SELECT_IDS_BY_COMPOSEKEYS("selectIdsByComposeKeys", "聯(lián)合主鍵查詢id", "<script>" + " select %s from %s " + "<where> <foreach collection=\"collection\" item=\"item\" open=\"(\" close=\")\" separator=\"or\"> ( %s ) </foreach> </where> </script>" ), /** * 根據(jù)聯(lián)合主鍵刪除 */ DELETE_BY_COMPOSEKEYS("deleteByComposeKeys", "聯(lián)合主鍵刪除", "<script>" + " delete from %s " + "<where> <foreach collection=\"collection\" item=\"item\" open=\"(\" close=\")\" separator=\"or\"> ( %s ) </foreach> </where> </script>" ), /** * 根據(jù)聯(lián)合主鍵更新 */ UPDATE_BY_COMPOSEKEYS("updateByComposeKeys" , "聯(lián)合主鍵批量修改", "<script>\n" + "<foreach collection=\"collection\" item=\"item\" separator=\";\"> update %s set %s where %s </foreach>\n" + "</script>" ); private final String method; private final String desc; private final String sql; CustomSqlMethodEnum(String method, String desc, String sql) { this.method = method; this.desc = desc; this.sql = sql; } public String getMethod() { return method; } public String getDesc() { return desc; } public String getSql() { return sql; } }
下面是構(gòu)造批量插入和更新的代碼邏輯
批量插入:主要是構(gòu)造——表名、插入列、插入值,這里為了防止覆蓋數(shù)據(jù)庫默認值,也可以做if test 的那種判空,來取消插入,不過比較麻煩,這里就不做了
public class InsertBatch extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = new NoKeyGenerator(); CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.INSERT_BATCH; // ==== 拼接 sql 模板 ============== StringBuilder columnScriptBuilder = new StringBuilder(LEFT_BRACKET); StringBuilder valuesScriptBuilder = new StringBuilder(LEFT_BRACKET); // 主鍵拼接 if (StringUtils.isNotBlank(tableInfo.getKeyColumn())) { //(x1,x2,x3 columnScriptBuilder.append(tableInfo.getKeyColumn()).append(COMMA); //(item.xx,item.xx valuesScriptBuilder.append(SqlScriptUtils.safeParam(SQLConditionWrapper.ITEM + DOT + tableInfo.getKeyProperty())).append(COMMA); } // 普通字段拼接 List<TableFieldInfo> fieldList = tableInfo.getFieldList(); for (TableFieldInfo fieldInfo : fieldList) { //有更新默認填充器的不參與賦值 if (!fieldInfo.isWithInsertFill() && fieldInfo.isWithUpdateFill()) { continue; } columnScriptBuilder.append(fieldInfo.getColumn()).append(COMMA); valuesScriptBuilder.append(SqlScriptUtils.safeParam(SQLConditionWrapper.getCondition(fieldInfo.getProperty()).toString())).append(COMMA); } // 替換多余的逗號為括號 //(x1,x2) columnScriptBuilder.setCharAt(columnScriptBuilder.length() - 1, ')'); //(item.xx,item.xx2) valuesScriptBuilder.setCharAt(valuesScriptBuilder.length() - 1, ')'); // sql 模板占位符替換 String columnScript = columnScriptBuilder.toString(); String valuesScript = valuesScriptBuilder.toString(); String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript); // === mybatis 主鍵邏輯處理:主鍵生成策略,以及主鍵回填======= String keyColumn = null; String keyProperty = null; // 表包含主鍵處理邏輯,如果不包含主鍵當普通字段處理 if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) { if (tableInfo.getIdType() == IdType.AUTO) { /** 自增主鍵 */ keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(sqlMethod.getMethod(), tableInfo, builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } // 模板寫入 SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addInsertMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource, keyGenerator, keyProperty, keyColumn); } }
更新sql構(gòu)造,這里要注意字段上的字段策略(FieldStrategy),附上處理類
public class UpdateBatchByIds extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.UPDATE_BATCH; /** * update table set <if test ="item.pro!=null">key1=#{item.pro}</if>, key2=#{item.pro2} where keycolom = %s * */ StringBuilder withChevScript = new StringBuilder(); StringBuilder lastFiledScriptBuilder=new StringBuilder(); StringBuilder keyScriptBuilder = new StringBuilder(); if (StringUtils.isNotBlank(tableInfo.getKeyColumn())) { keyScriptBuilder.append(tableInfo.getKeyColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.ITEM + DOT + tableInfo.getKeyProperty())); } List<TableFieldInfo> fieldList = tableInfo.getFieldList(); for (int i = 0; i < fieldList.size(); i++) { TableFieldInfo fieldInfo = fieldList.get(i); Boolean isLast=(i==(fieldList.size()-1)); //有插入默認填充器的不參與賦值 if (fieldInfo.isWithInsertFill() && !fieldInfo.isWithUpdateFill()) { continue; } boolean change = false; if (Objects.equals(fieldInfo.getUpdateStrategy(), FieldStrategy.NOT_NULL) && !fieldInfo.isWithUpdateFill()) { SQLConditionWrapper.appendNotNull(withChevScript, fieldInfo.getProperty()); change = true; } if (Objects.equals(fieldInfo.getUpdateStrategy(), FieldStrategy.NOT_EMPTY) && !fieldInfo.isWithUpdateFill()) { SQLConditionWrapper.appendNotEmpty(withChevScript, fieldInfo.getProperty()); change = true; } if (change) { withChevScript.append(fieldInfo.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.getCondition(fieldInfo.getProperty()).toString())); withChevScript.append(COMMA).append("</if>"); if(isLast&&lastFiledScriptBuilder.length()==0){ //如果沒有其他字段替補,弄個占位的,不然會出現(xiàn)語法錯誤的情況 withChevScript.append(tableInfo.getKeyColumn()).append(EQUALS).append(tableInfo.getKeyColumn()); } }else{ lastFiledScriptBuilder.append(fieldInfo.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.getCondition(fieldInfo.getProperty()).toString())).append(COMMA); } } //處理多余的逗號 if(!StringUtils.isBlank(lastFiledScriptBuilder)&&!StringUtils.isBlank(withChevScript)){ int leftChevIndex= withChevScript.lastIndexOf(LEFT_CHEV); if(withChevScript.charAt(leftChevIndex-1)!=','){ withChevScript.replace(leftChevIndex,leftChevIndex+1,",<"); } if(lastFiledScriptBuilder.lastIndexOf(COMMA)==lastFiledScriptBuilder.length()-1){ lastFiledScriptBuilder.deleteCharAt(lastFiledScriptBuilder.length()-1); } } withChevScript.append(lastFiledScriptBuilder); // sql 模板占位符替換 String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), withChevScript, keyScriptBuilder); // 模板寫入 SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } }
public class SQLConditionWrapper { public final static String ITEM = "item"; public static StringBuilder appendNotNull(StringBuilder builder, String property) { return appendEnd(appendStart(builder, getCondition(property))); } public static StringBuilder appendNotEmpty(StringBuilder builder, String property) { StringBuilder condition = getCondition(property); StringBuilder append = appendStart(builder, condition) .append(" ").append(AND).append(" ").append(condition).append(EXCLAMATION_MARK).append(EQUALS).append(" ").append("''"); return appendEnd(append); } private static StringBuilder appendEnd(StringBuilder builder) { builder.append("\"") .append(RIGHT_CHEV); return builder; } private static StringBuilder appendStart(StringBuilder builder, StringBuilder item) { builder.append(LEFT_CHEV) .append("if test=\"") .append(item).append(EXCLAMATION_MARK).append(EQUALS).append(NULL); return builder; } public static StringBuilder getCondition(String property) { return new StringBuilder() .append(ITEM).append(DOT).append(property); } }
然后就自定義mapper及sql注入器
public interface CustomMapper<T> extends BaseMapper<T> { /** * 批量插入 * @param collection 批量插入數(shù)據(jù) * @return ignore */ int insertBatch(@Param("collection") Collection<T> collection); /** * 批量更新 * @param collection * @return */ int updateBatchByIds(@Param("collection") Collection<T> collection); }
這里sql注入器要判斷下是繼承了該類,才進行方法擴展
@ConditionalOnExpression("${mybatis-plus.custom-mapper.enabled:true}") @Component public class CustomSqlInject extends DefaultSqlInjector { //初始化時候會加載 @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); //屬于自己定義的mapper才添加方法 if (((ParameterizedTypeImpl) mapperClass.getGenericInterfaces()[0]).getRawType().equals(CustomMapper.class)) { methodList.add(new InsertBatch()); methodList.add(new UpdateBatchByIds()); } if (((ParameterizedTypeImpl) mapperClass.getGenericInterfaces()[0]).getRawType().equals(ComposeKeyMapper.class)) { methodList.add(new SelectByComposeKeys()); methodList.add(new SelectIdsByComposeKeys()); methodList.add(new UpdateByComposeKeys()); methodList.add(new InsertBatch()); } return methodList; } }
可以直接注入到spring里面,或者通過進行設(shè)置
GlobalConfigUtils.getGlobalConfig(sqlSessionFactory.getConfiguration()).setSqlInjector(xx)
測試一下,定義實體,下面的typeHandler可以去掉,
@TableName( value = "demo" ) @Data @Accessors(chain = true) public class Demo extends BaseEntity { private static final long serialVersionUID = 1L; @TableId("id") private Long id; @TableField("name") private String name; @TableField( value = "age", typeHandler = LongFormatAESEncryptHandler.class ) private Long age; @TableField( value = "secret", typeHandler = StringFormatAESEncryptHandler.class ) private String secret; @TableField( value = "CREATED_ID", fill = FieldFill.INSERT ) private Long createdId; @TableField( value = "CREATED_BY", fill = FieldFill.INSERT ) private String createdBy; @TableField( value = "CREATION_DATE", fill = FieldFill.INSERT ) private Date creationDate; @TableField( value = "CREATED_BY_IP", fill = FieldFill.INSERT ) private String createdByIp; @TableField( value = "LAST_UPDATED_ID", fill = FieldFill.UPDATE ) private Long lastUpdatedId; @TableField( value = "LAST_UPDATED_BY", fill = FieldFill.UPDATE ) private String lastUpdatedBy; @TableField( value = "LAST_UPDATE_DATE", fill = FieldFill.INSERT_UPDATE ) private Date lastUpdateDate; @TableField( value = "LAST_UPDATED_BY_IP", fill = FieldFill.UPDATE ) private String lastUpdatedByIp; }
繼承自定義mapper
public interface TempMapper extends CustomMapper<Demo> { }
controller了的兩個方法,可以支持事務(wù)
@GetMapping("/testBatch") @Transactional(rollbackFor = Exception.class) public void testBatch() { List<Demo> demos = new LinkedList<>(); for (int i = 0; i < 10; i++) { Demo de = new Demo().setAge(Long.valueOf(i)) .setId(Long.valueOf(i + 1)) .setName("test" + i) .setSecret(UUID.randomUUID().toString()); demos.add(de); } tempMapper.insertBatch(demos); } @GetMapping("/testBatch2") @Transactional(rollbackFor = Exception.class) public void testBatch2() { List<Demo> demos = new LinkedList<>(); for (int i = 0; i < 5; i++) { Demo de = new Demo().setAge(Long.valueOf(i)) .setId(Long.valueOf(i + 1)) .setName("test-change" + i) .setSecret(UUID.randomUUID().toString()); demos.add(de); } tempMapper.updateBatchByIds(demos); //int i=1/0; }
對于自定sql注入,還可以抽取一些公用的方法進行添加,比如邏輯刪除,版本號更新,進行聯(lián)合主鍵的批量修改和查詢,下面給出聯(lián)合主鍵的處理方案
下面是構(gòu)造根據(jù)wrapper和聯(lián)合主鍵進行查詢的,核心也是定義好sql,然后進行注入,但這里需要自定義一個注解來標識哪個是聯(lián)合主鍵
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ComposeKey { }
public class SelectByComposeKeys extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.SELECT_BY_COMPOSEKEYS; //select #{ew.sqlSelect} from table_name where (composekey1=xx and composekey2=xx) or (composekey1=xx1 and composekey2=xx1) String sqlTemplate = sqlMethod.getSql(); String tableName = tableInfo.getTableName(); List<TableFieldInfo> composeKeys = tableInfo.getFieldList().stream().filter(e -> e.getField().isAnnotationPresent(ComposeKey.class)) .collect(Collectors.toList()); if(CollectionUtils.isEmpty(composeKeys)){ throw new ApiException("not composeKey found in class:"+modelClass.getName()); } StringBuilder builder=new StringBuilder(); for (int i = 0; i < composeKeys.size(); i++) { TableFieldInfo composeKey = composeKeys.get(i); builder.append(composeKey.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.ITEM + DOT + composeKey.getProperty())); if(i!=composeKeys.size()-1){ builder.append(" ").append(AND).append(" "); } } String finalSql = String.format(sqlTemplate, tableName, builder); SqlSource sqlSource = languageDriver.createSqlSource(configuration, finalSql, modelClass); return this.addSelectMappedStatementForTable(mapperClass,sqlMethod.getMethod(),sqlSource,tableInfo); } }
更新操作
public class UpdateByComposeKeys extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.UPDATE_BY_COMPOSEKEYS; /** * update table set <if test ="item.pro!=null">key1=#{item.pro}</if>, key2=#{item.pro2} where keycolom = %s */ StringBuilder withChevScript = new StringBuilder(); StringBuilder lastFiledScriptBuilder = new StringBuilder(); StringBuilder keyScriptBuilder = new StringBuilder(); StringBuilder placeHolder = new StringBuilder(); List<TableFieldInfo> fieldList = tableInfo.getFieldList(); for (int i = 0; i < fieldList.size(); i++) { TableFieldInfo fieldInfo = fieldList.get(i); Boolean isLast = (i == (fieldList.size() - 1)); if (fieldInfo.getField().isAnnotationPresent(ComposeKey.class)) { keyScriptBuilder.append(" ").append(fieldInfo.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.ITEM + DOT + fieldInfo.getProperty())) .append(" ").append(AND); placeHolder.append(fieldInfo.getColumn()).append(EQUALS).append(fieldInfo.getColumn()).append(COMMA); continue; } //有插入默認填充器或者主鍵的不參與賦值 if (fieldInfo.isWithInsertFill() && !fieldInfo.isWithUpdateFill()) { continue; } boolean change = false; if (Objects.equals(fieldInfo.getUpdateStrategy(), FieldStrategy.NOT_NULL) && !fieldInfo.isWithUpdateFill()) { SQLConditionWrapper.appendNotNull(withChevScript, fieldInfo.getProperty()); change = true; } if (Objects.equals(fieldInfo.getUpdateStrategy(), FieldStrategy.NOT_EMPTY) && !fieldInfo.isWithUpdateFill()) { SQLConditionWrapper.appendNotEmpty(withChevScript, fieldInfo.getProperty()); change = true; } if (change) { withChevScript.append(fieldInfo.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.getCondition(fieldInfo.getProperty()).toString())); withChevScript.append(COMMA).append("</if>"); if (isLast && lastFiledScriptBuilder.length() == 0) { //如果沒有其他字段替補,弄個占位的,不然會出現(xiàn)語法錯誤的情況 if (placeHolder.length() > 0) { //刪除多余的逗號 placeHolder.deleteCharAt(placeHolder.length() - 1); withChevScript.append(placeHolder); } } } else { lastFiledScriptBuilder.append(fieldInfo.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.getCondition(fieldInfo.getProperty()).toString())).append(COMMA); } } if (placeHolder.length() == 0) { throw new ApiException("not composeKey found in class:" + modelClass.getName()); } //處理多余的逗號 if (!StringUtils.isBlank(lastFiledScriptBuilder) && !StringUtils.isBlank(withChevScript)) { int leftChevIndex = withChevScript.lastIndexOf(LEFT_CHEV); if (withChevScript.charAt(leftChevIndex - 1) != ',') { withChevScript.replace(leftChevIndex, leftChevIndex + 1, ",<"); } if (lastFiledScriptBuilder.lastIndexOf(COMMA) == lastFiledScriptBuilder.length() - 1) { lastFiledScriptBuilder.deleteCharAt(lastFiledScriptBuilder.length() - 1); } } if(!StringUtils.isBlank(keyScriptBuilder)){ int i = keyScriptBuilder.lastIndexOf(AND); keyScriptBuilder.delete(i,i+AND.length()); } withChevScript.append(lastFiledScriptBuilder); // sql 模板占位符替換 String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), withChevScript, keyScriptBuilder); // 模板寫入 SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } }
public class SelectIdsByComposeKeys extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.SELECT_IDS_BY_COMPOSEKEYS; //select composekeys from table_name where (composekey1=xx and composekey2=xx) or (composekey1=xx1 and composekey2=xx1) String sqlTemplate = sqlMethod.getSql(); String tableName = tableInfo.getTableName(); List<TableFieldInfo> composeKeys = tableInfo.getFieldList().stream().filter(e -> e.getField().isAnnotationPresent(ComposeKey.class)) .collect(Collectors.toList()); if(CollectionUtils.isEmpty(composeKeys)){ throw new ApiException("not composeKey found in class:"+modelClass.getName()); } StringBuilder builder=new StringBuilder(); StringBuilder select=new StringBuilder(); for (int i = 0; i < composeKeys.size(); i++) { TableFieldInfo composeKey = composeKeys.get(i); select.append(composeKey.getColumn()); builder.append(composeKey.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.ITEM + DOT + composeKey.getProperty())); if(i!=composeKeys.size()-1){ builder.append(" ").append(AND).append(" "); select.append(COMMA); } } String finalSql = String.format(sqlTemplate,select, tableName, builder); SqlSource sqlSource = languageDriver.createSqlSource(configuration, finalSql, modelClass); return this.addSelectMappedStatementForTable(mapperClass,sqlMethod.getMethod(),sqlSource,tableInfo); } }
public class DeleteByComposeKeys extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.DELETE_BY_COMPOSEKEYS; //delete from table_name where (composekey1=xx and composekey2=xx) or (composekey1=xx1 and composekey2=xx1) String sqlTemplate = sqlMethod.getSql(); String tableName = tableInfo.getTableName(); List<TableFieldInfo> composeKeys = tableInfo.getFieldList().stream().filter(e -> e.getField().isAnnotationPresent(ComposeKey.class)) .collect(Collectors.toList()); if(CollectionUtils.isEmpty(composeKeys)){ throw new ApiException("not composeKey found in class:"+modelClass.getName()); } StringBuilder builder=new StringBuilder(); for (int i = 0; i < composeKeys.size(); i++) { TableFieldInfo composeKey = composeKeys.get(i); builder.append(composeKey.getColumn()).append(EQUALS).append(SqlScriptUtils.safeParam(SQLConditionWrapper.ITEM + DOT + composeKey.getProperty())); if(i!=composeKeys.size()-1){ builder.append(" ").append(AND).append(" "); } } String finalSql = String.format(sqlTemplate, tableName, builder); SqlSource sqlSource = languageDriver.createSqlSource(configuration, finalSql, modelClass); return this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource); } }
定義mappe類,這里對于原生的方法就不進行支持了
public interface ComposeKeyMapper<T> extends CustomMapper<T> { /** * @param params 查詢的主鍵入?yún)? * @param wrapper 查詢的列會用到 ew.sqlSelect * @return */ List<T> selectByComposeKeys(@Param("collection") Collection<T> params, @Param(Constants.WRAPPER) Wrapper<T> wrapper); /** * 批量更新 * * @param params */ void updateByComposeKeys(@Param("collection") Collection<T> params); /** * 只查詢主鍵 * * @param params * @return */ List<T> selectIdsByComposeKeys(@Param("collection") Collection<T> params); /** * 聯(lián)合主鍵刪除 * @param params * @return */ int deleteByComposeKeys(@Param("collection")Collection<T> params); /** * 下面的方法不支持 * @param entity * @return */ @Override default int updateById(T entity) { throw new UnsupportedOperationException(); } @Override default int updateBatchByIds(Collection<T> collection) { throw new UnsupportedOperationException(); } @Override default T selectById(Serializable id) { throw new UnsupportedOperationException(); } @Override default List<T> selectBatchIds(Collection<? extends Serializable> idList) { throw new UnsupportedOperationException(); } @Override default int deleteBatchIds(Collection<? extends Serializable> idList){ throw new UnsupportedOperationException(); } @Override default int deleteById(Serializable id){ throw new UnsupportedOperationException(); } }
可以看到查詢是按照預(yù)期給出的,也支持wrapper形式的查詢
要注意,xml文件里的優(yōu)先級是比自定義注入的優(yōu)先級高的,這里沒給出單個update,單個查詢,單個刪除的方法,這些都可以根據(jù)自己業(yè)務(wù)需求弄一下,項目上沒用到就不寫了
對于sqlSession也可以自己做一些小優(yōu)化
類型有reuse,編譯一次可以繼續(xù)服用,使用場景:執(zhí)行同一個sql,參數(shù)值不同
默認的simple是每次都會編譯一下
batch相當于一個會話里面執(zhí)行多條sql
reuse示例
public static void executeReuse(Consumer<SqlSession> consumer) { SqlSessionFactory factory = SpringContextHolder.getBean(SqlSessionFactory.class); SqlSession sqlSession = factory.openSession(ExecutorType.REUSE, true); try { consumer.accept(sqlSession); } finally { sqlSession.close(); } }
到此這篇關(guān)于mybatis-plus攔截器、字段填充器、類型處理器、表名替換、SqlInjector(聯(lián)合主鍵處理)的文章就介紹到這了,更多相關(guān)mybatis-plus攔截器、字段填充器、類型處理器、表名替換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MybatisPlus攔截器如何實現(xiàn)數(shù)據(jù)表分表
- mybatis-plus配置攔截器實現(xiàn)sql完整打印的代碼設(shè)計
- MyBatis-Plus攔截器實現(xiàn)數(shù)據(jù)權(quán)限控制的方法
- Mybatis-plus通過添加攔截器實現(xiàn)簡單數(shù)據(jù)權(quán)限
- MybatisPlusInterceptor實現(xiàn)sql攔截器超詳細教程
- MyBatis-Plus攔截器實現(xiàn)數(shù)據(jù)權(quán)限控制的示例
- mybatis-plus 攔截器敏感字段加解密的實現(xiàn)
- MyBatis-Plus攔截器對敏感數(shù)據(jù)實現(xiàn)加密
- mybatisplus 的SQL攔截器實現(xiàn)關(guān)聯(lián)查詢功能
- Mybatis Plus 3.4.0分頁攔截器的用法小結(jié)
相關(guān)文章
SpringBoot實現(xiàn)License認證(只校驗有效期)的詳細過程
License也就是版權(quán)許可證書,一般用于收費軟件給付費用戶提供的訪問許可證明,這篇文章主要介紹了SpringBoot實現(xiàn)License認證(只校驗有效期),需要的朋友可以參考下2024-04-04java圖片滑動驗證(登錄驗證)原理與實現(xiàn)方法詳解
這篇文章主要介紹了java圖片滑動驗證(登錄驗證)原理與實現(xiàn)方法,結(jié)合實例形式詳細分析了java圖片滑動登錄驗證的相關(guān)原理、實現(xiàn)方法與操作技巧,需要的朋友可以參考下2019-09-09Spring中property-placeholder的使用與解析詳解
本篇文章主要介紹了Spring中property-placeholder的使用與解析詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-058個簡單部分開啟Java語言學(xué)習(xí)之路 附j(luò)ava學(xué)習(xí)書單
8個簡單部分開啟Java語言學(xué)習(xí)之路,附j(luò)ava學(xué)習(xí)書單,這篇文章主要向大家介紹了學(xué)習(xí)java語言的方向,感興趣的小伙伴們可以參考一下2016-09-09使用spring-task定時任務(wù)動態(tài)配置修改執(zhí)行時間
這篇文章主要介紹了使用spring-task定時任務(wù)動態(tài)配置修改執(zhí)行時間,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11如何保證RabbitMQ全鏈路數(shù)據(jù)100%不丟失問題
這篇文章主要介紹了如何保證RabbitMQ全鏈路數(shù)據(jù)100%不丟失問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05