MP高级功能:
我们介绍以下七种高级功能:逻辑删除,自动填充,乐观锁插件,性能分析插件,多租户SQL解析器,动态表名SQL解析器,SQL注入器。
逻辑删除
1.逻辑删除简介
其实就是一种假删除的状态,修改数据库中的标记是否删除的字段,之后再数据库中仍然可以看到这条删除的记录
2.逻辑删除的实现
首先我们在application.yml中对mybatisplus的配置中设置逻辑删除和未删除的值:
mybatis-plus:
global-config:
db-config:
logic-not-delete-value:0
logic-delete-value:1
//这也是MP的默认值,是一个全局配置
*接着我们写MyBatisPlus的配置类:
@Configuration
public class MyBatisPlusConfiguration{
@Bean
public ISqllnjector sqllnjector(){
//在3.1.1版本开始,我们就不需要这一步配置了
return new LogicSqllnjector ();
}
}
在实体类的标记逻辑删除的变量上添加注解:
//这个注解中我们也可以传入逻辑生效或者未生效的值,是一个局部变量,只对当前实体生效
//一般情况下我们只设置全局的就可以
@TableLogic(delval="",value="")
private Integer deleted;
接下来我们进行测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test(){
@Autowired
private UserMapper userMapper;
@Test
public void deleteById(){
sout(userMapper.deleteById(87589347598123L));
}
}
注意:MP在查询或者修改操作时会自动把逻辑删除标识加入where条件中,剔除已经删除的记录。
3.查询中排除删除标识字段及注意事项
我们可以在实体类的成员变量中加入注解来删除查询结果中的标识字段:
@TableLogic()
@TableField(select=false)
private Integer deleted
注意事项:在你自定义的语句中,MP不会给你添加带删除标识的where查询条件
UserMapper中:
@Select("select*from user ${ew.customSqlSegment}")
List<User> mySelectList(@Param(Constant.WRAPPER)Wrapper<User> wrapper);
Test类中
@Test
public void mySelect(){
userMapper.mySelectList(Wrapper.<User>lambdaQuery().gt(User::getAge,25))
.forEach(System.out::println);
//会查询出全部年龄大于25的记录
//我们可以自己添加where条件
}
自动填充
1.自动填充简介
有时候我们需要为像新增时间,修改时间,新增人,修改人这样的字段去自动填充值,这时候我们就可以使用MP为我们提供的自动填充功能
2.自动填充实现
1)首先我们为实体类中需要填充的字段加上注解:
@TableField(fill=FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill=FieldFill.UPDATE)
private LocalDateTime updateTime;
2)建立填充处理器实现MP提供的源对象处理器接口
@Component
public class MyMetaObjectHandler implements MetaObjectHandler{
@Override
public void insertFill(MetaObject metaObject){
//"createTime"是实体类中的属性名,不是数据库中的字段名。
setInsertFieldValByName("createTime",LocalDateTime.now(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject){
//"updateTime"是实体类中的属性名,不是数据库中的字段名。
setupdateFieldValByName("updateTime",LocalDateTime.now(),metaObject);
}
}
3)进行测试
public class FillTest{
@Autoeired
private UserMapper userMapper;
@Test
public void insert(){
User user = new User();
user.setName("liumingchao");
user.setAge(29);
sout(userMapper.insert(user));
}
@Test
public void updateById(){
User user = new User();
user.setName("liumingchao");
user.setAge(29);
user.setId(123741273657818788L)
sout(userMapper.updateById(user));
}
}
tips:有时候我们会在插入的数据库中碰到乱码的情况,我们只要在配置文件的数据库url配置中加入charactorEncoding=UTF-8
3.自动填充的优化
有时候我们要填充的值需要进行一系列方法调用才能获取到值,这些方法会产生一定的开销,因此我们可以通过处理器实现类中的metaObject对象配置哪些实体需要自动填充
@Component
public class MyMetaObjectHandler implements MetaObjectHandler{
@Override
public void insertFill(MetaObject metaObject){
boolean hasSetter = metaObjec.hasSeter("createTime");
if(hasSetter){
setInsertFieldValByName("createTime",LocalDateTime.now(),metaObject);
}
}
}
我们也可以判断在某个实体的属性为null时才自动填充
@Override
public void updateFill(MetaObject metaObject){
Object val = getFieldValByName("updateTime",metaObject);
if(val==null){
setupdateFieldValByName("updateTime",LocalDateTime.now(),metaObject);
}
}
tips:如果你要自动填充的是新增人修改人,我们一般通过requestcontextholder对象的静态方法获取session中的user,再进行设置。
乐观锁插件
1.简介
当我们需要更新一条记录时,希望这条记录没有被别人更新过,是为了防止更新冲突
乐观锁的一种实现:1.取出记录时,获取当前version 2.更新时带上这个version 3.版本正确则更新成功,错误则失败
eg.
update user set name = '向南天',version = 3 where id = 413285719832718 and version = 2
乐观锁和悲观锁各有自己的应用场景,乐观锁适合写比较少的场景(多读场景),因为在多读场景即使有冲突也很少发生,这样会节省锁的开销,提高了系统的整体吞吐量,在多写的情况下,会经常产生冲突,就会导致上层应用不断进行重试,这样反倒会降低系统的性能,因此在这种场景下我们一般使用悲观锁。
2.功能实现
1)实现步骤
1.配置乐观锁插件
@Configuratin
public class MybatisPlusCongifuration{
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
2.在记录版本的字段上添加注解
@Version
private Integer version;
3.测试
public class OptTest{
@Test
public void updateById(){
int version = 1;
User user = new User();
user.setName("liumingchao");
user.setAge(30);
user.setId(123741273657818788L)
user.SetVersion(version)
sout(userMapper.updateById(user));
}
}
2)注意事项
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
性能分析插件
用来调sql语句和执行时间
1.性能分析实现
1)在配置类中添加性能分析插件
@Configuratin
public class MybatisPlusCongifuration{
@Bean
@Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor;
//设置输出格式
performanceInterceptor.setFormat(true);
//配置慢sql的时长,超过这个时间会报错
performanceInterceptor.setMaxTime(5L);
return new PerformanceInterceptor();
}
}
@Profile({“dev”,“test”})声明在开发环境和测试环境开启
2)右键,run as->run configuration->VM arguments设置-Dspring.profiles.active=dev才能看到效果
2.执行SQL分析打印
见MP官网
多租户SQL解析器
1.多租户概念介绍
多租户技术是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是指面向企业的用户)共用相同的系统或者程序,并且确保用户间数据的隔离性。多租户一般有三种数据隔离方案,第一种是独立数据库,一个租户一个数据库,用户数据隔离级别最高,它的优点是为不同的租户提供了独立的数据库,有助于数据模型的扩展设计,满足不同租户的独特需求,如果出现故障,恢复数据比较简单,缺点是增加了数据库的数量,随之增加了维护成本和开销。第二种是共享数据库独立schema,这种方案的优点是为安全性要求较高的租户提供了一定程度的逻辑数据隔离,它并不是完全的隔离,每个数据库可支持更高的租户数量。缺点是如果出现了故障,数据库恢复比较困难,因为恢复数据库将涉及其它租户的数据库。第三种方案是共享数据库,共享schema,共享数据表,在表中增加租户id字段,这是共享程度最高,隔离级别最低的模式。优点是维护成本和购置成本最低,也允许每个数据库支持的租户最多,缺点是隔离级别最低,安全性最低。需要在设计开发时增加对安全的开发量,数据备份和恢复最困难甚至需要逐条备份和还原
2.多租户实现
多租户属于SQL解析部分,依赖MP分页插件,需要在分页插件中设置解析器。我们来实现上述的第三种方法。
1)在配置类中配置分页插件
@Configuratin
public class MybatisPlusCongifuration{
@Bean
public PaginationIterceptor paginationIterceptor(){
PaginationIterceptor paginationIterceptor = new PaginationIterceptor();
ArrayList<ISqlPaeser> sqlParserList = new ArrayList<ISqlPaeser>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(new TenanrHandler)(){
@Override
public String getTenantIdColunm(){
return "manage_id";//表中的字段名
}
@Override
public Expression getTenantId(){
return new LongValue(12378957169814598L)
}
@Override
//过滤一些表
public boolean doTableFilter(String tableName){
if("role".equals(tableName)){
return true;
}
return false;
}
});
sqlParserList.add(tenantSqlParser);
paginationIterceptor.setSqlParserList(sqlParserList);
return paginationIterceptor;
}
}
3.特定SQL过滤
1.在分页插件中配置过滤器
paginationIterceptor.setSqlParserFilter(new ISqlParserFilter(){
@Override
public boolean doFilter(MetaObject metaObject){
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
//全限定名
if("com.mp.dao.UserMapper.selectById".equals(ms.getId())){
return true:
}
return false;
}
})
2.在UserMapper的方法上加上注解
@SqlParser(filter=true)
动态表名SQL解析器
1.动态表名的应用场景
有时候一个表数据量太大,我们进行分表存储,例如某些日志信息表,针对不同机构的表。我们查询这些表时需要动态生成表名。分库分表插件也可以解决这个问题。
2.动态表名的实现
我们也在分页插件的配置中配置
@Configuratin
public class MybatisPlusCongifuration{
public static ThreadLocal<String> myTableName = new ThreadLocal<>();
@Bean
public PaginationIterceptor paginationIterceptor(){
PaginationIterceptor paginationIterceptor = new PaginationIterceptor();
ArrayList<ISqlPaeser> sqlParserList = new ArrayList<ISqlPaeser>();
DynamicTableNameParser dynamicTableNameParser = new DynamicTableNameParser ();
Map<String,ITableNameHandler> tableNameHandlerMap = new HashMap<String,ITableNameHandler>();
tableNameHandlerMap.put("user",new ItableNameHandler(){
@Override
public String dynamicTableName(MetaObject metaObject,String sql,String tableName){
return myTableName.get();
}
});
dynamicTableNameParser.setTableNameHandlerMap(tableNameHandlerMap);
sqlParserList.add(dynamicTableNameParser);
}
}
测试:
@Test
public void select(){
MybatisPlusConfiguration.myTableName.set("user_2019");
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
3.注意事项
1)我们在方法中不传MybatisPlusConfiguration.myTableName.set(“user_2019”) ,就不进行替换。
2)上面对多租户sql语句的过滤在这儿也会生效:
paginationIterceptor.setSqlParserFilter(new ISqlParserFilter(){
@Override
public boolean doFilter(MetaObject metaObject){
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
//全限定名
if("com.mp.dao.UserMapper.selectById".equals(ms.getId())){
return true:
}
return false;
}
})
@SqlParser(filter=true)
SQL注入器
1.简介
使用它我们就可以自定义通用方法,就像selectById,insert这类方法,自定义的方法要添加到SQL注入器中,MP给我们提供的方法也是放在了SQL注入器中,在MP给我们提供的方法不能满足我们的需求时,我们可以自己注入。
2.实现步骤
1)创建定义方法的类
public class DeletaAllMethod extends AbstractMethod{
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass,Class<?> modelClass,TableInfo tableInfo){
//执行的sql
String sql = "delete from" + tableInfo.getTableName();
//mapper接口方法名
String method = "deleteAll"
Sqlsource sqlsource = langusgeDriver.createSqlSource(configuration,sql,modelClass);
return addDeleteMappedStatement(mapperClass,method,sqlSource)
}
}
2)创建注入器
@Component
public mySqlInjector extends DefaultSqlInjector{
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass){
List<AbstrctMethod> methodList = super.getMathodList(mapperClass);
methodList.add(new DeleteAllMethod());
return methodList;
}
}
3)在Mapper中加入自定义方法
//删除所有,返回影响行数
int deleteAll();
4)测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class InjectorTest{
@Autowired
private UserMapper userMapper;
@Test
public void deleteAll(){
sout(userMapper.deleteAll());
}
}
5)补充
我们不再去继承BaseMapper,继承我们自己的MyMapper,就不用在Mapper层中写我们自定义的方法了
public interface MyMapper<T> extends BaseMapper<T>{
//删除所有,返回影响行数
int deleteAll();
}
注意:List methodList = super.getMathodList(mapperClass),不这样做我们就不能使用MP帮我们定义的方法了
MP帮我们提供了三个选装件,这三个选装件也是自定义方法
InsertBatchSomeColumn:批量新增数据,自选字段insert
LogicDeleteByIdWithFill:根据id逻辑删除数据,并带字段填充功能
alwaysUpdateSomeColumn:根据id更新固定的某些字段