根据名字我们就可以看出来MybatisPlus是Mybatis的增强版,事实上也是如此,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 操作智能分析阻断,也可自定义拦截规则,预防误操作 |
CREATE TABLE 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)
);
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2b8
driver-class-name: com.mysql.cj.jdbc.Driver
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
@Mapper
让我们的容器能够扫描到,同时需要继承BaseMapper@Repository
@Mapper
public interface UserMapper extends BaseMapper<User> {
//extends BaseMapper===》CRUD操作已经做完
}
我们Mapper接口一个方法都没有写,只是继承了BaseMapper类,我们在测试类中自动装配Mapper接口,可以看到下图MybatisPlus给我们实现的CRUD操作。
简单测试一下查询所有用户操作:
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//查询全部用户
// 参数是一个 wrapper ,条件构造器,这里我们先不用nuLl
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
}
运行结果:
#配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
延用上面程序,进行插入操作:
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setName("高朗");
user.setAge(18);
user.setEmail("[email protected]");
userMapper.insert(user);
}
}
可以看到我没有设置ID,也就是数据库的主键,运行:
我们可以看到程序为我们自动生成了一个ID,这就是Mybatis-Plus的主键生成,那么为什么会生成一个这样的ID,其实内部实现采用雪花算法。
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是∶使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。
采用注解形式:在主键上加@TableId
注解,并设置注解的type值:
可以设置的type值:
IdType:
值 | 描述 |
---|---|
AUTO | 数据库ID自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert前自行set主键值 |
ASSIGN_ID | 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法) |
ASSIGN_UUID | 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法) |
分布式全局唯一ID 长整型类型(please use ASSIGN_ID) | |
32位UUID字符串(please use ASSIGN_UUID) | |
分布式全局唯一ID 字符串类型(please use ASSIGN_ID) |
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
注意这里主键自增需要和数据库主键相同,也就是说数据库中主键也需要设置为自增,不然会报错。
运行上面插入的测试代码:
发现sql语句中没有Id,打开数据库表就可以发现ID是自增的:
如果使用IdType.NONE策略,表示未设置主键类型。
自定义IdType.INPUT策略,表示我们需要设置主键的值,不然不会生成为null。
默认的策略,主键类型Long或String。
如果使用IdType.ASSIGN_UUID策略,则会自动生成不含中划线的UUID作为主键,主键类型为String。
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setId(6L);
user.setName("小明");
user.setAge(19);
userMapper.updateById(user);
}
}
某条数据的创建时间和更新时间,我们不希望自己动手写,就可以用到自动填充。
ALTER TABLE user ADD create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间';
ALTER TABLE user ADD update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间';
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
private Date createTime;
private Date updateTime;
}
(a)更新操作:
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setId(6L);
user.setName("潇潇");
user.setAge(22);
userMapper.updateById(user);
}
}
运行代码,查看数据库表,我们更新的数据的update_time字段自动更新为电脑的时间
(b)增加操作
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setName("高朗");
user.setAge(22);
user.setEmail("[email protected]");
userMapper.insert(user);
}
}
运行程序,查看数据库表,create_time和update_time两个字段为电脑时间:
ALTER TABLE `user` DROP COLUMN create_time;
ALTER TABLE `user` DROP COLUMN update_time;
ALTER TABLE user ADD create_time datetime COMMENT '创建时间';
ALTER TABLE user ADD update_time datetime COMMENT '更新时间';
@TableField(fill = FieldFill.XXX)
FieldFill 枚举:
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
实体类注解:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject):字段名,放入字段名的内容,MetaObject类
//插入时,两个字段都需要更新
log.info("start insert fill ....");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
//更新时,更新字段需要更新
log.info("start update fill ....");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
(a)插入操作
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setName("Linda");
user.setAge(18);
user.setEmail("[email protected]");
userMapper.insert(user);
}
}
运行:
(b)更新操作
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setId(10L);
user.setAge(20);
user.setEmail("[email protected]");
userMapper.updateById(user);
}
}
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
ALTER TABLE `user` ADD version INT(5) DEFAULT 1
@Version
注解@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Integer version;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
@Configuration
public class MybatisPlusConfig {
//乐观锁添加方式一:
@Bean
public MybatisPlusInterceptor MybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//乐观锁
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//分页设置
//mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
// //乐观锁添加方式二:
// @Bean
// public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor(){
// return new OptimisticLockerInnerInterceptor();
// }
}
(a)成功测试:
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
//Step1:查询用户
User user = userMapper.selectById(10L);
//Step2:修改用户
user.setName("小明");
user.setAge(18);
userMapper.updateById(user);
}
}
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
//Step1:对同一个用户更新操作
User user = userMapper.selectById(10L);
User user2 = userMapper.selectById(10L);
//Step2:修改用户1
user.setName("高朗");
user.setAge(17);
//Step3:修改用户2
user2.setName("Linda");
user2.setAge(16);
userMapper.updateById(user);
userMapper.updateById(user2);
}
}
结果:第二条更新没有操作。(第一条更新完后,版本加一,3,而第二条更新时版本为2对应不了数据库中的版本3,更新失败。
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
//根据ID查询
@Test
public void selectTest(){
//查询ID为1的
User user = userMapper.selectById(1);
System.out.println(user);
}
//批量查询
@Test
public void selectTest2(){
//查询ID为1,2,3的
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
userList.forEach(System.out::println);
}
//条件查询 map
@Test
public void selectTest3(){
HashMap<String, Object> map = new HashMap<>();
//查询年龄为20的用户 age为数据库的字段名
map.put("age",20);
List<User> userList = userMapper.selectByMap(map);
userList.forEach(System.out::println);
}
}
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor MybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//乐观锁
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//分页设置
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void PageSelect(){
//参数1:当前页数 参数二:页面大小
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page, null);
//打印当前页的数据
page.getRecords().forEach(System.out::println);
//打印数据库中的数据总数
System.out.println(page.getTotal());
}
}
和查询操作类似,有根据ID删除,根据ID批量删除,map根据条件删除:
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
//根据ID删除
@Test
public void deleteTest(){
//删除ID为1的
userMapper.deleteById(1);
}
//批量删除
@Test
public void deleteTest2(){
//查询ID为2,3,4的
userMapper.deleteBatchIds(Arrays.asList(2, 3, 4));
}
//条件删除 map
@Test
public void deleteTest3(){
HashMap<String, Object> map = new HashMap<>();
//删除年龄为20的用户 age为数据库的字段名
map.put("age",20);
userMapper.deleteByMap(map);
}
}
(1)物理删除︰从数据库中直接移除。
(2)逻辑删除︰在数据库中没有被移除,而是通过一个变量来让他失效!
(3)相当于回收站,逻辑删除后,等同于放入回收站,回收站是可以恢复的。
(4)数据库表中可以添加一个deleted字段,默认值为0,逻辑删除后为1。
(1)数据库中加一个deleted字段
ALTER TABLE `user` ADD deleted INT(5) DEFAULT 0
(2)实体类加对应属性,并添加乐观锁@@TableLogic
注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Integer version;
@TableLogic
private Integer deleted;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
(3)配置
application.properties
# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
(4)测试:
//根据ID删除
@Test
public void deleteTest(){
//删除ID为1的
userMapper.deleteById(1);
}
运行:后台的操作变成了更新操作,而不是删除,而且对应的数据的deleted属性被修改成1
前面是逻辑删除,我们看看查询这个ID为1的数据,是否能够查询到:
//根据ID查询
@Test
public void selectTest(){
//查询ID为1的
User user = userMapper.selectById(1);
System.out.println(user);
}
运行:查询不到,后台查询语句帮我们加了一个deleted=0的条件