Mybatis-Plus:ActiveRecord和插件应用

1、ActiveRecord

ActiveRecord(简称AR)⼀直⼴受动态语⾔( PHP 、 Ruby 等)的喜爱,⽽ Java 作为准静态语⾔,对于 ActiveRecord 往往只能感叹其优雅,所以 Mybatis-Plus 也在 AR 道路上进⾏了⼀定的探索。

什么是ActiveRecord?

ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很⼤程度的快速实现模型的操作,⽽且简洁易懂。

ActiveRecord的主要思想是:

  • 每⼀个数据库表对应创建⼀个类,类的每⼀个对象实例对应于数据库中表的⼀⾏记录;通常表的每个字段在类中都有相应的Field;
  • ActiveRecord同时负责把⾃⼰持久化,在ActiveRecord中封装了对数据库的访问,即CRUD;
  • ActiveRecord是⼀种领域模型(Domain Model),封装了部分业务逻辑;

1.1、开启AR模式

在MP中,开启AR⾮常简单,只需要将实体对象继承Model即可。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User extends Model<User> {
    @TableId(type = IdType.AUTO) //指定Id的生成策略为自增长
    private Long id;
    @TableField(select = true) //查询的时候,不返回该字段的值
    private String name;
    private String age;
    @TableField(value = "email") //解决字段名不一致问题
    private String mail;
    @TableField(exist = false) //该字段在数据库表中不存在
    private String address;
}

1.2、根据主键查询

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestAR {
    /*
        在AR模式下,完成根据主键查询
     */
    @Test
    public void tesARSelectById(){
        User user = new User();
        user.setId(12L);

        User user1 = user.selectById();
        System.out.println(user1);
    }
}

1.3、新增数据

/*
    在AR模式下,完成添加操作
 */
@Test
public void tesARInsert(){
    User user = new User();
    user.setName("七喜");
    user.setAge("18");
    user.setMail("qixi@163.com");

    boolean insert = user.insert();
    System.out.println(insert);
}

结果:

MyBatis-Plus_21


1.4、更新操作

/*
    在AR模式下,完成更新操作
 */
@Test
public void tesARUpdate(){
    User user = new User();
    user.setId(13L);
    user.setName("七喜");
    user.setAge("48");
    user.setMail("qixi48@163.com");

    boolean insert = user.updateById();
    System.out.println(insert);
}

MyBatis-Plus_22


1.5、删除操作

/*
    在AR模式下,完成删除操作
 */
@Test
public void tesARDelete(){
    User user = new User();
    //user.setId(13L);

    boolean delete = user.deleteById(13L);
    System.out.println(delete);
}

1.6、根据条件查询

/*
    在AR模式下,根据条件进行查询
 */
@Test
public void tesARFindByWrapper(){
    User user = new User();
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.ge("age",18);

    List<User> users = user.selectList(queryWrapper);
    for (User user1 : users) {
        System.out.println(user1);
    }

}

结果:

User(id=1, name=Jone, age=18, mail=test1@baomidou.com, address=null)
User(id=2, name=Jack, age=20, mail=test2@baomidou.com, address=null)
User(id=3, name=Tom, age=28, mail=test3@baomidou.com, address=null)
User(id=4, name=Sandy, age=21, mail=test4@baomidou.com, address=null)
User(id=5, name=Billie, age=24, mail=test5@baomidou.com, address=null)
User(id=6, name=小七1, age=18, mail=xiaoqi@163.com, address=null)
User(id=12, name=小七, age=20, mail=test, address=null)

2、插件

2.1、Mybatis中的插件机制

MyBatis 允许你在已映射语句执⾏过程中的某⼀点进⾏拦截调⽤。默认情况下,MyBatis 允许使⽤插件来拦截的⽅法调⽤包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

我们看到了可以拦截Executor接⼝的部分⽅法,⽐如update,query,commit,rollback等⽅法,还有其他接⼝的⼀些⽅法等。

总体概括为:

  1. 拦截执⾏器的⽅法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截Sql语法构建的处理

拦截器示例:

@Intercepts({@Signature(
	type= Executor.class,
	method = "update",
	args = {MappedStatement.class,Object.class})})
public class MyInterceptor implements Interceptor {
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
    //拦截⽅法,具体业务逻辑编写的位置
    return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
    //创建target对象的代理对象,⽬的是将当前拦截器加⼊到该对象中
    return Plugin.wrap(target, this);
	}
	@Override
    public void setProperties(Properties properties) {
    //属性设置
	}
}

注⼊到Spring容器:

/**
 * ⾃定义拦截器
 */
@Bean
public MyInterceptor myInterceptor(){
	return new MyInterceptor();
}

或者通过xml配置,SqlMapConfig.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
   "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 	<plugins>
 		<plugin interceptor="com.lagou.mp.plugins.MyInterceptor"></plugin>
 	</plugins>
</configuration>

2.2、执⾏分析插件

在MP中提供了对SQL执⾏的分析的插件,可⽤作阻断全表更新、删除的操作,注意:该插件仅适⽤于开发环境,不适⽤于⽣产环境。

SpringBoot配置:

@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
	SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
	List<ISqlParser> sqlParserList = new ArrayList<>();
	// 攻击 SQL 阻断解析器、加⼊解析链
	sqlParserList.add(new BlockAttackSqlParser());
	sqlExplainInterceptor.setSqlParserList(sqlParserList);
	return sqlExplainInterceptor;
}

测试:

@Test
public void testUpdate(){
 	User user = new User();
 	user.setAge(20);
 	int result = this.userMapper.update(user, null);
 	System.out.println("result = " + result);
}

结果:

Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition
of table update operation
 at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:49)
 at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38)
 at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72)
 at
com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processUpdate(BlockAtta
ckSqlParser.java:45)
 at
com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(AbstractJsqlParse
r.java:92)
 at
com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlParser.java:
67)
 at
com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser(Abstract
SqlParserHandler.java:76)
 at
com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(SqlExplainIn
terceptor.java:63)
 at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
 at com.sun.proxy.$Proxy70.update(Unknown Source)
 at
org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
 ... 41 more

可以看到,当执⾏全表更新时,会抛出异常,这样有效防⽌了⼀些误操作。


2.3、性能分析插件

性能分析拦截器,⽤于输出每条 SQL 语句及其执⾏时间,可以设置最⼤执⾏时间,超过时间会抛出异常。

该插件只⽤于开发环境,不建议⽣产环境使⽤。

javaconfig⽅式

/*
    性能分析插件
 */
@Bean
public PerformanceInterceptor performanceInterceptor(){
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    //设置sql语句的最大执行时长
    performanceInterceptor.setMaxTime(100);
    //设置sql是否格式化显示
    performanceInterceptor.setFormat(true);

    return performanceInterceptor;
}

xml⽅式

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
 	PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 	"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 	<plugins>
 		<!-- SQL 执⾏性能分析,开发环境使⽤,线上不推荐。 maxTime 指的是 sql 最⼤执⾏时⻓ -->
 		<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
 			<property name="maxTime" value="100" />
 			<!--SQL是否格式化 默认false-->
 			<property name="format" value="true" />
 		</plugin>
 	</plugins>
</configuration>

执⾏结果:

Time:11 ms - ID:com.tao.mapper.UserMapper.selectList
Execute SQL:
    SELECT
        id,
        name,
        age,
        email AS mail 
    FROM
        user 
    WHERE
        age >= 18

可以看到,执⾏时间为11ms。如果将maxTime设置为1,那么,该操作会抛出异常。

Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL
execution time is too large, please optimize !
 at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:49)
 at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38)

2.4、乐观锁插件

2.4.1、主要适⽤场景

意图:

当要更新⼀条记录的时候,希望这条记录没有被别⼈更新

乐观锁实现⽅式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执⾏更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

2.4.2、插件配置

spring xml:

<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>

spring boot:

/*
    乐观锁插件
 */
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
    return new OptimisticLockerInterceptor();
}

2.4.3、注解实体字段

需要为实体字段添加@Version注解。

第⼀步,为表添加version字段,并且设置初始值为1:

ALTER TABLE `user` ADD COLUMN `version` int(10) NULL AFTER `email`;

UPDATE `user` SET `version`='1';

第⼆步,为User实体对象添加version字段,并且添加@Version注解:

@Version
private Integer version;

2.4.4、测试

测试⽤例:

@Test
public void tesARUpdate(){
    User user = new User();
    User user1 = user.selectById(12L);

    user.setId(12L);
    user.setName("七喜");
    user.setVersion(user1.getVersion());

    boolean insert = user.updateById();
    System.out.println(insert);
}

执⾏⽇志:

Time:4 ms - ID:com.tao.mapper.UserMapper.updateById
Execute SQL:
    UPDATE
        user 
    SET
        name='七喜',
        version=2 
    WHERE
        id=12 
        AND version=1

true

可以看到,更新的条件中有version条件,并且更新的version为2。

如果再次执⾏,更新则不成功。这样就避免了多⼈同时更新时导致数据的不⼀致。


2.4.5、特别说明

  • ⽀持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity 中
  • 仅⽀持 updateById(id) 与 update(entity, wrapper) ⽅法
  • 在 update(entity, wrapper) ⽅法下, wrapper 不能复⽤!!!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值