最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2021年11月09日 08:52:05   作者:LL小蝸牛  
本文主要介紹了mybatis-plus攔截器、字段填充器、類型處理器、表名替換、SqlInjector(聯(lián)合主鍵處理),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下

最近有個練手的小例子,大概就是配置兩個數(shù)據(jù)源,從一個數(shù)據(jù)源讀取數(shù)據(jù)寫到另一個數(shù)據(jù)源,雖然最后做了出來,但是不支持事務(wù)。。。就當(dāng)是對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)度的核心,負(fù)責(zé)SQL語句的生成和查詢緩存的維護(hù)
  • StatementHandler 封裝了JDBC Statement操作,負(fù)責(zé)對JDBC statement 的操作,如設(shè)置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換成List集合。
  • ParameterHandler 負(fù)責(zé)對用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù),
  • ResultSetHandler 負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對象轉(zhuǎn)換成List類型的集合;
  • TypeHandler 負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換
  • MappedStatement MappedStatement維護(hù)了一條<select|update|delete|insert>節(jié)點(diǎn)的封裝,
  • SqlSource 負(fù)責(zé)根據(jù)用戶傳遞的parameterObject,動態(tài)地生成SQL語句,將信息封裝到BoundSql對象中,并返回
  • BoundSql 表示動態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息

組件介紹

攔截器

mybatis可以在執(zhí)行語句的過程中對特定對象進(jìn)行攔截調(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ù)

這四個是可以攔截的對象,大概的做法是實(shí)現(xiàn)mybatis攔截器的接口并在上面添加注解來確定攔截那些方法

下面是接口Interceptor所要實(shí)現(xiàn)的方法,setPropertites可以用來初始化,而plugin則包裝目標(biāo)對象供攔截器處理,基于動態(tài)代理實(shí)現(xiàn),Plugin類是動態(tài)代理類,對實(shí)現(xiàn)Interceptor的接口的類進(jìn)行處理,而實(shí)現(xiàn)的攔截器會被加入到攔截器鏈進(jì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)過代理鏈處理的對象

實(shí)現(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ù)源,這個其實(shí)原理和之前寫的一篇代碼級別讀寫分離很相似,也是通過ThreadLocal存放當(dāng)前線程的數(shù)據(jù)源,然后通過攔截器來判斷用哪個數(shù)據(jù)源,交由AbstarctRoutingDataSource來根據(jù)ThreadLoacl里面的值來處理。

但是有個問題,兩個數(shù)據(jù)源轉(zhuǎn)換,表名、字段名不一定相等,比如從pgsql的一個叫user_info表里的數(shù)據(jù)轉(zhuǎn)到mysql叫user表的數(shù)據(jù),字段名都不相同

我的處理方法是查詢對象的目標(biāo)的字段名為準(zhǔn),然后給每個字段一個注解指向修改對象的數(shù)據(jù)源表字段名,如果查詢目標(biāo)表沒有插入目標(biāo)表的字段,便在select的時候默認(rèn)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)的實(shí)體class,可以用來獲取字段信息注解及表名信息注解,而另一個成員變量則是用來存放待會說到的表名替換,這里面實(shí)現(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(即增刪改)方法進(jìn)行攔截,改方法使用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) {
 
    }
}

然后對字段進(jìn)行修改的攔截器,這里為什么要繼承AbstactSqlPaserHandler呢,因?yàn)榭梢詮?fù)用他的方法,以及為后來加入表名替換的類做準(zhǔn)備,這里的流程是獲取原來字段的名字,并改為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)在還有一個問題要處理,就是表名替換,但是這個有個小坑,這個功能也相當(dāng)于上面替換sql的功能比如insert into user(user_info,user_id) values ...,比如把user這個表名替換為user_info這個表來執(zhí)行,此時的插入語句會把所有user的替換成user_info,這時候官方的建議是用@TableName這個注解更改表名避免出現(xiàn)這個情況

表名處理器

使用:實(shí)現(xiàn)ITableNameHandler,并實(shí)現(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表里面沒有,這時候想在插入時使用一個默認(rèn)的值,這時候可以使用字段填充器

做法:實(shí)現(xiàn)MetaObjectHandler接口,并重寫里面的方法,然后在字段的@TableField的fill類型里面說明需要填充時情況

例子,對createTime,updateTime,bouns進(jìn)行默認(rèn)填充,使用getFieldValueByName和setFieldValByName方法進(jìn)行賦值

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ù)組實(shí)現(xiàn)類型轉(zhuǎn)換處理,setxxx方法主要是對參數(shù)進(jìn)行處理,然后把處理后的結(jié)果放入數(shù)據(jù)庫中,而get方法則處理從數(shù)據(jù)庫取出來的數(shù)據(jù)該如何處理,這里接受一個lambda函數(shù)作為方法轉(zhuǎn)換,即字符串-》目標(biāo)類型,這里定義一個抽象類統(tǒng)一處理方法,然后具體轉(zhuǎn)換方法由子類實(shí)現(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方法也不支持,因?yàn)閮蓚€數(shù)據(jù)源都不一樣,他是先查,看是否有再做更新或者插入操作,這些問題仍需解決,且當(dāng)作一個使用方法記錄的小例子吧

補(bǔ)充

mybatis-plus默認(rèn)的baseMapper不支持批量的插入和更新,有時候會為了這個批量的方法去繼承serviceImpl類,而這個類有沒有其他的業(yè)務(wù)代碼在里面,繼承僅僅是為了獲取批量操作的方法,會顯得這個類有點(diǎn)”貧血“

對于這種情況,偶然看到一個開源的項目onemall對BaseMapper做了擴(kuò)展,使它支持了批量插入的做法,在此基礎(chǔ)下,我也做了一定的擴(kuò)展

根據(jù)自己的見解,簡單描述下流程:

核心就是自定義一個sql自動注入器,mybatis-plus會掃描mapper類 ,為每個mapper類構(gòu)造一個MappedStatement(相當(dāng)于mapper里的一個sql)

添加到配置類里面(具體在下面(2)),只會在初始化時候執(zhí)行一次,而我們要做的,就是把某個特定mapper,注入我們想要的模板方法

具體注入在MybatisMapperAnnotationBuilder,注入動態(tài)sql,getSqlInjector獲取sql注入器,并為當(dāng)前的MapperBuilderAssistant對象(類似于mapper)注入mappedStatement(sql)

(1)注入開始,里面的inspectInject就是

inspectInject就會把a(bǔ)bstractMethod一一注入進(jìn)去

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的實(shí)現(xiàn)類,而默認(rèn)的DefaultSqlInject類就有我們baseMapper所包含的默認(rèn)方法,我們要添加多兩個方法,批量更新和批量插入

下面開始編碼

首先繼承AbstarctMethod類,實(shí)現(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;進(jìn)行分號分割

因?yàn)橐玫綐?biāo)簽解析的功能,需要用script包裹起來。


具體初始化模板如下, 對其進(jìn)行填充

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ù)庫默認(rèn)值,也可以做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) {
            //有更新默認(rèn)填充器的不參與賦值
            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;
        // 表包含主鍵處理邏輯,如果不包含主鍵當(dāng)普通字段處理
        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));
            //有插入默認(rèn)填充器的不參與賦值
            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){
                    //如果沒有其他字段替補(bǔ),弄個占位的,不然會出現(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注入器要判斷下是繼承了該類,才進(jìn)行方法擴(kuò)展

@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里面,或者通過進(jìn)行設(shè)置

GlobalConfigUtils.getGlobalConfig(sqlSessionFactory.getConfiguration()).setSqlInjector(xx)

測試一下,定義實(shí)體,下面的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注入,還可以抽取一些公用的方法進(jìn)行添加,比如邏輯刪除,版本號更新,進(jìn)行聯(lián)合主鍵的批量修改和查詢,下面給出聯(lián)合主鍵的處理方案

下面是構(gòu)造根據(jù)wrapper和聯(lián)合主鍵進(jìn)行查詢的,核心也是定義好sql,然后進(jìn)行注入,但這里需要自定義一個注解來標(biāo)識哪個是聯(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;
            }
            //有插入默認(rèn)填充器或者主鍵的不參與賦值
            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) {
                    //如果沒有其他字段替補(bǔ),弄個占位的,不然會出現(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類,這里對于原生的方法就不進(jìn)行支持了

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ù)值不同

默認(rèn)的simple是每次都會編譯一下

batch相當(dāng)于一個會話里面執(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)文章

  • Spring的懶加載機(jī)制原理和配置詳解

    Spring的懶加載機(jī)制原理和配置詳解

    這篇文章主要介紹了Spring的懶加載機(jī)制原理和配置詳解,Spring提供了懶加載機(jī)制,所謂的懶加載機(jī)制就是可以規(guī)定指定的bean不在啟動時立即創(chuàng)建,而是在后續(xù)第一次用到時才創(chuàng)建,從而減輕在啟動過程中對時間和內(nèi)存的消耗,需要的朋友可以參考下
    2023-10-10
  • 啟動 Eclipse 彈出 Failed to load the JNI shared library jvm.dll 錯誤的解決方法

    啟動 Eclipse 彈出 Failed to load the JNI shared library jvm.dll

    這篇文章主要介紹了有時候,新電腦上回碰到打開Eclipse時,彈出提示“Failed to load the JNI shared library jvm.dll”錯誤,這里給大家分享解決方案
    2016-08-08
  • Spring-AOP 靜態(tài)正則表達(dá)式方法如何匹配切面

    Spring-AOP 靜態(tài)正則表達(dá)式方法如何匹配切面

    這篇文章主要介紹了Spring-AOP 靜態(tài)正則表達(dá)式方法如何匹配切面的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 解決多模塊項目中Mybatis的Mapper內(nèi)部方法找不到的問題

    解決多模塊項目中Mybatis的Mapper內(nèi)部方法找不到的問題

    這篇文章主要介紹了解決多模塊項目中Mybatis的Mapper內(nèi)部方法找不到的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • IDEA報錯Error?running‘Application‘:Command?line?is?too?long的問題

    IDEA報錯Error?running‘Application‘:Command?line?is?too?lo

    這篇文章主要介紹了IDEA報錯Error?running?‘Application‘:Command?line?is?too?long的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • java maven中如何引入自己的lib

    java maven中如何引入自己的lib

    在JavaMaven項目中引入自己的庫可以簡化為幾個步驟:首先,確保庫以JAR格式存在或打包成JAR;其次,將JAR文件放置在項目目錄或安裝到本地Maven倉庫;最后,在pom.xml中添加依賴,這樣做可以使項目更加模塊化,便于管理和維護(hù),感興趣的朋友跟隨小編一起看看吧
    2024-09-09
  • Java?lambda表達(dá)式與泛型整理總結(jié)

    Java?lambda表達(dá)式與泛型整理總結(jié)

    Lambda?表達(dá)式(lambda?expression)是一個匿名函數(shù),Lambda表達(dá)式基于數(shù)學(xué)中的λ演算得名。泛型編程,故如其名,是一個泛化的編程方式。其實(shí)現(xiàn)原理為程序員編寫一個函數(shù)/類的代碼示例,讓編譯器去填補(bǔ)出不同的函數(shù)實(shí)現(xiàn)
    2022-07-07
  • Java實(shí)現(xiàn)簡單棋盤存檔和讀取功能

    Java實(shí)現(xiàn)簡單棋盤存檔和讀取功能

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡單棋盤存檔和讀取功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • JDK的具體安裝步驟(帶圖帶解釋巨詳細(xì))

    JDK的具體安裝步驟(帶圖帶解釋巨詳細(xì))

    Java是一種廣泛使用的編程語言,許多應(yīng)用程序和系統(tǒng)都依賴于它,如果您想進(jìn)行Java編程或運(yùn)行Java應(yīng)用程序,首先需要安裝Java開發(fā)工具包(JDK),這篇文章主要給大家介紹了關(guān)于JDK具體安裝步驟的相關(guān)資料,文中介紹的方法帶圖帶解釋巨詳細(xì),需要的朋友可以參考下
    2024-05-05
  • 基于HttpClient在HTTP協(xié)議接口測試中的使用(詳解)

    基于HttpClient在HTTP協(xié)議接口測試中的使用(詳解)

    下面小編就為大家?guī)硪黄贖ttpClient在HTTP協(xié)議接口測試中的使用(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10

最新評論

隆回县| 大安市| 尚义县| 怀宁县| 邳州市| 陇川县| 海原县| 英吉沙县| 辽阳县| 韶山市| 宜黄县| 博湖县| 分宜县| 洛阳市| 神池县| 台东市| 四会市| 桐梓县| 建水县| 潮州市| 万州区| 满洲里市| 桂阳县| 体育| 赤城县| 承德市| 镇安县| 望城县| 滁州市| 宕昌县| 卓资县| 静宁县| 武宁县| 凤台县| 乳源| 芜湖县| 囊谦县| 卫辉市| 汨罗市| 保亭| 高安市|