MybatisPlus快速上手:
MybatisPlus快速上手1——MybatisPlus插件的介绍和整合
Mybatis快速上手2——通用的CRUD操作
PlusMybatisPlus快速上手3——相关配置
PlusMybatisPlus快速上手4——条件构造器Wrapper
ActiveRecord
ActiveRecord(简称AR)一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,对于ActiveRecord 往往只能感叹其优雅,所以我们也在 AR 道路上进行了一定的探索,喜欢大家能够喜欢。
什么是ActiveRecord?
ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。ActiveRecord的主要思想是:
- 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段 在类中都有相应的Field;
- ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;;
- ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑;
在mybatisPlus中使用ActiveRecord
在MybatisPlus中,开启ActiveRecord非常简单,只需要将实体对象继承Model即可。
ActiveRecord的CRUD操作
首先我们需要在User中继承Model类
@Data //生成get\set方法
@NoArgsConstructor //无参构造
@AllArgsConstructor //全参构造
@TableName("tb_user") //指定对应的数据库表名
public class User extends Model<User> {
@TableId(value = "id",type = IdType.AUTO) //指定id类型为自增长
private Long id;
private String userName;
@TableField(select = false) //查询时,不返回该字段的值
private String password;
private String name;
private Integer age;
@TableField(value = "email") //属性名与数据库中的字段名不一致时,指定数据库表中的字段名
private String mail;
@TableField(exist = false)
private String address; //在数据库中不存在的,若不指定会报错
}
接下来我们进行CRUD操作
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestUserMapper2 {
/** * 这里的方法不需要注入userMapper,因为在ActiveRecord中已经封装 * * 注意:这里User实体类继承了Model<User>,拥有了Model类的相关方法 * 但是底层依旧调用的是我们所定义的UserMapper * ActiveRecord中封装了对数据库访问的相关方法 */
//使用ActiveRecord,根据id查询 selectById()
@Test
public void TestSelectById(){
User user = new User();
user.setId(1L);
//Preparing: SELECT id,user_name,name,age,email AS mail FROM tb_user WHERE id=?
User user1 = user.selectById();
System.out.println(user1);
}
//使用ActiveRecord,插入数据 insert()
@Test
public void TestInsert(){
User user = new User();
user.setUserName("zhuowen");
user.setPassword("123456");
user.setAge(18);
user.setName("卓汶");
user.setMail("[email protected]");
//Preparing: INSERT INTO tb_user ( user_name, password, name, age, email ) VALUES ( ?, ?, ?, ?, ? )
boolean result = user.insert();//返回插入是否成功
System.out.println("插入数据的结果:"+result);
}
// 使用ActiveRecord,更新操作
@Test
public void TestUpdate(){
User user = new User();
user.setId(14L);
user.setAge(100);//需要更新的数据
//Preparing: UPDATE tb_user SET age=? WHERE id=?
boolean result = user.updateById();
//也可以使用update进行更新,传入参数为 Wrapper
System.out.println(result);
}
//删除操作
@Test
public void TestDelete(){
User user = new User();
//方式1
user.setId(14L);
user.deleteById();
//方式2
user.deleteById(13L);
//方式3,使用delete()方法 wrapper参数的形式
}
//根据条件查询数据
@Test
public void TestSelect(){
User user = new User();
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.ge("age",30);//条件:大于等于30岁
List<User> userList = user.selectList(wrapper);
for (User user1 : userList) {
System.out.println(user1);
}
}
}
使用ActiveRecord不需要我们再注入UserMapper,他已经帮我们封装好了,但是我们创建的UserMapper依旧不能缺少,其内部调用的是我们所创建的mapper
插件与插件机制
mybatis的插件机制
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用(拦截器)。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。
总体概括为:
1. 拦截执行器的方法
2. 拦截参数的处理
3. 拦截结果集的处理
4. 拦截Sql语法构建的处理
拦截器示例:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.context.annotation.Bean;
import java.util.Properties;
@Intercepts({
@Signature(
type= Executor.class, //拦截的类型(Executor执行器)
method = "update", //拦截执行器中的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();
}
执行分析插件
在MybatisPlus中提供了对SQL执行的分析的插件,可用作阻断全表更新、删除的操作
,注意:该插件仅适用于开发环境,不适用于生产环境。
在配置文件或配置类中注入sqlExplainInterceptor,并提供相应的条件,这里我们测试防止全表更新、删除
//SQL分析插件
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List<ISqlParser> list = new ArrayList<>();
list.add(new BlockAttackSqlParser());//全表更新删除的阻断器
sqlExplainInterceptor.setSqlParserList(list);
sqlExplainInterceptor.setSqlParserList(list);
return sqlExplainInterceptor;
}
测试案例:
/** * 测试全表更新,SQL分析器的阻断效果 */
@Test
public void testUpdateAll(){
User user = new User();
user.setAge(18);
boolean result = user.update(null); //没有条件,全表更新
System.out.println(result);
}
可以看到,当执行全表更新时,会抛出异常,这样有效防止了一些误操作。
性能分析插件
性能分析拦截器,用于输出每条 SQL 语句及其执行时间
,可以设置最大执行时间,超过时间会抛出异常。该插件只用于开发环境,不建议生产环境使用。
//性能分析插件
@Bean
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//最大执行时间,单位ms
performanceInterceptor.setFormat(true);//对输出的SQL做格式化,默认为false
return performanceInterceptor;
}
在最大执行时间之内时,会输出时间,且我们对SQL语句进行了格式化:
若超出最大执行时间,则会报错
乐观锁
主要适用场景
意图:
当要更新一条记录的时候,希望这条记录没有被别人更新
,如果同时更新一条记录,肯定是会出错的。
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
插件配置
方式一:Spring的xml配置文件方式
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>
方式二:使用SpringBoot在配置类注入
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
方式三:Mybatis的配置文件方式
<plugins>
<!--乐观锁插件-->
<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"></plugin>
</plugins>
注解实体字段
需要为实体字段添加@Version注解。
第一步,为表添加version字段,并且设置初始值为1:
ALTER TABLE `tb_user`
ADD COLUMN `version` int(10) NULL AFTER `email`;
UPDATE `tb_user` SET `version`='1';
第二步,为User实体对象添加version字段,并且添加@Version注解:
@Version
private Integer version;
测试
/** * 测试乐观锁 */
@Test
public void TestUpdateVersion(){
User user = new User();
user.setId(1L);
user.setAge(22);//需要更新的数据
user.setVersion(1); //当前版本信息
boolean result = user.updateById();
System.out.println(result);
}
/** * 测试乐观锁 */
@Test
public void TestUpdateVersion(){
User user = new User();
user.setId(1L);
User userVersion = user.selectById();
user.setAge(23);//需要更新的数据
user.setVersion(userVersion.getVersion()); //当前版本信息
boolean result = user.updateById();
System.out.println(result);
}
若版本信息不正确,执行可以通过,但是不会进行修改,保证了不会同时进行修改的问题。即若A和B同时修改数据,A和B都User userVersion = user.selectById();
获取到了当前版本的对象,但是B先修改并提交了,届时version=3
,导致A无法继续修改数据。
特别说明
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
,一般使用int
- 整数类型下newVersion = oldVersion + 1
- newVersion 会回写到 entity 中
- 仅支持 updateById(id)
与 update(entity, wrapper)
方法
- 在 update(entity, wrapper)
方法下, wrapper 不能复用!!!
Sql 注入器
我们已经知道,在MybaisPlus中,通过AbstractSqlInjector
将BaseMapper中的方法注入到了Mybatis容器
,这样这些方法才可以正常执行。
那么,如果我们需要扩充BaseMapper中的方法,又该如何实现呢?
下面我们以扩展findAll方法为例进行学习。
如果只是一个实体类需要创建新的方法,我们可以在mapper中进行实现,但是若所有的实体类都需要使用新的方法(相同),我们可以进行以下创建
编写MyBaseMapper
/** * 之后的mapper就不需要继承BaseMapper了,只需要继承我们自己定义的MyBaseMapper即可 * 这样,不仅可以得到BaseMapper中的方法,也可以使用MyBaseMapper我们自己定义的方法 * @param <T> */
public interface MyBaseMapper<T> extends BaseMapper<T> {
List<T> findAll();
//可以拓展其他方法
}
其他的Mapper都可以继承该Mapper,这样实现了统一的扩展。
如:
public interface UserMapper extends MyBaseMapper<User> {
}
编写MySqlInjector
如果直接继承AbstractSqlInjector
的话,原有的BaseMapper
中的方法将失效,所以我们选择继承DefaultSqlInjector
进行扩展。
public class MySqlInjector extends DefaultSqlInjector {
//注意,这里需要继承的是DefaultSqlInjector
//获取getMethod方法
@Override
public List<AbstractMethod> getMethodList() {
List<AbstractMethod> list = new ArrayList<>();
//获取父类中的集合
list.addAll(super.getMethodList());
//再扩充自定义的方法
list.add(new findAll());
return list;
}
}
编写FindAll
public class findAll extends AbstractMethod {
//将sql加入到statement
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql = "select * from"+tableInfo.getTableName();
SqlSource sqlSource = languageDriver.createSqlSource(configuration,sql,modelClass);
return this.addSelectMappedStatement(mapperClass, "findAll", sqlSource ,modelClass,tableInfo);
}
}
注册到Spring容器
/** * 注入自定义的sql注入器 * @return */
@Bean
public MySqlInjector mySqlInjector(){
return new MySqlInjector();
}
测试
@Test
public void testFindAll(){
List<User> userList = mapper.findAll();
for (User user : userList) {
System.out.println(user);
}
}
自动填充功能
有些时候我们可能会有这样的需求,插入或者更新数据时,希望有些字段可以自动填充数据
,比如密码、version、时间字段等。在MP中提供了这样的功能,可以实现自动填充。
添加 @TableField 注解
/** * @TableField 表属性标签 * select = false 查询时设置不允许被查询 * fill = FieldFill.INSERT 插入时自动填充 */
@TableField(select = false,fill = FieldFill.INSERT)
private String password;
为password添加自动填充功能,在新增数据时有效。
FieldFill提供了多种模式选择:
编写MyMetaObjectHandler
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/** * 在插入数据时填充 * @param metaObject */
@Override
public void insertFill(MetaObject metaObject) {
//获取password的值,进行判断,若为null,则进行填充,若不为空,就不做处理
Object password = getFieldValByName("password", metaObject);
if (null == password){
setFieldValByName("password","888888",metaObject);//设置默认密码
}
}
/** * 在更新数据时填充 * @param metaObject */
@Override
public void updateFill(MetaObject metaObject) {
}
}
测试
@Test
public void testInsert(){
User user = new User();
user.setUserName("zangsan2");
user.setName("张思睿2");
user.setAge(1);
user.setMail("[email protected]");
user.setAddress("bj");
int result = mapper.insert(user); //返回值是数据库受影响的行数
System.out.println("result==>"+result);
//获取自增长后的id值,自增长后的id值会回填到user对象中
System.out.println("id==>"+user.getId());
}
逻辑删除
开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删除,而并非真正的物理删除(非DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免数据被真正的删除。
MybatisPlus 就提供了这样的功能,方便我们使用,接下来我们一起学习下。
修改表结构
为tb_user表增加deleted字段
,用于表示数据是否被删除,1代表删除,0代表未删除
。
ALTER TABLE `tb_user`
ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER `version`;
同时,也修改User实体,增加deleted属性并且添加@TableLogic注解:
@TableLogic //逻辑删除字段,1代表删除,0代表正常状态
private Integer deleted;
配置
application.properties:
# 逻辑删除状态值设置,删除默认是1,未删除默认是0,即是下面的设置可以省略
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value=1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value=0
测试
//根据id删除数据
@Test
public void testDeleteById(){
int result = mapper.deleteById(16L);
System.out.println("result==>"+result);
}
当我们查询时
//查询所有数据
@Test
public void testSelectList1(){
User user = new User();
for (User user1 : user.selectList(null)) {
System.out.println(user1);
}
}
//根据id查询
@Test
public void testSelectById(){
User user = mapper.selectById(16L);
System.out.println(user);
}
被逻辑删除的数据拿不到
通用枚举
解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
修改表结构
# 性别字段,1表示男,2表示女,默认值是1
ALTER TABLE `tb_user`
ADD COLUMN `sex` int(1) NULL DEFAULT 1 COMMENT '1-男,2-女' AFTER `deleted`;
定义枚举
public enum SexEnum implements IEnum<Integer> {
MAN(1,"男"),
WOMAN(2,"女");
private int value;//数字
private String desc;//性别
SexEnum(int value, String desc) {
this.value = value; this.desc = desc;
}
@Override public Integer getValue() {
return this.value;
}
@Override public String toString() {
return this.desc;
}
}
配置
# 枚举包扫描 自动扫描包下的所有枚举,将他们载入到插件中
mybatis-plus.type-enums-package=cn.itcast.mp.enums
修改实体
在User中添加SexEnum属性
//性别(枚举类型)
private SexEnum sex;
测试
插入
@Test
public void testInsert(){
User user = new User();
user.setUserName("zangsan4");
user.setName("张思睿4");
user.setAge(23);
user.setMail("[email protected]");
user.setPassword("123456");
user.setAddress("bj");
user.setVersion(1);
user.setSex(SexEnum.WOMAN); //使用枚举类型
int result = mapper.insert(user); //返回值是数据库受影响的行数
System.out.println("result==>"+result);
//获取自增长后的id值,自增长后的id值会回填到user对象中
System.out.println("id==>"+user.getId());
}
查询
//根据id查询
@Test
public void testSelectById(){
User user = mapper.selectById(17L);
System.out.println(user);
}
条件查询
@Test
public void testSelectBySex(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("sex",SexEnum.WOMAN);//查询性别为女的数据
List<User> userList = mapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
文章评论