MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
官网:https://baomidou.com/
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]');
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.3.RELEASEversion>
<relativePath/>
parent>
<groupId>cn.yanghuisengroupId>
<artifactId>mp-demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>mp-demoname>
<description>Demo project for MyBatis-Plusdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.2version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
注:如果引入了MyBatis-Plus就不要在引入MyBtais,两者选一
修改application.yml文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mpdemo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
使用Lombok
减少代码
package cn.yanghuisen.mpdemo.entity;
import lombok.Data;
/**
* @author Y
* @date 2020/8/15 20:41
* @desc 用户实体类
*/
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
package cn.yanghuisen.mpdemo.mapper;
import cn.yanghuisen.mpdemo.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Y
* @date 2020/8/15 20:39
* @desc 用户Mapper接口
*/
public interface UserMapper extends BaseMapper<User> {
}
UserMapper接口继承BaseMapper,泛型参数为实体类对象
package cn.yanghuisen.mpdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Y
* @date 2020/8/15 20:38
* @desc 启动类
*/
@SpringBootApplication
@MapperScan("cn.yanghuisen.mpdemo.mapper")
public class MpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MpDemoApplication.class, args);
}
}
使用@MapperScan
指定扫描的mapper
接口路径
package cn.yanghuisen.mpdemo;
import cn.yanghuisen.mpdemo.entity.User;
import cn.yanghuisen.mpdemo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.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;
import java.util.List;
@SpringBootTest // 测试注解
@RunWith(SpringRunner.class) // 使用Spring环境
@Slf4j // 日志打印
class MpDemoApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void selectListTest() {
log.info("*****查询User列表*****");
List<User> userList = userMapper.selectList(null);
log.info("*****打印userList*****");
userList.forEach(System.out::println);
}
}
User(id=1, name=Jone, age=18, [email protected])
User(id=2, name=Jack, age=20, [email protected])
User(id=3, name=Tom, age=28, [email protected])
User(id=4, name=Sandy, age=21, [email protected])
User(id=5, name=Billie, age=24, [email protected])
到此就可以体会到MyBatis-Plus的强大魅力,只需要简单的配置就可以实现基本的CRUD操作。
开始日志,查看具体SQL命令
application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
表名注解
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 表名 |
schema | String | 否 | “” | schema |
keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(如果设置了全局 tablePrefix 且自行设置了 value 的值) |
resultMap | String | 否 | “” | xml 中 resultMap 的 id |
autoResultMap | boolean | 否 | false | 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建并注入) |
主键注解
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 主键字段名 |
type | Enum | 否 | IdType.NONE | 主键类型 |
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方法) |
ASSIGN_ID ) |
|
ASSIGN_UUID ) |
|
ASSIGN_ID ) |
字段注解(非主键)
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 数据库字段名 |
el | String | 否 | “” | 映射为原生 #{ ... } 逻辑,相当于写在 xml 里的 #{ ... } 部分 |
exist | boolean | 否 | true | 是否为数据库表字段 |
condition | String | 否 | “” | 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s} ,参考 |
update | String | 否 | “” | 字段 update set 部分注入, 例如:update="%s+1":表示更新时会set version=version+1(该属性优先级高于 el 属性) |
insertStrategy | Enum | N | DEFAULT | 举例:NOT_NULL: insert into table_a( |
updateStrategy | Enum | N | DEFAULT | 举例:IGNORED: update table_a set column=#{columnProperty} |
whereStrategy | Enum | N | DEFAULT | 举例:NOT_EMPTY: where |
fill | Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 |
select | boolean | 否 | true | 是否进行 select 查询 |
keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 进行处理 |
jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC类型 (该默认值不代表会按照该值生效) |
typeHandler | Class extends TypeHandler> | 否 | UnknownTypeHandler.class | 类型处理器 (该默认值不代表会按照该值生效) |
numericScale | String | 否 | “” | 指定小数点后保留的位数 |
FieldStrategy
值 | 描述 |
---|---|
IGNORED | 忽略判断 |
NOT_NULL | 非NULL判断 |
NOT_EMPTY | 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断) |
DEFAULT | 追随全局配置 |
FieldFill
字段填充策略
值 | 描述 |
---|---|
DEFAULT | 默认不处理 |
INSERT | 插入时填充字段 |
UPDATE | 更新时填充字段 |
INSERT_UPDATE | 插入和更新时填充字段 |
表字段逻辑处理注解(逻辑删除)
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 逻辑未删除值 |
delval | String | 否 | “” | 逻辑删除值 |
这里只是一部分查询接口,完整的查询接口请查看官方文档。
@Test
void insertTest(){
User user = new User();
user.setName("Ahh");
user.setAge(20);
user.setEmail("[email protected]");
userMapper.insert(user);
log.info(user.toString());
selectListTest();
}
调用insert方法进行数据插入,传入一个user对象。插入成功后会把生成的ID,回填到user对象中。
注:默认使用的是雪花算法生成对应的ID,如果要修改ID生成算法可以在实体类上添加注解@TableId
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
注:如果要使用auto自增主键的话,需要开启数据库的主键ID自增。
@Test
void updateTest(){
User user = new User();
user.setId(6L);
user.setName("啊哈哈");
user.setAge(20);
user.setEmail("[email protected]");
int i = userMapper.updateById(user);
log.info(i+"");
selectListTest();
}
注:updateById
传入的是user对象,不是具体的ID。
@Test
void selectUserByBatchId(){
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1,2,3,4,5));
userList.forEach(System.out::println);
}
@Test
void selectUserByMap(){
Map<String,Object> map = new HashMap<>();
map.put("id",1);
map.put("name","Jone");
List<User> userList = userMapper.selectByMap(map);
userList.forEach(System.out::println);
}
@Test
void deleteUserById(){
userMapper.deleteById(1302239794451763202L);
selectListTest();
}
删除同样有批量删除和map删除,使用方式和查询的一样
细心的就会发现,上面的CRUD都是一些简单的操作,如果是复杂的就没办法了,所以这时就要用到了条件构造器。
条件构造器有很多,具体的还请查看官方网文档,这里只会列举几个使用方法。
查询name不为空的用户,并且邮箱部位空的用户
@Test
public void isNotNullTest(){
// 查询name不为空的用户,并且邮箱部位空的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
// name不为null
wrapper.isNotNull("name");
// email部位null
wrapper.isNotNull("email");
userMapper.selectList(wrapper).forEach(System.out::println);
}
查询age大于等于22的用户
@Test
public void geTest(){
// 查询age大于等于22的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.ge("age",22);
userMapper.selectList(wrapper).forEach(System.out::println);
}
查询name等于Jone的用户
@Test
public void eqTest(){
// 查询name等于Jone的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","Jone");
System.out.println(userMapper.selectOne(wrapper));
}
selectOne:只查询一条数据
查询年龄在20到25之间的用户的数量
@Test
public void betweenTest(){
// 查询年龄在20到25之间的用户的数量
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,25);
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
selectCount:查询数量
@Test
public void likeTest(){
// 查询name中包含a的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name","a");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
selectMaps:返回List
形式的数据
查询name中不包含a的用户
@Test
public void notLikeTest(){
// 查询name中不包含a的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name","a");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
查询name中以e结尾的用户
@Test
public void likeLeftTest(){
// 查询name中以e结尾的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeLeft("name","e");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
查询name中以j开头的用户
@Test
public void likeRightTest(){
// 查询name中以j结开头的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeRight("name","j");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
id 在子查询中查询出来
@Test
public void inSqlTest(){
// id 在子查询中查询出来
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id < 5");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
selectObjs:返回List
形式的数据
根据id进行降序排序
@Test
public void orderByDescTest(){
// 根据id进行降序排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
MyBatisPlus也提供了分页插件,但是需要稍微的配置一下
在配置类中添加分页插件
package cn.yanghuisen.mpdemo.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Y
* @date 2020/9/6 15:57
* @desc MyBatisPlus配置类
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
测试分页
@Test
void pageTest(){
// 第1页,5条数据
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
log.info("总条数:"+page.getTotal());
}
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
MyBatisPlus
在 3.0.3
版本之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.0version>
dependency>
编码
package cn.yanghuisen.mpdemo;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.ArrayList;
/**
* @author Y
* @date 2020/9/6 19:01
* @desc 代码生成
*/
public class CodeGeneration {
public static void main(String[] args) {
// 构建一个代码生成器对象
AutoGenerator autoGenerator = new AutoGenerator();
// 配置策略
GlobalConfig gc = new GlobalConfig();
// 获取项目路径
String projectPath = System.getProperty("user.dir");
// 设置输出目录
gc.setOutputDir(projectPath+"src/main/java");
// 设置作者
gc.setAuthor("啊哈哈");
// 是否打开资源管理器,false,不打开
gc.setOpen(false);
// 是否覆盖
gc.setFileOverride(false);
// service名称
gc.setServiceName("%sService");
// ID类型
gc.setIdType(IdType.ASSIGN_ID);
// 日期类型
gc.setDateType(DateType.ONLY_DATE);
// 设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mpdemo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
// 包的配置
PackageConfig pc = new PackageConfig();
// 设置模块名称
pc.setModuleName("demo");
// 设置包名
pc.setParent("cn.yanghuisen.mpdemo");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("t_user");
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 自动lombok
strategy.setEntityLombokModel(true);
// 逻辑删除
strategy.setLogicDeleteFieldName("flag");
// 自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> fills = new ArrayList<>();
fills.add(createTime);
fills.add(updateTime);
strategy.setTableFillList(fills);
// 乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
// 把全局配置设置到代码生成器中
autoGenerator.setGlobalConfig(gc);
// 把数据源设置到代码生成器中
autoGenerator.setDataSource(dsc);
// 把包配置设置到代码生成器中
autoGenerator.setPackageInfo(pc);
// 把策略配置设置到代码生成器中
autoGenerator.setStrategy(strategy);
// 执行
autoGenerator.execute();
}
}
在阿里巴巴开发手册中明确规定,所有的数据库表中都应该含有create_time
和update_time
两个字段(用于表示该条记录的创建和修改时间)。通过自动填充可以自动化的设置这两个字段的值,避免每次都要手动设置。自动填充有两种方式。
数据库级别的就很简单了,给表中添加create_time
和update_time
字段后,设置下就好了。不同的数据库工具设置方法不一样,这里以Navicat
为例。
不建议使用这种方式,一般工作中不允许随意改变数据库
注:代码级别设置自动填充的话,需要关闭数据库级别的自动填充(取消Navicat中的根据当前时间戳更新)
通过@TableField
注解设置填充策略。
修改实体类,添加@TableField
注解,设置填充策略
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
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;
}
编写自动填充处理器类
package cn.yanghuisen.mpdemo.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author Y
* @date 2020/9/6 15:21
* @desc 自动填充处理器类
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时的填充策略
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
/**
* 更新时的填充策略
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
这样在进行插入和更新操作时,就可以自动填充时间到数据库了。
乐观锁
,顾名思义,很乐观。就是它总会知道自己不会出现问题,无论干什么都不会上锁。如果出现问题,再次更新测试。
悲观锁
,顾名思义,很悲观。就是它做什么都会上锁,再去操作。
乐观锁在数据库中一般使用version
字段表示。
给数据库表添加version
字段,默认值为1
实体类添加version
属性,并给其添加@Version
注解
package cn.yanghuisen.mpdemo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
/**
* @author Y
* @date 2020/8/15 20:41
* @desc 用户实体类
*/
@TableName("user")
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
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;
/**
* 乐观锁
*/
@Version
private Integer version;
}
创建配置类,注册乐观锁插件
package cn.yanghuisen.mpdemo.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Y
* @date 2020/9/6 15:57
* @desc MyBatisPlus配置类
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
物理删除:直接把数据中的数据删除
逻辑删除:在数据中没有被删除,而是通过一个变量(字段)来表示其失效(删除)状态
添加flag
字段(用于表示是否是删除状态),类型为int
,默认值为0
实体类中添加flag
属性,并且设置注解@TableLogic
package cn.yanghuisen.mpdemo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
/**
* @author Y
* @date 2020/8/15 20:41
* @desc 用户实体类
*/
@TableName("user")
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
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;
/**
* 乐观锁
*/
@Version
private Integer version;
/**
* 逻辑删除
*/
@TableLogic
private Integer flag;
}
在application.yml
进行配置逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 逻辑已经删除的值(默认为1)
logic-not-delete-value: 0 # 逻辑没有删除的值(默认为0)
测试逻辑删除
@Test
void deleteUserById(){
userMapper.deleteById(7L);
selectListTest();
}
注:在添加逻辑删除后,在查询时会自动过滤掉逻辑删除的记录。