# 功能说明

  • 代码生成、自动实现基础CRUD操作方法
  • 灵活的多数据源配置(多业务线、多租户、读写分离灵活组合)
  • 读写分离,强制读主库策略
  • 多租户模式:schema隔离、字段级隔离
  • 字段自动填充:ID、审计字段、租户字段自动填充
  • 自动缓存管理:所有查询方法结果自动缓存、自动更新,事务回滚缓存同步回滚机制
  • 数据权限:基于注解重写SQL实现自动数据权限处理,兼容各种复杂嵌套,join查询
  • 数据变更日志记录:通过执行SQL前后数据对比生成数据变更记录
  • 分表:支持各种复杂SQL分表
  • 分页组件
  • 敏感操作保护:无查询条件删除等敏感操作保护

# 使用说明

# 添加依赖

<dependency>
    <groupId>com.mendmix</groupId>
    <artifactId>mendmix-mybatis</artifactId>
    <version>[最新版本]</version>
</dependency>

# 初始化

# 数据源配置

以下是配置的一主二从的数据源配置

master.db.url=jdbc:mysql://localhost:3306/demo_db?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT&allowPublicKeyRetrieval=true
master.db.username=root
master.db.password=123456

#slave ....
slave1.db.url=jdbc:mysql://127.0.0.1:3306/demo_db_bak1?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT&allowPublicKeyRetrieval=true
slave1.db.username=root
slave1.db.password=123456
#
slave2.db.url=jdbc:mysql://localhost:3306/demo_db_bak2?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT&allowPublicKeyRetrieval=true
slave2.db.username=root
slave2.db.password=123456

对应的mybatis配置为:

mybatis.type-aliases-package=${mendmix.application.base-package}.dao.entity
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.mapper-package=${mendmix.application.base-package}.dao.mapper

以下完整模式的配置(group、tenant、master/slave任意组合)

group[order].tenant[1001].master.db.url=jdbc:mysql://localhost:3306/demo_db?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT&allowPublicKeyRetrieval=true
group[order].tenant[1001].master.db.username=root
group[order].tenant[1001].master.db.password=123456

#slave ....
group[order].tenant[1001].slave[0].db.url=jdbc:mysql://127.0.0.1:3306/demo_db?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT&allowPublicKeyRetrieval=true
group[order].tenant[1001].slave[0].db.username=root
group[order].tenant[1001].slave[0].db.password=123456
#
group[order].tenant[1001].slave[1].db.url=jdbc:mysql://127.0.0.1:3306/demo_db?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT&allowPublicKeyRetrieval=true
group[order].tenant[1001].slave[1].db.username=root
group[order].tenant[1001].slave[1].db.password=123456

对应的mybatis配置为:

order.mybatis.type-aliases-package=${mendmix.application.base-package}.dao.entity.order
order.mybatis.mapper-locations=classpath:mapper/order/*.xml
order.mybatis.mapper-package=${mendmix.application.base-package}.dao.mapper.order

# spring XML方式

只需要修改以下两处的配置内容 ,其他保持不变。

<!--数据源-->
<bean id="routeDataSource" class="com.mendmix.mybatis.datasource.MultiRouteDataSource">
   <constructor-arg index="0" value="default" />  
</bean>
<!--mybatis SqlSessionFactory-->
<bean id="aarouteSqlSessionFactory" class="com.mendmix.mybatis.spring.SqlSessionFactoryBean">
	    <!-- 如果包含多个数据源则需要指定groupName,默认为:default -->
	    <property name="groupName" value="default" />
		<property name="configLocation" value="classpath:mybatis-configuration.xml" />
		<property name="mapperLocations" value="classpath:mapper/*Mapper.xml" />
		<property name="typeAliasesPackage" value="com.mendmix.mybatis.test.entity" />
		<property name="dataSource" ref="routeDataSource" />
	</bean>

# springboot方式

# 添加依赖

<dependency>
    <groupId>com.mendmix</groupId>
    <artifactId>mendmix-springcloud-support</artifactId>
    <version>[最新版本]</version>
</dependency>

# 分页插件

以员工StaffEntity为例,定义Mapper接口如下:

public interface StaffEntityMapper extends BaseMapper<StaffEntity,Integer> {
	List<StaffEntity> findListByParam(StaffQueryParam param);
}

以上接口除了可以按正常方式调用,还可以通过以下方式分页方式查询

//不封装DTO
Page<StaffEntity> pageInfo = PageExecutor.pagination(new PageParams(1,10), new PageDataLoader<UserEntity>() {
    @Override
    public List<UserEntity> load() {
       return staffEntityMapper.findListByParam(param);
    }
});

//查询并转换成dto的方式
Page<Staff> page = PageExecutor.pagination(pageParams, new ConvertPageDataLoader<StaffEntity,Staff>() {
	@Override
	public List<StaffEntity> load() {
		return staffMapper.selectAll();
	}

	@Override
	public Staff convert(StaffEntity e) {
		return entityConvertDTO(e);
	}
});

# 读写分离

  • 添加slave数据源自动实现读写分离
  • 考虑到主从同步延迟问题,一些查询方法希望也通过主库查询,默认启用事务注解@Transactional的方法都强制通过主库查询,另外你也通过注解@UseMaster强制使用主库。

# 自动缓存

# 添加配置

mendmix.mybatis.cache.enabled=true
# 多组数据源
组名.mendmix.mybatis.cache.enabled=true

# 引入redis依赖

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
</dependency>

# 添加配置

spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456

# 注解说明

所有缓存配置只需要一个注解@Cache搞定,以下是注解选项说明

选项 必填 说明
expire 过期时间(秒),不指定则按默认过期规则
uniqueIndex 是否业务上唯一索引,如果为true则仅缓存引用关系
concurrency 是否允许并发查询,默认为:true。为false则在缓存未命中启动分布式锁确保只有一个请求去查询数据库
userScope 当前登录用户范围
evictOnMethods 默认同一Mapper下的CUD方法都会自动刷新同一实体关联的缓存,如果是关联查询可以配置evictOnMethods跨Mapper刷新缓存。
refKey 缓存引用,引用已存在的缓存

# 使用举例

public interface AccountEntityMapper extends BaseMapper<AccountEntity, Integer> {
    
    @Cache(uniqueIndex = true,expire = 7200)
    @Select("SELECT * FROM omp_account WHERE mobile=#{mobile} AND deleted=0 LIMIT 1")
    @ResultMap("BaseResultMap")
    AccountEntity findByMobile(String mobile);

    @Cache(evictOnMethods = {"SnsAccounyBindingEntityMapper.*"})
    @Select("SELECT a.*,b.union_id FROM omp_account a LEFT JOIN omp_account_weixin_binding b ON a.id = b.account_id WHERE a.enabled = 1 and b.open_id = #{openId} LIMIT 1 ")
    @ResultMap("BaseResultMap")
    AccountEntity findByWeixinOpenId(@Param("openId") String openId);
}

# CacheIgnore

如果开启缓存功能默认所有按主键查询都会自动缓存,如果希望某个实体不自动缓存,只需要在对应的mapper接口类加上注解@CacheIgnore即可

# 分表

# 添加配置

mendmix.mybatis.tableshard.enabled=true

# 定义分表策略

public class TestTableShardStrategy implements TableShardingStrategy {

	@Override
	public String buildShardingTableName(String tableName,InvocationVals invocation) {
		String tenantId = CurrentRuntimeContext.getTenantId();
		//实际表名
		return tableName + tenantId.substring(0, 2);
	}

}

# 指定分表对象

@Table(name = "staff")
@TableSharding(strategy = TestTableShardStrategy.class)
public class ExampleStaffEntity extends ExampleBaseEntity {
	//TODO
}

# 数据权限

# 添加配置

mendmix.mybatis.dataPermission.enabled=true
mendmix.mybatis.dataPermssion.allMatchMode.enabled=true
mendmix.mybatis.dataPermission.columns=city_id;product_type:productGroup
mendmix.mybatis.dataPermission.columns[mall_order]=category_id
#当前用户创建数据匹配用
mendmix.mybatis.createBy.columnName=created_by
#基于组织架构数据权限用
mendmix.mybatis.department.columnName=dept_id

# 查询上下文(在拦截器根据当前登录用户数据权限信息设置)

List<KeyValues> profiles = LoginContext.getDataProfiles();//这是假设的一个方法
if(profiles != null) {
	for (KeyValues keyValues : profiles) {
		MybatisRuntimeContext.addDataProfileMappings(keyValues.getKey(), keyValues.getValues().toArray(new String[0]));
	}
}

# 查询示例

假如当前用户的数据权限为:cityId("440"),areaId("440105","440106")

@Before
public void init(){
	MybatisRuntimeContext.addDataProfileMappings("cityId", "440");
	MybatisRuntimeContext.addDataProfileMappings("areaId", "440105","440106");
}

@Test
public void testQuery(){
   //
   userMapper.findByType(1);
   //
   userDetailMapper.findList();
}

不启用数据权限的情况以上测试的执行SQL如下:

SELECT * FROM t_users where type = 1 AND deleted = 0

SELECT d.* FROM t_user_details d JOIN t_users u ON u.id = d.user_id where u.deleted = 0

启用数据权限后执行SQL如下:

SELECT * FROM t_users where type = 1 AND deleted = 0 AND city_id = '440' AND area_id IN ("440105","440106")

SELECT d.* FROM t_user_details d JOIN t_users u ON u.id = d.user_id where u.deleted = 0 AND u.city_id = '440' AND u.area_id IN ("440105","440106")

# 跳过数据权限的方式

  • 开始事务的方法内部查询默认不启用数据权限
  • 添加注解@DataProfileIgnore的方法的查询方法不启用数据权限
  • 配置项mendmix.mybatis.dataProfile.excludeMapperIds配置的Mapper接口

# 其他配置

#开启字段级多租户
mendmix.mybatis.tenant.columnName=shop_id
#自动过滤软删除
mendmix.mybatis.softDelete.columnName=is_deleted
mendmix.mybatis.softDelete.falseValue=0
Last Updated: 6/14/2022, 9:51:10 PM