Skip to content

数据源持久化相关工具类

说明

封装了数据源、Mybatis相关配置类、工具类.

[源码结构]
java
└── top
    └── acuity
        └── commons
            └── database
                ├── config
                │   ├── BaseMybatisConfiguration.java               # 全局Mybatias公共配置
                │   └── AcuityMetaObjectHandler.java			    # 元对象字段(id、created_by等字段)填充控制器
                ├── injector
                │   ├── AcuitySqlInjector.java						# SQL 注入器(增强SuperMapper中全局接口)
                │   └── method
                │       └── UpdateAllById.java						# 具体注入的方法
                ├── mybatis
                │   ├── WriteInterceptor.java						# 禁止修改、删除重要数据拦截器
                │   ├── conditions
                │   │   ├── Wraps.java								# 对mybatis-plus提供的Wrappers增强
                │   │   ├── query
                │   │   │   ├── LbQueryWrap.java					# 对mybatis-plus提供的LambdaQueryWrapper增强
                │   │   │   └── QueryWrap.java						# 对mybatis-plus提供的QueryWrapper增强
                │   │   └── update
                │   │       └── LbUpdateWrap.java					# 对mybatis-plus提供的LambdaUpdateWrapper增强
                │   ├── handlers
                │   │   └── MybatisEnumTypeHandler.java				# JavaBean字段枚举类型的转换器
                │   └── typehandler
                │       ├── BaseLikeTypeHandler.java				# 模糊查询类型处理器									
                │       ├── FullLikeTypeHandler.java
                │       ├── LeftLikeTypeHandler.java
                │       └── RightLikeTypeHandler.java
                ├── p6spy
                │   └── TenantP6SpyLogger.java						# 控制如何打印 p6spy SQL语句 
                ├── plugins
                │   ├── AcuityTenantLineInnerInterceptor.java		# DATASOURCE_COLUMN模式 自动拼接SQL拦截器
                │   ├── TenantLineAnnotationRegister.java
                │   └── TenantLineHelper.java
                └── properties										# 配置类
                    ├── DatabaseProperties.java
                    ├── IdType.java
                    └── MultiTenantType.java
└── top
    └── acuity
        └── commons
            └── database
                ├── config
                │   ├── BaseMybatisConfiguration.java               # 全局Mybatias公共配置
                │   └── AcuityMetaObjectHandler.java			    # 元对象字段(id、created_by等字段)填充控制器
                ├── injector
                │   ├── AcuitySqlInjector.java						# SQL 注入器(增强SuperMapper中全局接口)
                │   └── method
                │       └── UpdateAllById.java						# 具体注入的方法
                ├── mybatis
                │   ├── WriteInterceptor.java						# 禁止修改、删除重要数据拦截器
                │   ├── conditions
                │   │   ├── Wraps.java								# 对mybatis-plus提供的Wrappers增强
                │   │   ├── query
                │   │   │   ├── LbQueryWrap.java					# 对mybatis-plus提供的LambdaQueryWrapper增强
                │   │   │   └── QueryWrap.java						# 对mybatis-plus提供的QueryWrapper增强
                │   │   └── update
                │   │       └── LbUpdateWrap.java					# 对mybatis-plus提供的LambdaUpdateWrapper增强
                │   ├── handlers
                │   │   └── MybatisEnumTypeHandler.java				# JavaBean字段枚举类型的转换器
                │   └── typehandler
                │       ├── BaseLikeTypeHandler.java				# 模糊查询类型处理器									
                │       ├── FullLikeTypeHandler.java
                │       ├── LeftLikeTypeHandler.java
                │       └── RightLikeTypeHandler.java
                ├── p6spy
                │   └── TenantP6SpyLogger.java						# 控制如何打印 p6spy SQL语句 
                ├── plugins
                │   ├── AcuityTenantLineInnerInterceptor.java		# DATASOURCE_COLUMN模式 自动拼接SQL拦截器
                │   ├── TenantLineAnnotationRegister.java
                │   └── TenantLineHelper.java
                └── properties										# 配置类
                    ├── DatabaseProperties.java
                    ├── IdType.java
                    └── MultiTenantType.java

配置文件解释

yaml
acuity:
  database: # 字段介绍参考 DatabaseProperties
    # COLUMN模式中隔离 租户 的列名  oracle数据库initDatabasePrefix不能超过11个字符
    initDatabasePrefix:
      - acuity_base  
    # COLUMN模式自动拼接租户条件的字段  
    tenantIdColumn: 'created_org_id'      
    # 数据源模式
    multiTenantType: DATASOURCE_COLUMN    
    # 是否不允许写入数据  WriteInterceptor
    isNotWrite: false
    # 是否启用数据权限
    isDataScope: true
    # 是否启用  sql性能规范插件
    isBlockAttack: false
    # 是否启用  sql性能规范插件
    isIllegalSql: false
    # 是否启用分布式事务
    isSeata: false
    # 生产环境请设置p6spy = false
    p6spy: true
    # 单页分页条数限制
    maxLimit: -1
    # 溢出总页数后是否进行处理
    overflow: true
    # 生成 countSql 优化掉 join, 现在只支持 left join
    optimizeJoin: true
    # id生成策略    # 可选值 HU_TOOL、DEFAULT、CACHE
    id-type: CACHE
    # id生成策略 = HU_TOOL时,配置项。参数说明参考:https://hutool.cn/docs/#/core/%E5%B7%A5%E5%85%B7%E7%B1%BB/%E5%94%AF%E4%B8%80ID%E5%B7%A5%E5%85%B7-IdUtil?id=snowflake
    hutoolId:
    	# 终端ID (0-31)      单机配置0 即可。 集群部署,根据情况每个实例自增即可。
      workerId: 0
      # 数据中心ID (0-31)   单机配置0 即可。 集群部署,根据情况每个实例自增即可。
      dataCenterId: 0
    # id生成策略 = DEFAULT时,配置项。参数说明参考:https://github.com/baidu/uid-generator
    default-id:   
      time-bits: 31
      worker-bits: 22
      seq-bits: 10
    # id生成策略 = CACHE时,配置项。
    cache-id:
    	# 当前时间,相对于时间基点"${epochStr}"的增量值,单位:秒,
      time-bits: 31
      # 机器id
      worker-bits: 22
      # 每秒下的并发序列,13 bits可支持每秒8192个并发,即2^13个并发
      seq-bits: 10
      # 客户历元,单位为秒。可以改成你的项目开始开始的时间
      epochStr: '2020-09-15'
      # RingBuffer size扩容参数, 可提高UID生成的吞吐量.
      boost-power: 3              
      # 指定何时向RingBuffer中填充UID, 取值为百分比(0, 100), 默认为50
      padding-factor: 50
acuity:
  database: # 字段介绍参考 DatabaseProperties
    # COLUMN模式中隔离 租户 的列名  oracle数据库initDatabasePrefix不能超过11个字符
    initDatabasePrefix:
      - acuity_base  
    # COLUMN模式自动拼接租户条件的字段  
    tenantIdColumn: 'created_org_id'      
    # 数据源模式
    multiTenantType: DATASOURCE_COLUMN    
    # 是否不允许写入数据  WriteInterceptor
    isNotWrite: false
    # 是否启用数据权限
    isDataScope: true
    # 是否启用  sql性能规范插件
    isBlockAttack: false
    # 是否启用  sql性能规范插件
    isIllegalSql: false
    # 是否启用分布式事务
    isSeata: false
    # 生产环境请设置p6spy = false
    p6spy: true
    # 单页分页条数限制
    maxLimit: -1
    # 溢出总页数后是否进行处理
    overflow: true
    # 生成 countSql 优化掉 join, 现在只支持 left join
    optimizeJoin: true
    # id生成策略    # 可选值 HU_TOOL、DEFAULT、CACHE
    id-type: CACHE
    # id生成策略 = HU_TOOL时,配置项。参数说明参考:https://hutool.cn/docs/#/core/%E5%B7%A5%E5%85%B7%E7%B1%BB/%E5%94%AF%E4%B8%80ID%E5%B7%A5%E5%85%B7-IdUtil?id=snowflake
    hutoolId:
    	# 终端ID (0-31)      单机配置0 即可。 集群部署,根据情况每个实例自增即可。
      workerId: 0
      # 数据中心ID (0-31)   单机配置0 即可。 集群部署,根据情况每个实例自增即可。
      dataCenterId: 0
    # id生成策略 = DEFAULT时,配置项。参数说明参考:https://github.com/baidu/uid-generator
    default-id:   
      time-bits: 31
      worker-bits: 22
      seq-bits: 10
    # id生成策略 = CACHE时,配置项。
    cache-id:
    	# 当前时间,相对于时间基点"${epochStr}"的增量值,单位:秒,
      time-bits: 31
      # 机器id
      worker-bits: 22
      # 每秒下的并发序列,13 bits可支持每秒8192个并发,即2^13个并发
      seq-bits: 10
      # 客户历元,单位为秒。可以改成你的项目开始开始的时间
      epochStr: '2020-09-15'
      # RingBuffer size扩容参数, 可提高UID生成的吞吐量.
      boost-power: 3              
      # 指定何时向RingBuffer中填充UID, 取值为百分比(0, 100), 默认为50
      padding-factor: 50

AcuityMetaObjectHandler :

处理调用Mapper相关接口操作是数据库时, id、createTime、createdBy、updateTime、updatedBy等字段数据设置.

MyBatis Plus 元数据处理类,用于自动 注入 id, createdTime, updatedTime, createdBy, updatedBy 等字段

处理逻辑: 实体参数中, 上述字段值为空时, 才会生成id或者日期对其赋值.

注入其他字段

新增注解

在需要自动注入参数的字段添加@TableField(fill = FieldFill.INSERT_UPDATE)

java
@TableField(value = "org_id", fill = FieldFill.INSERT_UPDATE)
protected Long orgId
@TableField(value = "org_id", fill = FieldFill.INSERT_UPDATE)
protected Long orgId
编写注入代码
java
public class AcuityMetaObjectHandler implements MetaObjectHandler {
 	  @Override
  	public void insertFill(MetaObject metaObject) {
      // 执行 insert 语句时,自动注入参数
      this.setFieldValByName("orgId", 1234L, metaObject);
    } 
  
    @Override
    public void updateFill(MetaObject metaObject) {
        // 执行 update 语句时,自动注入参数
        this.setFieldValByName("orgId", 1234L, metaObject);
    }
}
public class AcuityMetaObjectHandler implements MetaObjectHandler {
 	  @Override
  	public void insertFill(MetaObject metaObject) {
      // 执行 insert 语句时,自动注入参数
      this.setFieldValByName("orgId", 1234L, metaObject);
    } 
  
    @Override
    public void updateFill(MetaObject metaObject) {
        // 执行 update 语句时,自动注入参数
        this.setFieldValByName("orgId", 1234L, metaObject);
    }
}

判断逻辑

  • insert 方法,自动填充 id, createdTime, updatedTime, createdBy, updatedBy 字段,字段为空时自动生成,不为空时使用原始值。
  • update 方法,自动填充 id, updatedTime, updatedBy 字段,字段为空时自动生成,不为空时使用原始值。

注入值

[提示]

mybatis-plus 的id字段 INPUT 类型的赋值方式不受 @TableId(type = IdType.INPUT) 决定,若你的实体类只有id字段,没有createdBy、createdTime等字段,AcuityMetaObjectHandler类的代码不会执行,请参考mybaits-plus官方open in new window寻找解决方案。

id

created_time(字段为空时,赋值为LocalDateTime.now(),不为空时使用原始值。)

created_by (字段为空时,赋值为ContextUtil.getUserId(),不为空时使用原始值。)

updated_time(字段为空时,赋值为LocalDateTime.now(),不为空时使用原始值。)

updated_by(字段为空时,赋值为ContextUtil.getUserId(),不为空时使用原始值。)

java
@Slf4j
public class AcuityMetaObjectHandler implements MetaObjectHandler {
  	private void fillId(MetaObject metaObject) {
        if (uidGenerator == null) {
            // 这里使用SpringUtils的方式"异步"获取对象,防止启动时,报循环注入的错
            uidGenerator = SpringUtils.getBean(UidGenerator.class);
        }
        // 根据 Acuity.database.id-type 决定实现类
        Long id = uidGenerator.getUid();

        //1. 继承了SuperEntity 若 ID 中有值,就不设置
        if (metaObject.getOriginalObject() instanceof SuperEntity) {
            Object oldId = ((SuperEntity) metaObject.getOriginalObject()).getId();
            if (oldId != null) {
                return;
            }
            // 判断id字段的类型是字符串还是Long
            Object idVal = StrPool.STRING_TYPE_NAME.equals(metaObject.getGetterType(SuperEntity.ID_FIELD).getName()) ? String.valueOf(id) : id;
            this.setFieldValByName(SuperEntity.ID_FIELD, idVal, metaObject);
            return;
        }

        // 2. 没有继承SuperEntity, 但主键的字段名为:  id
        if (metaObject.hasGetter(SuperEntity.ID_FIELD)) {
            Object oldId = metaObject.getValue(SuperEntity.ID_FIELD);
            if (oldId != null) {
                return;
            }

            Object idVal = StrPool.STRING_TYPE_NAME.equals(metaObject.getGetterType(SuperEntity.ID_FIELD).getName()) ? String.valueOf(id) : id;
            this.setFieldValByName(SuperEntity.ID_FIELD, idVal, metaObject);
            return;
        }

        // 3. 实体没有继承 Entity 和 SuperEntity,且 主键名为其他字段
        TableInfo tableInfo = TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
        if (tableInfo == null) {
            return;
        }
        // 主键类型
        Class<?> keyType = tableInfo.getKeyType();
        if (keyType == null) {
            return;
        }
        // id 字段名
        String keyProperty = tableInfo.getKeyProperty();
        Object oldId = metaObject.getValue(keyProperty);
        if (oldId != null) {
            return;
        }

        // 反射得到 主键的值
        Field idField = ReflectUtil.getField(metaObject.getOriginalObject().getClass(), keyProperty);
        Object fieldValue = ReflectUtil.getFieldValue(metaObject.getOriginalObject(), idField);
        // 若 ID 中有值,就不设置
        if (ObjectUtil.isNotEmpty(fieldValue)) {
            return;
        }
        Object idVal = keyType.getName().equalsIgnoreCase(StrPool.STRING_TYPE_NAME) ? String.valueOf(id) : id;
        this.setFieldValByName(keyProperty, idVal, metaObject);
    }
}
@Slf4j
public class AcuityMetaObjectHandler implements MetaObjectHandler {
  	private void fillId(MetaObject metaObject) {
        if (uidGenerator == null) {
            // 这里使用SpringUtils的方式"异步"获取对象,防止启动时,报循环注入的错
            uidGenerator = SpringUtils.getBean(UidGenerator.class);
        }
        // 根据 Acuity.database.id-type 决定实现类
        Long id = uidGenerator.getUid();

        //1. 继承了SuperEntity 若 ID 中有值,就不设置
        if (metaObject.getOriginalObject() instanceof SuperEntity) {
            Object oldId = ((SuperEntity) metaObject.getOriginalObject()).getId();
            if (oldId != null) {
                return;
            }
            // 判断id字段的类型是字符串还是Long
            Object idVal = StrPool.STRING_TYPE_NAME.equals(metaObject.getGetterType(SuperEntity.ID_FIELD).getName()) ? String.valueOf(id) : id;
            this.setFieldValByName(SuperEntity.ID_FIELD, idVal, metaObject);
            return;
        }

        // 2. 没有继承SuperEntity, 但主键的字段名为:  id
        if (metaObject.hasGetter(SuperEntity.ID_FIELD)) {
            Object oldId = metaObject.getValue(SuperEntity.ID_FIELD);
            if (oldId != null) {
                return;
            }

            Object idVal = StrPool.STRING_TYPE_NAME.equals(metaObject.getGetterType(SuperEntity.ID_FIELD).getName()) ? String.valueOf(id) : id;
            this.setFieldValByName(SuperEntity.ID_FIELD, idVal, metaObject);
            return;
        }

        // 3. 实体没有继承 Entity 和 SuperEntity,且 主键名为其他字段
        TableInfo tableInfo = TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
        if (tableInfo == null) {
            return;
        }
        // 主键类型
        Class<?> keyType = tableInfo.getKeyType();
        if (keyType == null) {
            return;
        }
        // id 字段名
        String keyProperty = tableInfo.getKeyProperty();
        Object oldId = metaObject.getValue(keyProperty);
        if (oldId != null) {
            return;
        }

        // 反射得到 主键的值
        Field idField = ReflectUtil.getField(metaObject.getOriginalObject().getClass(), keyProperty);
        Object fieldValue = ReflectUtil.getFieldValue(metaObject.getOriginalObject(), idField);
        // 若 ID 中有值,就不设置
        if (ObjectUtil.isNotEmpty(fieldValue)) {
            return;
        }
        Object idVal = keyType.getName().equalsIgnoreCase(StrPool.STRING_TYPE_NAME) ? String.valueOf(id) : id;
        this.setFieldValByName(keyProperty, idVal, metaObject);
    }
}
java
public class SuperEntity<T> implements Serializable {
  	@TableId(value = "id", type = IdType.INPUT)
    protected T id;
}
public class SuperEntity<T> implements Serializable {
  	@TableId(value = "id", type = IdType.INPUT)
    protected T id;
}
java
public abstract class BaseMybatisConfiguration {
		/**
     * Acuity.database.id-type = DEFAULT 或 Acuity.database.id-type 未设置时启用。
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = DatabaseProperties.PREFIX, name = "id-type", havingValue = "DEFAULT", matchIfMissing = true)
    public UidGenerator getDefaultUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
        DefaultUidGenerator uidGenerator = new DefaultUidGenerator();
        BeanUtil.copyProperties(databaseProperties.getDefaultId(), uidGenerator);
        uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
        return uidGenerator;
    }
    /**
     * Acuity.database.id-type = CACHE 时启用。
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = DatabaseProperties.PREFIX, name = "id-type", havingValue = "CACHE")
    public UidGenerator getCacheUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
        CachedUidGenerator uidGenerator = new CachedUidGenerator();
        DatabaseProperties.CacheId cacheId = databaseProperties.getCacheId();
        BeanUtil.copyProperties(cacheId, uidGenerator);
        if (cacheId.getRejectedPutBufferHandlerClass() != null) {
            RejectedPutBufferHandler rejectedPutBufferHandler = ReflectUtil.newInstance(cacheId.getRejectedPutBufferHandlerClass());
            uidGenerator.setRejectedPutBufferHandler(rejectedPutBufferHandler);
        }
        if (cacheId.getRejectedTakeBufferHandlerClass() != null) {
            RejectedTakeBufferHandler rejectedTakeBufferHandler = ReflectUtil.newInstance(cacheId.getRejectedTakeBufferHandlerClass());
            uidGenerator.setRejectedTakeBufferHandler(rejectedTakeBufferHandler);
        }
        uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
        return uidGenerator;
    }
    /**
     * Acuity.database.id-type = HU_TOOL 时启用。
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = DatabaseProperties.PREFIX, name = "id-type", havingValue = "HU_TOOL")
    public UidGenerator getHuToolUidGenerator() {
        DatabaseProperties.HutoolId id = databaseProperties.getHutoolId();
        return new HuToolUidGenerator(id.getWorkerId(), id.getDataCenterId());
    }
}
public abstract class BaseMybatisConfiguration {
		/**
     * Acuity.database.id-type = DEFAULT 或 Acuity.database.id-type 未设置时启用。
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = DatabaseProperties.PREFIX, name = "id-type", havingValue = "DEFAULT", matchIfMissing = true)
    public UidGenerator getDefaultUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
        DefaultUidGenerator uidGenerator = new DefaultUidGenerator();
        BeanUtil.copyProperties(databaseProperties.getDefaultId(), uidGenerator);
        uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
        return uidGenerator;
    }
    /**
     * Acuity.database.id-type = CACHE 时启用。
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = DatabaseProperties.PREFIX, name = "id-type", havingValue = "CACHE")
    public UidGenerator getCacheUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
        CachedUidGenerator uidGenerator = new CachedUidGenerator();
        DatabaseProperties.CacheId cacheId = databaseProperties.getCacheId();
        BeanUtil.copyProperties(cacheId, uidGenerator);
        if (cacheId.getRejectedPutBufferHandlerClass() != null) {
            RejectedPutBufferHandler rejectedPutBufferHandler = ReflectUtil.newInstance(cacheId.getRejectedPutBufferHandlerClass());
            uidGenerator.setRejectedPutBufferHandler(rejectedPutBufferHandler);
        }
        if (cacheId.getRejectedTakeBufferHandlerClass() != null) {
            RejectedTakeBufferHandler rejectedTakeBufferHandler = ReflectUtil.newInstance(cacheId.getRejectedTakeBufferHandlerClass());
            uidGenerator.setRejectedTakeBufferHandler(rejectedTakeBufferHandler);
        }
        uidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
        return uidGenerator;
    }
    /**
     * Acuity.database.id-type = HU_TOOL 时启用。
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = DatabaseProperties.PREFIX, name = "id-type", havingValue = "HU_TOOL")
    public UidGenerator getHuToolUidGenerator() {
        DatabaseProperties.HutoolId id = databaseProperties.getHutoolId();
        return new HuToolUidGenerator(id.getWorkerId(), id.getDataCenterId());
    }
}

SuperMapper增强

  • AcuitySqlInjector

    SuperMapper的自定义sql 注入器

  • UpdateAllById

    增强Mapper功能,使得SuperMapper拥有修改所有字段等方法

Wraps: 增强MybatisPlus的Wrappers类

  • LbqWrapper
java
* 相比 LambdaQueryWrapper 的增强如下:
* 1new QueryWrapper(T entity)时, 对entity 中的string字段 %和_ 符号进行转义,便于模糊查询
* 2new QueryWrapper(T entity)时, 对entity 中 RemoteData 类型的字段 值为null或者 key为null或者""时,忽略拼接成查询条件
* 3,对nested、eq、ne、gt、ge、lt、le、in、*like*、 等方法 进行条件判断,null"" 字段不加入查询
* 4,对*like*相关方法的参数 %和_ 符号进行转义,便于模糊查询
* 5,增加 leFooter 方法, 将日期参数值,强制转换成当天 235959
* 6,增加 geHeader 方法, 将日期参数值,强制转换成当天 000000
* 相比 LambdaQueryWrapper 的增强如下:
* 1new QueryWrapper(T entity)时, 对entity 中的string字段 %和_ 符号进行转义,便于模糊查询
* 2new QueryWrapper(T entity)时, 对entity 中 RemoteData 类型的字段 值为null或者 key为null或者""时,忽略拼接成查询条件
* 3,对nested、eq、ne、gt、ge、lt、le、in、*like*、 等方法 进行条件判断,null"" 字段不加入查询
* 4,对*like*相关方法的参数 %和_ 符号进行转义,便于模糊查询
* 5,增加 leFooter 方法, 将日期参数值,强制转换成当天 235959
* 6,增加 geHeader 方法, 将日期参数值,强制转换成当天 000000
  • QueryWrap
java
* 相比 QueryWrapper 的增强如下:
* 1new QueryWrapper(T entity)时, 对entity 中的string字段 %和_ 符号进行转义,便于模糊查询
* 2new QueryWrapper(T entity)时, 对entity 中 RemoteData 类型的字段 值为null或者 key为null或者""时,忽略拼接成查询条件
* 3,对nested、eq、ne、gt、ge、lt、le、in、*like*、 等方法 进行条件判断,null"" 字段不加入查询
* 4,对*like*相关方法的参数 %和_ 符号进行转义,便于模糊查询
* 5,增加 leFooter 方法, 将日期参数值,强制转换成当天 235959
* 6,增加 geHeader 方法, 将日期参数值,强制转换成当天 000000
* 相比 QueryWrapper 的增强如下:
* 1new QueryWrapper(T entity)时, 对entity 中的string字段 %和_ 符号进行转义,便于模糊查询
* 2new QueryWrapper(T entity)时, 对entity 中 RemoteData 类型的字段 值为null或者 key为null或者""时,忽略拼接成查询条件
* 3,对nested、eq、ne、gt、ge、lt、le、in、*like*、 等方法 进行条件判断,null"" 字段不加入查询
* 4,对*like*相关方法的参数 %和_ 符号进行转义,便于模糊查询
* 5,增加 leFooter 方法, 将日期参数值,强制转换成当天 235959
* 6,增加 geHeader 方法, 将日期参数值,强制转换成当天 000000
  • LbuWrapper
java
1, 对nested、eq、ne、gt、ge、lt、le、in、*like*、 等方法 进行条件判断,null"" 字段不加入查询
2, 对*like*相关方法的参数 %和_ 符号进行转义,便于模糊查询
1, 对nested、eq、ne、gt、ge、lt、le、in、*like*、 等方法 进行条件判断,null"" 字段不加入查询
2, 对*like*相关方法的参数 %和_ 符号进行转义,便于模糊查询
java
BaseEmployee entity = new BaseEmployee();
entity.setRealName("ls");
LbQueryWrap<BaseEmployee> wrap1 = Wraps.<BaseEmployee>lbQ(entity);

LbQueryWrap<BaseEmployee> wrap2 = Wraps.<BaseEmployee>lbQ().like(BaseEmployee::getRealName, "zs").eq(BaseEmployee::getId, null)
        .in(BaseEmployee::getUserId, Collections.emptyList());

// 模糊查询 realName 字段为  zs_1  和  ls% 的
LbQueryWrap<BaseEmployee> wrap3 = Wraps.<BaseEmployee>lbQ().like(BaseEmployee::getRealName, "zs_1").like(BaseEmployee::getRealName, "ls%");

LbQueryWrap<BaseEmployee> wrap4 =  Wraps.<BaseEmployee>lbQ().leFooter(BaseEmployee::getCreatedTime, LocalDateTime.now()).geHeader(BaseEmployee::getCreatedTime, LocalDateTime.now());
BaseEmployee entity = new BaseEmployee();
entity.setRealName("ls");
LbQueryWrap<BaseEmployee> wrap1 = Wraps.<BaseEmployee>lbQ(entity);

LbQueryWrap<BaseEmployee> wrap2 = Wraps.<BaseEmployee>lbQ().like(BaseEmployee::getRealName, "zs").eq(BaseEmployee::getId, null)
        .in(BaseEmployee::getUserId, Collections.emptyList());

// 模糊查询 realName 字段为  zs_1  和  ls% 的
LbQueryWrap<BaseEmployee> wrap3 = Wraps.<BaseEmployee>lbQ().like(BaseEmployee::getRealName, "zs_1").like(BaseEmployee::getRealName, "ls%");

LbQueryWrap<BaseEmployee> wrap4 =  Wraps.<BaseEmployee>lbQ().leFooter(BaseEmployee::getCreatedTime, LocalDateTime.now()).geHeader(BaseEmployee::getCreatedTime, LocalDateTime.now());
sql
-- wrap1
select * from base_employee where (real_name LIKE '%ls%')

-- wrap2 空参数不拼接到sql中
select * from base_employee where (real_name LIKE '%zs%')

-- wrap3 特殊字符自动转义: _ 和 % 转义为  \_ 和 \%
select * from base_employee where (real_name LIKE '%zs\_1%' AND real_name LIKE '%ls\%')

-- wrap4 日期字段转换
select * from base_employee where (created_time <= '2023-04-07 23:59:59' AND created_time >= '2023-04-07 00:00:00')
-- wrap1
select * from base_employee where (real_name LIKE '%ls%')

-- wrap2 空参数不拼接到sql中
select * from base_employee where (real_name LIKE '%zs%')

-- wrap3 特殊字符自动转义: _ 和 % 转义为  \_ 和 \%
select * from base_employee where (real_name LIKE '%zs\_1%' AND real_name LIKE '%ls\%')

-- wrap4 日期字段转换
select * from base_employee where (created_time <= '2023-04-07 23:59:59' AND created_time >= '2023-04-07 00:00:00')

TypeHandler

自定义模糊查询处理器 , 在xml中想要使用like 关键字模糊查询, 无需自己拼接%

  • FullLikeTypeHandler
  • LeftLikeTypeHandler
  • RightLikeTypeHandler
xml
<select id="get">
select * from user where 1=1
and name like #{name, typeHandler=fullLike} 
and code like #{code, typeHandler=leftLike}  
and describe like #{describe, typeHandler=rightLike}  
</select>
<select id="get">
select * from user where 1=1
and name like #{name, typeHandler=fullLike} 
and code like #{code, typeHandler=leftLike}  
and describe like #{describe, typeHandler=rightLike}  
</select>
sql
select * from user where 1=1
and name like '%name%'
and code like '%code'
and describe like 'describe%'
select * from user where 1=1
and name like '%name%'
and code like '%code'
and describe like 'describe%'
sql
and name like #{name, typeHandler=fullLike}   =>  and name like '%name%'
and code like #{code, typeHandler=leftLike}  =>  and code like '%code'
and describe like #{describe, typeHandler=rightLike}  =>  and describe like 'describe%'
and name like #{name, typeHandler=fullLike}   =>  and name like '%name%'
and code like #{code, typeHandler=leftLike}  =>  and code like '%code'
and describe like #{describe, typeHandler=rightLike}  =>  and describe like 'describe%'
  • MybatisEnumTypeHandler 自定义枚举属性转换器。若枚举中有 value 字段,存取数据库时,按照value字段的值来匹配;若没有value字段,存取数据库时,按照name()的值来匹配。
java
public enum AgeEnum implements IEnum<Integer> {
    ONE(1, "一岁"),
    TWO(2, "二岁"),
    THREE(3, "三岁");

  	// 映射到数据库中的值  实际存取的值为 1、2、3 ;若没有value字段,且没有 getValue(), 实际存取的值为ONE、TWO、THREE
    private int value;
    private String desc;

    @Override
    public Integer getValue() {
        return this.value;
    }
}
public enum AgeEnum implements IEnum<Integer> {
    ONE(1, "一岁"),
    TWO(2, "二岁"),
    THREE(3, "三岁");

  	// 映射到数据库中的值  实际存取的值为 1、2、3 ;若没有value字段,且没有 getValue(), 实际存取的值为ONE、TWO、THREE
    private int value;
    private String desc;

    @Override
    public Integer getValue() {
        return this.value;
    }
}

TenantP6SpyLogger :

p6spy SQL日志打印类. 可以自己调整该类 实现dev环境, 控制台打印的红色SQL日志样式.

properties
#日志输出到控制台  可以修改为自己的实现类
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
#日志输出到控制台  可以修改为自己的实现类
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
java
public class StdoutLogger extends com.p6spy.engine.spy.appender.StdoutLogger {

    @Override
    public void logText(String text) {
        // 打印红色 SQL 日志
        System.err.println(text);
    }
}
public class StdoutLogger extends com.p6spy.engine.spy.appender.StdoutLogger {

    @Override
    public void logText(String text) {
        // 打印红色 SQL 日志
        System.err.println(text);
    }
}

AcuityTenantLineInnerInterceptor

说明

DATASOURCE_COLUMN模式专用的 小租户SQL处理拦截器,自动拦截SQL语句,并拼接小租户的条件。

java
public class MybatisAutoConfiguration extends BaseMybatisConfiguration {
    public MybatisAutoConfiguration(DatabaseProperties databaseProperties) {
        super(databaseProperties);
    }

    @Override
    protected List<InnerInterceptor> getPaginationBeforeInnerInterceptor() {
        List<InnerInterceptor> list = new ArrayList<>();
        if (MultiTenantType.DATASOURCE_COLUMN.eq(databaseProperties.getMultiTenantType())) {
            log.info("检查到配置了:{}模式,已加载 column 部分插件", databaseProperties.getMultiTenantType());
            // COLUMN 模式 多租户插件
            acuityTenantLineInnerInterceptor tli = new acuityTenantLineInnerInterceptor();
            tli.setTenantLineHandler(new TenantLineHandler() {
                @Override
                public String getTenantIdColumn() {
                    return databaseProperties.getTenantIdColumn();
                }

                @Override
                public boolean ignoreTable(String tableName) {
                    // 这里可以自己在ContextUtil 中加入一个 isIgnore、setIgnore、clearIgnore 方法,动态控制是否需要排除
//                    if (ContextUtil.isIgnore()) {
//                    ContextUtil.clearIgnore();
//                        return true;
//                    }

                    boolean ignoreTable = databaseProperties.getIgnoreTable() != null && databaseProperties.getIgnoreTable().contains(tableName);

                    boolean ignoreTablePrefix = databaseProperties.getIgnoreTablePrefix() != null &&
                            databaseProperties.getIgnoreTablePrefix().stream().anyMatch(prefix -> tableName.startsWith(prefix));
                    return ignoreTable || ignoreTablePrefix;
                }

                @Override
                public Expression getTenantId() {
                    return new LongValue(ContextUtil.getCurrentCompanyId());
                }
            });
          
            list.add(tli);
        }

        return list;
    }
}
public class MybatisAutoConfiguration extends BaseMybatisConfiguration {
    public MybatisAutoConfiguration(DatabaseProperties databaseProperties) {
        super(databaseProperties);
    }

    @Override
    protected List<InnerInterceptor> getPaginationBeforeInnerInterceptor() {
        List<InnerInterceptor> list = new ArrayList<>();
        if (MultiTenantType.DATASOURCE_COLUMN.eq(databaseProperties.getMultiTenantType())) {
            log.info("检查到配置了:{}模式,已加载 column 部分插件", databaseProperties.getMultiTenantType());
            // COLUMN 模式 多租户插件
            acuityTenantLineInnerInterceptor tli = new acuityTenantLineInnerInterceptor();
            tli.setTenantLineHandler(new TenantLineHandler() {
                @Override
                public String getTenantIdColumn() {
                    return databaseProperties.getTenantIdColumn();
                }

                @Override
                public boolean ignoreTable(String tableName) {
                    // 这里可以自己在ContextUtil 中加入一个 isIgnore、setIgnore、clearIgnore 方法,动态控制是否需要排除
//                    if (ContextUtil.isIgnore()) {
//                    ContextUtil.clearIgnore();
//                        return true;
//                    }

                    boolean ignoreTable = databaseProperties.getIgnoreTable() != null && databaseProperties.getIgnoreTable().contains(tableName);

                    boolean ignoreTablePrefix = databaseProperties.getIgnoreTablePrefix() != null &&
                            databaseProperties.getIgnoreTablePrefix().stream().anyMatch(prefix -> tableName.startsWith(prefix));
                    return ignoreTable || ignoreTablePrefix;
                }

                @Override
                public Expression getTenantId() {
                    return new LongValue(ContextUtil.getCurrentCompanyId());
                }
            });
          
            list.add(tli);
        }

        return list;
    }
}
java
@Repository
@TenantLine  // 加了@TenantLine注解,该类下的所有方法都会被拦截器拦截,并自动拼接 小租户SQL条件
public interface BaseEmployeeTestMapper extends SuperMapper<BaseEmployee> {
    /**
     * @TenantLine(false) 表示该方法不被拦截
     */
    @TenantLine(false)
    @Select("select * from base_employee where id = #{id}")
    BaseEmployee get(Long id);

}
@Repository
@TenantLine  // 加了@TenantLine注解,该类下的所有方法都会被拦截器拦截,并自动拼接 小租户SQL条件
public interface BaseEmployeeTestMapper extends SuperMapper<BaseEmployee> {
    /**
     * @TenantLine(false) 表示该方法不被拦截
     */
    @TenantLine(false)
    @Select("select * from base_employee where id = #{id}")
    BaseEmployee get(Long id);

}

TenantLineInnerInterceptor

说明

mybatis-plus 提供的租户拦截器,在COLUMN模式中使用。

  1. TenantLineInnerInterceptor 会将默认将所有的Mapper查询都动态拼接上条件
  2. acuityTenantLineInnerInterceptor 则默认不会拼接
  3. TenantLineInnerInterceptor 可以通过在Mapper类上加 @InterceptorIgnore(tenantLine = "true") ,防止拦截器拼接条件
  4. acuityTenantLineInnerInterceptor 则需要在Mapper类上加 @TenantLine ,在会动态拼接条件
  5. 无其他区别,拼接SQL的代码完全一致

欢迎使用天源云Saas快速开发系统