欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

mybatis-plus攔截器、字段填充器、類型處理器、表名替換、SqlInjector(聯(lián)合主鍵處理)

 更新時間:2021年11月09日 08:52:05   作者:LL小蝸牛  
本文主要介紹了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)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論