# 功能说明
- 代码生成、自动实现基础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