MyBatis-Plus简介
MyBatis-Plus官网
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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操作实例
一.环境搭建
- 创建Mysql数据库
mybatis_plus - 创建数据表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`name` varchar(30) NOT NULL,
`password` varchar(20) NOT NULL,
`age` int(11) NOT NULL,
`email` varchar(50) NOT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
`version` int(11) DEFAULT NULL,
`deleted` tinyint(3) unsigned zerofill NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1266228686886125575 DEFAULT CHARSET=utf8;
3.插入数据
INSERT INTO `user` VALUES ('4', 'Sandy', '123456', '21', 'test4@baomidou.com', '2020-05-29 16:57:22', '2020-05-29 16:57:19', null, '001');
INSERT INTO `user` VALUES ('111', 'lucy', '123123', '52', '111@qq.com', '2020-05-29 16:57:27', '2020-05-29 16:57:13', null, '000');
INSERT INTO `user` VALUES ('1266228686886125570', 'lucy', '123123', '52', '111@qq.com', '2020-05-29 16:56:59', '2020-05-29 16:57:03', null, '000');
INSERT INTO `user` VALUES ('1266228686886125571', 'lucy', '123123', '52', '111@qq.com', '2020-05-29 17:25:54', '2020-05-29 17:25:52', null, '000');
INSERT INTO `user` VALUES ('1266228686886125572', 'lisi', '123123', '12', '111@qq.com', '2020-05-29 16:53:01', '2020-05-29 16:53:01', null, '000');
INSERT INTO `user` VALUES ('1266228686886125573', 'wu', '1111', '223', 'qw@qq.com', '2020-05-29 17:44:02', '2020-05-29 17:53:40', '1', '000');
INSERT INTO `user` VALUES ('1266228686886125574', 'zs', '11121', '53', 'qwq@qq.com', '2020-05-29 17:45:15', '2020-05-29 17:59:53', '3', '000');
4.引入依赖
添加:mybatis-plus-boot-starter、MySQL、lombok、
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
注意:引入 MyBatis-Plus 之后请不要再次引入 MyBatis 以及 MyBatis-Spring,以避免因版本差异导致的问题。
5.配置application.properties
#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
#mybatis-plus日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
注意:
1、这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为Spring Boot 2.1 集成了 8.0版本的jdbc驱动,这个版本的 jdbc 驱动需要添加这个后缀,
2、这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱动,之前的 com.mysql.jdbc.Driver 已经被废弃,否则运行测试用例的时候会有 WARN 信息
二.编写代码
1.实体类
创建包 entity 编写实体类 User.java(此处使用了 Lombok 简化代码)
@Data
public class User {
/*自增策略
AUTO(0),数据库ID自增,未设置主键类型
NONE(1),不用策率 用户自己输入ID,该类型可以通过自己注册自动填充插件进行填充
INPUT(2), 自己输入设置id值
ASSIGN_ID(3),
ASSIGN_UUID(4), UUID(4)字符串全局唯一ID (idWorker 的字符串表示)
*/
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String password;
private Integer age;
private String email;
/*数据库表中添加自动填充字段*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/*@Version实现乐观锁*/
@Version
@TableField(fill = FieldFill.INSERT) //在初始时自动赋值
private Integer version; //版本号
/*逻辑删除*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
}
1.1 主键策略
MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID
参考资料:分布式系统唯一ID生成方案汇总:
- 自增策略
- 要想主键自增需要配置如下主键策略
- 需要在创建数据表的时候设置主键自增
- 实体字段中配置 @TableId(type = IdType.AUTO)
/* - AUTO(0),数据库ID自增,未设置主键类型 - NONE(1),不用策率 用户自己输入ID,该类型可以通过自己注自动填充插件进行填充 - INPUT(2), 自己输入设置id值 - ASSIGN_ID(3), - ASSIGN_UUID(4), UUID(4)字符串全局唯一ID (idWorker 的字符串表示) */
1.2 自动填充
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:
实体上添加注解
/*数据库表中添加自动填充字段*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
乐观锁
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
数据库中添加version字段
ALTER TABLE `user` ADD COLUMN `version` INT
/*@Version实现乐观锁*/
@Version
@TableField(fill = FieldFill.INSERT) //在初始时自动赋值
private Integer version; //版本号
逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
数据库中添加 deleted字段
ALTER TABLE `user` ADD COLUMN `deleted` boolean
实体类添加deleted 字段
实体类添加version字段
/*逻辑删除*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
application.properties 加入配置
此为默认值,如果你的默认值和mp默认的一样,该配置可无
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
配置类 config
/**
* mydatis_plus配置
*/
@Configuration
@MapperScan("git.matrix.mapper")
public class BatisPlusConfig {
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
/**
* 逻辑删除 后续版本不需要再配置
* @return
*/
/* @Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}*/
/**
2
* SQL 执行性能分析插件
3
* 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
4
*/
/*
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//ms,超过此处设置的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}*/
}
创建自动填充类 handle
/**
* Mp自动填充项
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/*
添加数据
setFieldValByName 根据名称设置属性值
*/
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("version",1,metaObject);//默认为1
this.setFieldValByName("deleted",0,metaObject);
}
/*
修改时
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
2.创建mapper 接口
创建包 mapper 编写Mapper 接口: UserMapper.java
@Repository //表示持久层
public interface UserMapper extends BaseMapper<User> {
// List<User>测试时使用List与HashMap时创建
List<User> selectByMap(HashMap<Object, Object> map);
}
3.测试
注意:
IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行。
为了避免报错,可以在 dao 层 (mapper层)的接口上添加 @Repository 注
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
//查询所有值
@Test
public void testSelectList(){
//UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper
//所以不填写就是无任何条件
List<User> users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
//users.forEach(System.out::println);
}
//添加insert
@Test
public void testAddUser(){
User user=new User();
// user.setId(1266227290828828674L);
user.setName("lisi");
user.setPassword("123123");
user.setAge(12);
user.setEmail("111@qq.com");
int insert = userMapper.insert(user);
System.out.println("结果:"+insert);//影响的行数
System.out.println(user); //id自动回填,显示结果
}
/*修改*/
@Test
public void testUpdateById(){
User user=new User();
user.setId(3l);
user.setAge(56);
int result = userMapper.updateById(user);
System.out.println(result);
System.out.println(user);
}
/**
* 测试乐观锁
*/
@Test
public void testAdd(){
User user = new User();
user.setName("zs");
user.setPassword("11121");
user.setAge(13);
user.setEmail("qwq@qq.com");
int result = userMapper.insert(user);
System.out.println(result);
System.out.println(user);
}
@Test
/*先查后改 ,最后版本号加一 version=2*/
public void testOptimisticLocker(){
// 根据ID查询数据
User user = userMapper.selectById(1266228686886125574L);
//User user = userMappere.selectById(1266228686886125573L);
//进行修改
user.setAge(53);
int result = userMapper.updateById(user);
System.out.println(result);
System.out.println(user);
}
/*删除*/
/*物理删除 真实删除,将对应数据从数据库中删除,
/之后查询不到此条被删除数据*/
/*物理删除*/
@Test
public void testDelete(){
long id= 1266228686886125569L;
int result = userMapper.deleteById(id);
System.out.println("result"+result);
}
@Test
//批量删除
public void testDeleteBatchIds(){
int result = userMapper.deleteBatchIds(Arrays.asList(2l, 3l));
System.out.println(result);
}
//简单的条件查询删除
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Billie");
map.put("email", "test5@baomidou.com");
int result = userMapper.deleteByMap(map);
System.out.println(result);
}
/*逻辑删除(类似回收站,不会真的删除)
将对应数据中代表是否被删除字段状态修改为“被删除状态”,
之后在数据库中仍旧能看到此条数据记录*/
//逻辑删除 实质将deleted=1
@Test
public void testDeleted(){
int result = userMapper.deleteById(4l);
System.out.println(result);
}
//后续再mp查询中都会自动添加 deleted=0 条件
//SELECT id,name,password,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0
/**
* 简单查询
*/
//1,根据id查询
@Test
public void testSelectId(){
User user = userMapper.selectById(4L);
System.out.println(user);
}
//通过多个id批量查询
@Test
public void testSelectIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(2L, 3L, 4L, 5l));
System.out.println(users);
}
//通过Map封装名字密码查询数据
@Test
public void testUserAdnPassword(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","Billie");
map.put("password","123456");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
//map中的key对应的是数据库中的列名。
// 例如数据库user_id,实体类是userId,这时map的key需要填写user_id
}
/*MP分页查询*/
@Test
public void testPage(){
//1创建page对象
// 传入两个参数,当前页,每页显示的记录数
Page<User> page = new Page<>(2,3);
//调用mp分页查询的方法 selectPage --过程在mybatis-Plus底层封装
//将分页所有数据封装到了page对象中
userMapper.selectPage(page,null); //条件为空
//通过page对象获取分页数据
System.out.println("当前页"+page.getCurrent());//当前页
System.out.println("每页数据"+page.getRecords());//每页数据list集合
System.out.println("每页显示的记录数"+page.getSize());//每页显示的记录数
System.out.println("总记录数"+page.getTotal());//总记录数
System.out.println("总页数"+page.getPages());//总页数
System.out.println("是否有上一页"+page.hasPrevious());//是否有上一页
System.out.println("是否有下一页"+page.hasNext());//下一页
}