今天介绍一个MyBatis-Plus官方发布的神器:mybatis-mate为mp企业级模块,支持分库分表,数据审计、数据敏感词过滤(AC算法),字段加密,字典回写(数据绑定),数据权限,表结构自动生成SQL维护等,旨在更敏捷优雅处理数据。
1.主要功能
字典绑定
字段加密
数据脱敏
表结构动态维护
数据审计记录
数据范围(数据权限)
数据库分库分表、动态据源、读写分离、数--据库健康检查自动切换。
2.使用
2.1依赖导入
SpringBoot引入自动依赖注解包
depency
/groupId
artifactIdmybatis-mate-starter/artifactId
/version
/depency
注解(实体分包使用)
depency
/groupId
artifactIdmybatis-mate-annotation/artifactId
/version
/depency
2.2字段数据绑定(字典回写)
例如user_sex类型sex字典结果映射到sexText属性
@FieldDict(type="user_sex",target="sexText")
privateIntegersex;
privateStringsexText;
实现IDataDict接口提供字典数据源,注入到Spring容器即可。
@Component
publicclassDataDictimplementsIDataDict{
/**
*从数据库或缓存中获取
*/
privateMapString,StringSEX_MAP=newConcurrentHashMapString,String(){{
put("0","女");
put("1","男");
}};
@Override
publicStringgetNameByCode(FieldDictfieldDict,Stringcode){
("字段类型:"+()+",编码:"+code);
returnSEX_(code);
}
}
2.3字段加密
属性@FieldEncrypt注解即可加密存储,会自动解密查询结果,支持全局配置加密密钥算法,及注解密钥算法,可以实现IEncryptor注入自定义算法。
@FieldEncrypt(algorithm=)
privateStringpassword;
2.4字段脱敏
属性@FieldSensitive注解即可自动按照预设策略对源数据进行脱敏处理,默认SensitiveType内置9种常用脱敏策略。
例如:中文名、银行卡账号、手机号码等脱敏策略。也可以自定义策略如下:
@FieldSensitive(type="testStrategy")
privateStringusername;
@FieldSensitive(type=)
privateStringmobile;
自定义脱敏策略testStrategy添加到默认策略中注入Spring容器即可。
@Configuration
publicclassSensitiveStrategyConfig{
/**
*注入脱敏策略
*/
@Bean
publicISensitiveStrategysensitiveStrategy(){
//自定义testStrategy类型脱敏处理
returnnewSensitiveStrategy().addStrategy("testStrategy",t-t+"***test***");
}
}
例如:文章敏感词过滤
/**
*演示文章敏感词过滤
*/
@RestController
publicclassArticleController{
@Autowired
privateSensitiveWordsMappersensitiveWordsMapper;
//测试访问下面地址观察请求地址、界面返回数据及控制台(普通参数)
//无敏感词http://localhost:8080/info?content=tomsee=1age=18
//英文敏感词http://localhost:8080/info?content=my%20content%20is%20tomcatsee=1age=18
//汉字敏感词http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E5%94%90%E5%AE%8B%E5%85%AB%E5%A4%A7%E5%AE%B6see=1
//多个敏感词http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9see=1size=6
//插入一个字变成非敏感词http://localhost:8080/info?content=%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9see=1size=6
@GetMapping("/info")
publicStringinfo(Articlearticle)throwsException{
(article);
}
//添加一个敏感词然后再去观察是否生效http://localhost:8080/add
//观察【猫】这个词被过滤了http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9see=1size=6
//嵌套敏感词处理http://localhost:8080/info?content=%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9see=1size=6
//多层嵌套敏感词http://localhost:8080/info?content=%E7%8E%8B%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9see=1size=6
@GetMapping("/add")
publicStringadd()throwsException{
Longid=3L;
if(null==(id)){
("插入一个敏感词:"+(newSensitiveWords(id,"猫")));
//插入一个敏感词,刷新算法引擎敏感词
();
}
return"ok";
}
//测试访问下面地址观察控制台(请求json参数)
//idea执行resources目录文件测试
@PostMapping("/json")
publicStringjson(@RequestBodyArticlearticle)throwsException{
(article);
}
}
2.5DDL数据结构自动维护
解决升级表结构初始化,版本发布更新SQL维护问题,目前支持MySql、PostgreSQL。
@Component
publicclassPostgresDdlimplementsIDdl{
/**
*执行SQL脚本方式
*/
@Override
publicListStringgetSqlFiles(){
(
//内置包方式
"db/",
//文件绝对路径方式
"D:\\db\\"
);
}
}
不仅仅可以固定执行,也可以动态执行!!
(newStringReader("DELETEFROMuser;\n"+
"INSERTINTOuser(id,username,password,sex,email)VALUES\n"+
"(20,'Duo','123456',0,'Duo@');"));
它还支持多数据源执行!!!
@Component
publicclassMysqlDdlimplementsIDdl{
@Override
publicvoidsharding(ConsumerIDdlconsumer){
//多数据源指定,主库初始化从库自动同步
Stringgroup="mysql";
ShardingGroupPropertysgp=(group);
if(null!=sgp){
//主库
().forEach(key-{
(group+key);
(this);
});
//从库
().forEach(key-{
(group+key);
(this);
});
}
}
/**
*执行SQL脚本方式
*/
@Override
publicListStringgetSqlFiles(){
("db/");
}
}
2.6动态多数据源主从自由切换
@Sharding注解使数据源不限制随意使用切换,你可以在mapper层添加注解,按需求指哪打哪!!
@Mapper
@Sharding("mysql")
publicinterfaceUserMapperextsBaseMapperUser{
@Sharding("postgres")
LongselectByUsername(Stringusername);
}
你也可以自定义策略统一调兵遣将
@Component
publicclassMyShardingStrategyextsRandomShardingStrategy{
/**
*决定切换数据源key{@linkShardingDatasource}
*
*@paramgroup动态数据库组
*@paraminvocation{@linkInvocation}
*@paramsqlCommandType{@linkSqlCommandType}
*/
@Override
publicvoiddetermineDatasourceKey(Stringgroup,Invocationinvocation,SqlCommandTypesqlCommandType){
//数据源组group自定义选择即可,keys为数据源组内主从多节点,可随机选择或者自己控制
(group,sqlCommandType,keys-chooseKey(keys,invocation));
}
}
可以开启主从策略,当然也是可以开启健康检查!具体配置:
mybatis-mate:
sharding:
health:true默认选择数据源
datasource:
mysql:从库读写分离时候负责sql查询操作,主库master默认可以不写
postgres:
-key:node1#数据节点
2.7分布式事务日志打印
部分配置如下:
/**
*p
*性能分析拦截器,用于输出每条SQL语句及其执行时间
*/p
*/
@Slf4j
@Component
@Intercepts({@Signature(type=,method="query",args={,}),
@Signature(type=,method="update",args={}),
@Signature(type=,method="batch",args={})})
publicclassPerformanceInterceptorimplementsInterceptor{
/**
*SQL执行最大时长,超过自动停止运行,有助于发现问题。
*/
privatelongmaxTime=0;
/**
*SQL是否格式化
*/
privatebooleanformat=false;
/**
*是否写入日志文件br
*true写入日志文件,不阻断程序执行!br
*超过设定的最大执行时长异常提示!
*/
privatebooleanwriteInLog=false;
@Override
publicObjectintercept(Invocationinvocation)throwsThrowable{
Statementstatement;
ObjectfirstArg=()[0];
if((())){
statement=(Statement)(firstArg).getValue("");
}else{
statement=(Statement)firstArg;
}
MetaObjectstmtMetaObj=(statement);
try{
statement=(Statement)("");
}catch(Exceptione){
//donothing
}
if(("delegate")){//Hikari
try{
statement=(Statement)("delegate");
}catch(Exceptione){
}
}
StringoriginalSql=null;
if(originalSql==null){
originalSql=();
}
originalSql=("[\\s]+","");
intindex=indexOfSqlStart(originalSql);
if(index0){
originalSql=(index);
}
//计算执行SQL耗时
longstart=();
Objectresult=();
longtiming=()-start;
//格式化SQL打印执行结果
Objecttarget=(());
MetaObjectmetaObject=(target);
StringBuilderformatSql=newStringBuilder();
("Time:").app(timing);
("ms-ID:").app(());
("\nExecuteSQL:").app(sqlFormat(originalSql,format)).app("\n");
if(()){
if(()=1()){
(());
}else{
(());
}
}else{
(formatSql);
if(()=1()){
thrownewRuntimeException("TheSQLexecutiontimeistoolarge,pleaseoptimize!");
}
}
returnresult;
}
@Override
publicObjectplugin(Objecttarget){
if(targetinstanceofStatementHandler){
(target,this);
}
returntarget;
}
@Override
publicvoidsetProperties(Propertiesprop){
StringmaxTime=("maxTime");
Stringformat=("format");
if((maxTime)){
=(maxTime);
}
if((format)){
=(format);
}
}
publiclonggetMaxTime(){
returnmaxTime;
}
publicPerformanceInterceptorsetMaxTime(longmaxTime){
=maxTime;
returnthis;
}
publicbooleanisFormat(){
returnformat;
}
publicPerformanceInterceptorsetFormat(booleanformat){
=format;
returnthis;
}
publicbooleanisWriteInLog(){
returnwriteInLog;
}
publicPerformanceInterceptorsetWriteInLog(booleanwriteInLog){
=writeInLog;
returnthis;
}
publicMethodgetMethodRegular(Class?clazz,StringmethodName){
if((clazz)){
returnnull;
}
for(Methodmethod:()){
if(().equals(methodName)){
returnmethod;
}
}
returngetMethodRegular((),methodName);
}
/**
*获取sql语句开头部分
*
*@paramsql
*@return
*/
privateintindexOfSqlStart(Stringsql){
StringupperCaseSql=();
SetIntegerset=newHashSet();
(("SELECT"));
(("UPDATE"));
(("INSERT"));
(("DELETE"));
(-1);
if((set)){
return-1;
}
ListIntegerlist=newArrayList(set);
(list,Integer::compareTo);
(0);
}
privatefinalstaticSqlFormattersqlFormatter=newSqlFormatter();
/**
*格式sql
*
*@paramboundSql
*@paramformat
*@return
*/
publicstaticStringsqlFormat(StringboundSql,booleanformat){
if(format){
try{
(boundSql);
}catch(Exceptionignored){
}
}
returnboundSql;
}
}
使用:
@RestController
@AllArgsConstructor
publicclassTestController{
privateBuyServicebuyService;
//数据库test表t_order在事务一致情况无法插入数据,能够插入说明多数据源事务无效
//测试访问http://localhost:8080/test
//制造事务回滚http://localhost:8080/test?error=true也可通过修改表结构制造错误
//注释ShardingConfig注入dataSourceProvider可测试事务无效情况
@GetMapping("/test")
publicStringtest(Booleanerror){
(null!=errorerror);
}
}
2.8数据权限
//测试test类型数据权限范围,混合分页模式
@DataScope(type="test",value={
//关联表user别名u指定部门字段权限
@DataColumn(alias="u",name="department_id"),
//关联表user别名u指定手机号字段(自己判断处理)
@DataColumn(alias="u",name="mobile")
})
@Select("selectu.*fromuseru")
ListUserselectTestList(IPageUserpage,Longid,@Param("name")Stringusername);
模拟业务处理逻辑:
@Bean
publicIDataScopeProviderdataScopeProvider(){
returnnewAbstractDataScopeProvider(){
@Override
protectedvoidsetWhere(PlainSelectplainSelect,Object[]args,DataScopePropertydataScopeProperty){
//args中包含mapper方法的请求参数,需要使用可以自行获取
/*
//测试数据权限,最终执行SQL语句
SELECTu.*FROMuseruWHERE(_idIN('1','2','3','5'))
'%1533%'
*/
if("test".equals(())){
//业务test类型
ListDataColumnPropertydataColumns=();
for(DataColumnPropertydataColumn:dataColumns){
if("department_id".equals(())){
//追加部门字段IN条件,也可以是SQL语句
SetStringdeptIds=newHashSet();
("1");
("2");
("3");
("5");
ItemsListitemsList=newExpressionList(().map(StringValue::new).collect(()));
InExpressioninExpression=newInExpression(newColumn(()),itemsList);
if(null==()){
//不存在where条件
(newParenthesis(inExpression));
}else{
//存在where条件and处理
(newAndExpression((),inExpression));
}
}elseif("mobile".equals(())){
//支持一个自定义条件
LikeExpressionlikeExpression=newLikeExpression();
(newColumn(()));
(newStringValue("%1533%"));
(newAndExpression((),likeExpression));
}
}
}
}
};
}
最终执行SQL输出:
SELECTu.*FROMuseru
WHERE(_idIN('1','2','3','5'))
'%1533%'LIMIT1,10
目前仅有付费版本,了解更多mybatis-mate使用示例详见: