MybatisPlus
为什么要学习MybatisPlus?
它可以节省我们大量的工作时间,所有的CRUD代码都可以自动化完成,简化Mybatis!
1、MybatisPlus是什么?
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
作者
Mybatis Plus是由baomidou (苞米豆) 组织开发并且开源的,目前该组织大概有16人左右。
2、快速开始
对于MybatisPlus常常有两种整合方式,分别是Spring+MP、SpringBoot+MP
使用第三方组件:
- 导入对应依赖
- 研究依赖如何配置
- 代码如何编写
- 提高扩展技术能力
步骤
-
创建数据库
mp_db
-
创建user表
DROP TABLE IF EXISTS tb_user; CREATE TABLE tb_user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) ); # ---真实开发,version(乐观锁)、deleted(逻辑删除)、gmt_create/gmr_modified
-
插入测试数据
DELETE FROM tb_user; INSERT INTO tb_user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
-
编写项目,初始化项目,使用SpringBoot初始化
-
导入依赖
<!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <!-- mybatisPlus整合包 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency>
注:导入了mybaits-plus就不要在导入mybaits依赖,存在版本差异问题!
-
连接数据库(和mybaits大同小异)
# mysql 5 驱动不同 com.mysql.jdbc.Driver spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp_db?useSSL=false&useUnicode=true?characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root # mysql 8 驱动不同、需要增加时区的配置 com.mysql.cj.jdbc.Driver mybatis-plus: configuration: # 日志输出 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 自增id类型 id-type: auto # 表名前缀 table-prefix: tb_
传统方式:pojo–>dao(连接mybaits,配置mapper.xml)–>service–>controller
-
使用mybatis-plus
-
编写pojo类
package com.zhang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private Long id; private String name; private Integer age; private String email; }
-
mapper接口
package com.zhang.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.zhang.pojo.User; import org.springframework.stereotype.Repository; // 在对应的 Mapper上面继承 BaseMapper 就拥有了BaseMapper中的所有方法 @Mapper public interface UserMapper extends BaseMapper<User> { // 所有的CRUD操作已经编写完成了 // 你不需要像之前那样编写一大堆配置文件(增删改查方法)了! }
-
使用
package com.zhang; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication // 扫描mapper文件夹 @MapperScan("com.zhang.mapper") public class MybaitsPlusApplication { public static void main(String[] args) { SpringApplication.run(MybaitsPlusApplication.class, args); } }
-
注:需要在主启动类扫描mapper包下的所有接口
@MapperScan("com.zhang.mapper")
-
测试类测试
package com.zhang; import com.zhang.mapper.UserMapper; import com.zhang.pojo.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class MybaitsPlusApplicationTests { // 继承了BaseMapper,所有的方法都来自父类,也可以编写自己的扩展方法 @Autowired private UserMapper userMapper; @Test void contextLoads() { // 参数是一个Wrapper,条件构造器,先不使用 null // 查询全部用户 List<User> userList = userMapper.selectList(null); userList.forEach(System.out::println); } }
-
说明:由于使用了MybatisSqlSessionFactoryBuilder进行了构建,**继承的BaseMapper中的方法就载入到了Sqlsession中,**所以就可以直接使用相关的方法
Mybatis-Plus帮我们把SQL语句和对应方法都定义好了!无需我们自己编写,直接进行调用即可
3、配置日志
所有的sql现在是不可见的,希望知道它是怎么执行的?所以需要通过日志查看
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
配置完毕日志之后,就可以查看相关的Sql信息了!
4、CRUD(扩展)
4.1、Insert
// 测试插入
@Test
public void testInsert() {
User user = new User();
user.setName("Mr zhang");
user.setAge(22);
user.setEmail("776970468@163.com");
// 会帮我们自动生成一个id(雪花算法)
int i = userMapper.insert(user);
System.out.println("受影响的行数:" + i);
System.out.println(user);
}
数据库插入的id默认值为:全局的唯一id
@TableField
在MP中通过@TableField注解可以指定字段的一些属性,常常解决的问题有2个:
-
对象中的属性名和字段名不一致的问题(非驼峰)
-
对象中的属性字段在表中不存在的问题
@TableField(select = false) // 查询时不返回该字段的值 private Integer age; @TableField(value = "email") // 指定数据库表中的字段名 private String mail; @TableField(exist = false) private String address; // 在数据库表中是不存在的
主键生成策略
雪花算法应用场景
雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的算法。它由Twitter公司提出,用于解决分布式系统中生成全局唯一ID的需求
雪花算法生成的ID是一个64位的整数,由以下几个部分组成:
- 时间戳:41位,精确到毫秒级,可以使用69年
- 节点ID:10位,用于标识分布式系统中的不同节点
- 序列号:12位,表示在同一毫秒内生成的不同ID的序号
通过将这三个部分组合在一起,雪花算法可以在分布式系统中生成全局唯一的ID,并保证ID的生成顺序性
需要注意的是,雪花算法依赖于系统的时钟,需要确保系统时钟的准确性和单调性,否则可能会导致生成的ID不唯一或不符合预期的顺序
雪花算法是一种简单但有效的生成唯一ID的算法,广泛应用于分布式系统中,如微服务架构、分布式数据库、分布式锁等场景,以满足全局唯一标识的需求
注:雪花算法生成的数字,在Java类中需要使用Long 或者 String类型的属性接值,数据库中字段类型要使用bigint或者varchar(64)!
主键自增
需要配置主键自增:
- 在实体类字段添加注解
@TableId(type= IdType.AUTO)
- 数据库字段一定要保证是自增(auto_increment)的,否则报错!
4.2、Retrieve
MP提供了多种查询操作。包括根据id查词,批量查询、查询单条数据、查询列表,分页查询等操作
// 测试查询
@Test
public void testRetrieve() {
//通过Id查询用户 查询单个用户
//User user = userMapper.selectById(1L);
//System.out.println(user);
// 根据Id查询多个用户 批量查询
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4));
userList.forEach(System.out::println);
}
// 查询单条数据
@Test
public void testSelectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 查询条件
queryWrapper.eq("name", "刘备");
// 当查询的数据有多条时,会报错
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
// 查询记录数
@Test
public void testSelectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 条件:年龄大于22的用户
queryWrapper.gt("age", 22);
// 根据条件查询数据条数
Integer count = userMapper.selectCount(queryWrapper);
System.out.println("count===>" + count);
}
// 按条件查询,使用map操作
@Test
public void testSelectByMap(){
HashMap<String, Object> map = new HashMap<>();
//自定义要查询的条件
map.put("id","1453714251707047937");
map.put("name","Mr zhang");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
分页查询
分页在各大网站非常常见!
- 原始的
limit
进行分页 - pageHelper第三方插件
- MybatisPlus内置了分页插件
使用
-
配置插件,分页拦截器组件
@Configuration // 表示是一个配置类 @MapperScan("com.zhang.mapper") // 设置mapper接口的扫描包 public class MybatisPlusConfig { @Bean // 配置分页插件 public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
-
测试用例
// 测试分页查询 @Test public void testSelectPage() { // 使用Page对象 Page<User> page = new Page<>(1, 3); //查询第一页,每页显示三条数据 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // 设置查询条件:根据 id 降序排列 queryWrapper.orderByDesc("id"); Page<User> userPage = userMapper.selectPage(page, queryWrapper); System.out.println("当前页:" + userPage.getCurrent()); System.out.println("每页显示条数:" + userPage.getSize()); System.out.println("总记录数:" + userPage.getTotal()); System.out.println("总页数:" + userPage.getPages()); // 分页查询到的数据 List<User> records = userPage.getRecords(); records.forEach(System.out::println); }
4.3、Update
@Test
public void testUpdate1() {
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("age", 25).set("name", "李明") // 更新的字段 列名:要设置的值
.lt("age", 21); // 更新的条件
int result = userMapper.update(null, updateWrapper);
System.out.println("受影响的行数:" + result);
}
// 测试更新
@Test
public void testUpdate2() {
User user = new User();
// 通过条件自动拼接动态sql
user.setId(6L);
user.setName("Mr Lin");
user.setAge(20);
user.setEmail("776070468@qq.com");
// 注:updateById,参数是一个对象!
int i = userMapper.updateById(user);
System.out.println("受影响的行数:" + i);
}
所有的SQL都是根据条件自动帮助我们动态编写的!
自动填充
创建时间、修改时间!这些操作一般都是自动化完成的,我们不希望手动更新!
阿里开发手册:所有数据库表:gmt_create、gmt_modified几乎所有表都要配置上,需要自动化
方式一:数据库级别(不建议使用,工作中不允许修改数据库)
-
在表中新增字段 create_time,update_time
-
再次测试插入方法,需要把实体类同步!
private LocalDateTime createTime; private LocalDateTime updateTime;
方式二:代码级别
-
删除数据库的默认值、更新的操作
-
实体类字段属性需要添加注解
// INSERT:插入时填充字段 @TableField(fill = FieldFill.INSERT) private Date createTime; // INSERT_UPDATE:插入和更新时填充字段 @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
-
编写处理器处理这个注解即可!
package com.zhang.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.util.Date; /** * @author Mr Zhang * @create:2021-10-28 22:35 */ @Slf4j @Component // 把编写好的处理器添加到IOC容器中! public class MyDataObjectHandler implements MetaObjectHandler { // 插入时候的填充策略 @Override public void insertFill(MetaObject metaObject) { log.info("开始新增时,字段属性填充"); this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } // 更新时候的填充策略 @Override public void updateFill(MetaObject metaObject) { log.info("开始修改时,字段属性填充"); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }
-
测试添加与更新操作、观察对应时间即可!
乐观锁
乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!
乐观锁:顾名思义十分乐观,它总是以为不会出现问题,无论干什么都不去上锁,反复去尝试,直到没有问题,再去操作!
悲观锁:顾名思义十分悲观,他总是以为总是出现问题,无论干什么都会先上锁,再去操作!
注:悲观锁和乐观锁是两种解决并发数据问题的思路,而不是具体技术实现!
具体技术和方案:
乐观锁实现方案和技术:
- 版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理
- CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试
- 无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全
悲观锁实现方案和技术:
- 锁机制:使用传统的锁机制,如互斥锁(Mutex Lock)或读写锁(Read-Write Lock)来保证对共享资源的独占访问
- 数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问
- 信号量(Semaphore):使用信号量来限制对资源的并发访问
乐观锁实现方式:
- 每条数据添加一个版本号字段version
- 取出记录时,获取当前version
- 更新时,检查获取版本号是不是数据库当前最新版本号
- 如果是[证明没有人修改数据],执行更新,set 数据更新,version = version+ 1
- 如果 version 不对[证明有人已经修改了],我们现在的其他记录就是失效数据!就更新失败
测试MP的乐观锁插件
-
给数据库添加version字段!并且设置初始值为1
-
给实体类添加属性并添加@Version注解
@Version // 乐观锁属性 @Version注解 private Integer version;
-
添加乐观锁插件
@Configuration @MapperScan("com.zhang.mapper") public class MybatisPlusConfig { // 注册乐观锁版本号插件 @Bean public MybatisPlusInterceptor optimisticLockerInnerInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // mybatis-plus会在每次更新的时候,帮我们对比版本号字段和增加版本号+1 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
-
测试
// 测试乐观锁生效场景 @Test public void testOptimisticLockSuccess() { // 先查询,在更新 获取version数据 // 同时查询两条,但是version唯一,最后更新的失败 User u1 = userService.getById(1L); // version:1 u1.setAge(66); // 模拟另外一个线程执行了插队操作 User u2 = userService.getById(1L); // version:1 u2.setAge(77); boolean flag = userService.updateById(u2); // version ===> 2 System.out.println("flag = " + flag); // true // 乐观锁生效失败! flag = userService.updateById(u1); System.out.println("flag = " + flag); // false }
4.4、Delete
// 测试单个删除
@Test
public void testDeleteById() {
// 根据id删除用户数据
int rs = userMapper.deleteById(9L);
System.out.println("受影响的行数:" + rs);
}
// 通过id批量删除
@Test
public void testDeleteBatchIds() {
userMapper.deleteBatchIds(Arrays.asList(1453714251707047938L, 1453714251707047939L));
}
// 通过map删除
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "李明");
map.put("age", 25);
// 根据 map 删除数据,多条件之间是 and 关系
int rs = userMapper.deleteByMap(map);
System.out.println("受影响的行数:" + rs);
}
// 根据条件删除
@Test
public void testDelete() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "Toms").
eq("email", "test3@baomidou.com");
// 根据包装条件删除用户 多条件之间是 and 关系
int i = userMapper.delete(queryWrapper);
System.out.println("受影响的行数:" + i);
}
逻辑删除
物理删除:真实删除,将对应数据从数据库中删除(DELETE操作),之后查询不到此条被删除的数据
逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效!(标记为删除状态),查询时需要携带状态条件,确保被标记的数据不被查询到。deleted = 0 => deleted = 1
管理员可以查看被删除的记录!防止数据的丢失(避免数据被真正的删除),类似于回收站
实现方式
-
在数据库添加
deleted
字段,用来表示数据是否被删除,1表示删除,0表示未删除 -
实体类添加属性以及注解
@TableLogic // 逻辑删除 private Integer deleted;
-
配置
mybatis-plus: global-config: db-config: # 逻辑删除属性名(全局配置) logic-delete-field: deleted # 未逻辑删除的值(默认值为0) logic-not-delete-value: 0 # 已逻辑删除的值(默认值为1) logic-delete-value: 1
记录依旧在数据库里面,但是值已经发生改变!
4.5、配置
在MP中有大量的配置。其中一部分为 MyBatis 原生所支持的配置,另一部分是MP的配置
4.5.1、基本配置(Mybatis)
configLocation
Spring Boot:
# 指定全局配置文件
mybatis-plus.config-location=classpath:mybatis-config.xml
mapperLocations
MyBatis Mapper所对应的XML文件位置,如果您在 Mapper中有自定义方法(XML 中有自定义实现),需要进行该配置,告诉Mapper所对应的XML文件位置
SpringBoot:
# 指定Mapper.xml文件的路径
mybatis-plus.mapper-locations=classpath:/mappers/*.xml
typeAliasesPackage
MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)
SpringBoot:
# 指定实体对象的扫描包
mybatis-plus.type-aliases-package=com.zhang.pojo
本部分(Configuration)的配置大都为 MyBatis 原生支持的配置,这意味着您可以通过 MyBatis XML 配置文件的形式进行配置
mapUnderscoreToCamelCase
- 类型:
boolean
- 默认值:
true
是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。
# 关闭驼峰映射
mybatis-plus.configuration.map-underscore-to-camel-case=false
cacheEnabled
- 类型:
boolean
- 默认值:
true
开启Mybatis二级缓存,默认为 true。
# 禁用缓存
mybatis-plus.configuration.cache-enabled=false
4.5.2、DB策略配置
idType
- 类型:
com.baomidou.mybatisplus.annotation.IdType
- 默认值:
ASSIGN_ID
全局默认主键类型,设置后,即可省略实体对象中的@ Tableld(type = IdType.AUTO)配置
SpringBoot:
# 全局的id生成策略
mybatis-plus.global-config.db-config.id-type=auto
tablePrefix
- 类型:
String
- 默认值:
null
表名前缀,全局配置后可省略@TableName()注解配置
SpringBoot:
# 全局的表名前缀 需保证所有表名都是以 tb_ 开头
mybatis-plus.global-config.db-config.table-prefix=tb_
4.6、条件构造器
在MP中,Wrapper接口的实现类关系如下:
可以看到,AbstractWrapper和AbstractChainWrapper是重点实现
@Test
void contextLoads() {
//查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于18
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email").
ge("age", 18);
userMapper.selectList(wrapper).forEach(System.out::println);
}
@Test
public void testEq() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 查询姓名 等于 曹操 年龄在30~40岁之间的用户
//queryWrapper.eq("name", "曹操")
// .between("age", 30, 40);
// 查询姓名 等于 张良、刘备、林峰 年龄不在10~20岁之间
// SELECT id,name,email AS mail FROM tb_user WHERE (name IN (?,?,?) AND age NOT BETWEEN ? AND ?)
queryWrapper.in("name", "张良", "刘备", "林峰")
.notBetween("age", 10, 20);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
// 范围查询
@Test
void testBetween(){
//查询年龄 20~30的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);
Integer count = userMapper.selectCount(wrapper);//查询结果数
System.out.println(count);
}
// 模糊查询
@Test
void testLike() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//左和右 %l%
wrapper.notLike("name", "l").likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
// 排序
@Test
public void testOrderBy() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 按照年龄降序排序 从大到小
// SELECT id,name,age,email AS mail FROM tb_user ORDER BY age DESC
queryWrapper.orderByDesc("age");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
// 逻辑查询
@Test
public void testOr() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 查询姓名等于林峰 年龄等于40岁的用户
// SELECT id,name,email AS mail FROM tb_user WHERE (name = ? OR age = ?)
// 参数 林峰(String), 40(Integer)
queryWrapper.eq("name", "林峰").or().eq("age", 40);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
// 指定返回的字段
@Test
public void testSelect() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// SELECT id,name FROM tb_user WHERE (name = ? OR age = ?)
queryWrapper.eq("name", "林峰")
.or()
.eq("age", 40)
.select("id", "name"); //只查询 id 和 name这两列字段,指定查询字段
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
5、ActiveRecord
ActiveRecord (简称AR)一直广受动态语言( PHP、Ruby等 )的喜爱。而Java作为准静态语言,对于ActiveRecord往往只能感叹其优雅,所以我们也在AR道路上进行了一定的探索,喜欢大家能够喜欢
什么是ActiveRecord?
**ActiveRecord也属于ORM (对象关系映射)层,**由Rails最早提出,遵循标准的ORM模型;**表映射到记录,记录映射到对象,字段映射到对象属性。**配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。
ActiveRecord主要思想:
- 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段在类中都有相应的Field
- ActveRecord同时负责把自己持久化。在ActiveRecord中封装了对数据库的访问,即CRUD
- ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑
5.1、开启AR之旅
在MP中,开启AR非常简单,只需要将实体对象继承Model<T>即可
package com.zhang.pojo;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> implements Serializable {
private Long id;
private String name;
private Integer age;
private String mail;
}
5.2.1、根据主键查询
package com.zhang;
import com.zhang.mapper.UserMapper;
import com.zhang.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
public class TestUserMapper2 {
@Test
public void testSelectById() {
User user = new User();
user.setId(4L);
User u1 = user.selectById();
System.out.println(u1);
}
}
5.2.2、Insert
@Test
public void testInsert() {
User user = new User(null, "晓琳", 35, "xiaolin@qq.com");
// 调用AR的insert方法 插入是否成功
boolean isSuccess = user.insert();
System.out.println("插入是否成功:" + isSuccess);
System.out.println(user.getId());
}
5.2.3、Retrieve
@Test
public void testSelect() {
User user = new User();
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询年龄大于等于25的用户 并且根据 id 降序排序
wrapper.ge("age", 25)
.orderByDesc("id");
List<User> users = user.selectList(wrapper);
users.forEach(System.out::println);
}
5.2.4、Update
@Test
public void testUpdate() {
User user = new User();
user.setId(5L); // 更新条件
user.setName("小张"); //更新数据
user.setAge(18);
user.setMail("xiaozhang@163.com");
boolean isSuccess = user.updateById();
System.out.println("更新是否成功:" + isSuccess);
}
5.2.5、Delete
@Test
public void testDelete() {
User user = new User();
boolean isSuccess = user.deleteById(11L);
System.out.println("删除是否成功:" + isSuccess);
}
6、插件与扩展
6.1、防全表更新与删除插件
在MP中提供了对SQL执行的分析的插件,可用作阻断恶意的全表更新、删除的操作
注入MybatisPlusInterceptor类,并配置BlockAttackInnerInterceptor拦截器
@Bean
public MybatisPlusInterceptor optimisticLockerInnerInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 防止全表更新和删除的拦截器
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
测试:
// 测试全表更新
@Test
public void testAllTableUpdate() {
User user = new User();
user.setName("xxx");
user.setAge(28);
// update t_user set name = ?,age =? where deleted = 0
boolean isSuccess = userService.update(user, null);
System.out.println("isSuccess = " + isSuccess);
}
结果:Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
禁止表更新操作
可以看到,当执行全表更新时,会抛出异常,这样会有效防止了一些误操作!
6.2、通用枚举
解决了繁琐的配置,让mybatis优雅的使用枚举属性!
6.2.1、修改表结构
ALTER TABLE `t_user`
ADD COLUMN `gender` int(1) NULL DEFAULT 1 COMMENT '1-男 2-女' AFTER `deleted`
6.2.2、定义枚举
@Getter
public enum GenderEnum {
MAN(1, "男"),
WOMAN(2, "女");
GenderEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
@EnumValue
private final int code;
private final String desc;
@Override
public String toString() {
// 注:toString不可省略,否则无法显示具体值
return this.desc;
}
}
6.2.3、修改实体
private GenderEnum gender; // 性别,枚举类型
6.2.4、测试
@Test
public void testInsert() {
User user = User.builder()
.name("貂蝉")
.age(25)
.email("diaochan@qq.com")
.gender(GenderEnum.WOMAN) // 使用的是枚举
.build();
boolean flag = userService.save(user);
System.out.println("id===>" + user.getId());
System.out.println("flag = " + flag);
}
从测试可以看出,可以很方便的使用枚举了
结论:插入的是数字2,查询自动转换显示为’女’
6.3、 MybatisX 快速开发插件
MybatisX是一款基于IDEA的快速开发插件,为效率而生
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 MybatisX
搜索并安装
功能:
- Java与Xml调回跳转
- Mapper方法自动生成XML