您的位置:首页 > 编程语言 > Java开发

基于SpringBoot的Mybatis Plus使用笔记

2020-06-03 05:40 393 查看

Mybatis Plus

快速开始

mybatis plus可以说是一款惊艳到我的插件,mybatis plus对mybatis做了一个全面的提升,不需要再写麻烦的mapper.xml,并且自带Basemapper不需要开发者写大量重复的sql语句,使用起来非常简单,能够节省节省大量的时间。但其在一对多或多对一映射的友好程度还不及JPA,需要自己手动实现手写sql语句以及自定义类。不过整体来说已经大大降低了mybatis的使用门槛

引入依赖

一个是springboot的集成依赖,一个是数据库连接池依赖

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>

配置连接数据库的配置文件

写在

application.yaml
中,要记得加上时区的配置,不然会出错

spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
mode: HTML
datasource:
username: root
password: xxxxxxxx
url: jdbc:mysql://localhost:3306/db1?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver

创建数据库

DROP TABLE IF EXISTS user;

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)
);
DELETE FROM user;

INSERT INTO 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');

以上步骤完毕后,我们需要编写pojo类,对应数据库的字段,并且写一个操作接口

pojo类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private int age;
private String name;
private String email;
}

新建一个mapper文件夹,写一个接口继承BaseMapper,并将pojo的泛型引入

package com.zhou.mapper;

@Repository
public interface UserMapper extends BaseMapper<User> {
}

在运行类中加入

@MapperScan("com.zhou.mapper")

@SpringBootApplication
@ComponentScan("com.zhou")
@MapperScan("com.zhou.mapper")
@ServletComponentScan
public class SpringbootdemoApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}

}

因为Mybatis Plus已经帮我们完成了很多基本操作,因此我们只需要直接使用即可

这里使用了selectList,并且输出

package com.zhou.springbootdemo;

import com.zhou.entity.Account;
import com.zhou.entity.User;
import com.zhou.mapper.UserMapper;
import org.apache.catalina.core.ApplicationContext;
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 SpringbootdemoApplicationTests {
@Autowired
UserMapper userMapper;
@Test
void contextLoads() {
// wrapper参数,条件构造器,先不用写成Null
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}

}

配置sql日志 打印sql语句

我们把sql打印出来,查看执行了什么sql

只需要在

application.yaml
加上这个配置

mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

CRUD拓展

插入

为了保证id的唯一,我们可以不设置id的值,mybatis plus会默认使用

雪花算法
生成一个唯一ID

@Test
public void testInsert(){
User user = new User();
user.setAge(3);
user.setName("周哥");
user.setEmail("21312@qq.com");
userMapper.insert(user);
System.out.println(user);
}

如果需要更改生成id的策略,我们回到pojo类中,就可以更改了

默认是

IdType.ID_WORKER

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private int age;
private String name;
private String email;
}

更新 update

@Test
public void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("周周");
user.setEmail("21312@qq.com");
userMapper.updateById(user);
System.out.println(user);
}

mybatis plus通过条件自动拼接动态sql

自动填充

这边以日期为例子,一般数据库都要记录一条字段的创建时间和更新时间

先在pojo字段上边加上注解

@TableField

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private Integer age;
private String name;
private String email;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

创建handler文件夹,在里边新建MyHandler类之后改写一个MetaObjectHandler接口

一定要记得使用

@Component
将其托管,不然无法使用

@Slf4j
@Component
public class MyHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始填充");
// 这里将new Date()作为自动填充值
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}

@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}

再插入或者更新,就会自动填充当前时间节点值

乐观锁

乐观锁: 总是认为不会出现问题,无论干什么都不上锁,有问题再次更新值测试

悲观锁:总是认为会出现问题,各种上锁

实现方式:

  1. 取出记录的时候,获取当前version
  2. 更新的时候,带上这个version
  3. 执行更新时,set version = new Version where version = oldVersion
  4. 如果version不对,更新失败
线程A
update user set name = "zhou", version = version + 1
where id = 2 and version = 1;

线程B
update user set name = "zhou", version = version + 1
where id = 2 and version = 1;
  1. 先在数据库中加入version字段,并且使其默认值为1
  2. 实体类加对应字段
@Version
private Integer version;
  1. 注册组件,新建一个文件夹config,配置类

这些MapperScan的配置可以写在配置类之上

@EnableTransactionManagement
@MapperScan("com.zhou.mapper")
@Configuration
public class MybatisConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}

为了多线程保证正确,如果有一个线程抢先操作了,后来的线程不会覆盖前边的线程

@Test
public void testVersion(){
User user = userMapper.selectById(2);
user.setName("周杰伦");

User user2 = userMapper.selectById(2);
user2.setName("周杰伦222");
userMapper.updateById(user2);
userMapper.updateById(user);
}

查询操作

删除操作(delete)和这里同理,后边不赘述

  • 批量查询使用一个list来操作
  • 如果需要查询特定字段来查询,引入一个map即可
@Test
public void testVersion(){
User user = userMapper.selectById(2);
user.setName("周杰伦");

User user2 = userMapper.selectById(2);
user2.setName("周杰伦222");
userMapper.updateById(user2);
userMapper.updateById(user);
}
// 批量查询
@Test
public void testFindById(){
User user = userMapper.selectById(1);
System.out.println(user);

List<User> users = userMapper.selectBatchIds(List.of(1,2,3));
users.forEach(System.out::println);
}

// 条件查询
@Test
public void testFindById(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","周周");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}

分页

先在配置类中注册插件

@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}

直接使用即可

@Test
public void testPage(){
Page<User> userPage = new Page<>(2,5);
userMapper.selectPage(userPage, null);
userPage.getRecords().forEach(System.out::println);
}

逻辑删除

物理删除:直接从数据库中删除

逻辑删除:在数据库中没有被移除,而是通过一个变量让其失效,

deleted = 0
deleted = 1

管理员可以查看被删除的记录,防止数据库的丢失,类似于回收站

步骤:

  1. 在数据表中增加一个deleted字段,默认是0,长度为1
  2. 在Pojo类中增加一个字段,并加上
    @TableLogic
    注解
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private Integer age;
private String name;
private String email;
@Version
private Integer version;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

@TableLogic
private Integer deleted;
}
  1. application.yaml
    中配置逻辑删除
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:
db-config:
logic-delete-field: flag  #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

其实本质走的是更新操作,不是删除操作,因此会保留操作字段

UPDATE user SET deleted=1 WHERE id=1 AND deleted=0

删除之后,我们就不能通过select操作将其查询,因为查询时会自动拼接上deleted

性能分析插件

引入依赖 p6spy

<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.2</version>
</dependency>

因为这个是用于开发阶段,因此我们最好新建一个

application-dev.yaml
然后在
application.yaml
中激活该配置文件,在
application.yaml
中激活该配置

spring:
profiles:
active: dev

application-dev.yaml
写法

spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
mode: HTML
datasource:
username: root
password: xxxxxx
url: jdbc:p6spy:mysql://localhost:3306/db1?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.p6spy.engine.spy.P6SpyDriver

mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:
db-config:
logic-delete-field: flag  #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
server:
port: 9090

新建一个

spy.properties

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

之后就会在每个sql上输出执行时间

Wrapper 复杂查询

查询名字邮箱不为空并且年龄大于30的字段

public void testWrapper1(){
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.isNotNull("name").isNotNull("email").ge("age",30);
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}

查询名字等于特定值的字段

@Test
public void testWrapper1(){
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("name","Billie");
User user = userMapper.selectOne(userQueryWrapper);
System.out.println(user);
}

区间查询

SELECT id,age,name,email,version,create_time,update_time,deleted
FROM user WHERE deleted=0 AND (age BETWEEN 20 AND 30)
@Test
public void testWrapper3(){
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.between("age",20,30);
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}

模糊查询

@Test
public void testWrapper3(){
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.notLike("name","周").likeRight("email","t");
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}

代码自动生成

首先建立好规范的数据库,自动生成的意思就是根据数据库,建立一系列你需要的文件

例如pojo,controller,mapper,service

引入依赖

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
  1. 保留config中的所有文件,配置类需要自己写好
@EnableTransactionManagement
@MapperScan("com.zhou.mapper")
@Configuration
public class MybatisConfig {

@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}}
  1. 编写生成类 自动生成文件的代码如下
package com.zhou.springbootdemo;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
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.NamingStrategy;

import java.util.List;

public class Generator {
public static void main(String[] args) {
AutoGenerator mpg = new AutoGenerator();

// 1.全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
System.out.println(projectPath);
// 输出目录
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("zhou");
gc.setFileOverride(true);
gc.setOpen(false);
gc.setServiceName("%Service");
gc.setSwagger2(true);  //实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);

// 2.设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUsername("root");
dsc.setPassword("xxxxx");
dsc.setUrl("jdbc:mysql://localhost:3306/db1?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);

// 3. 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog");
pc.setParent("com.zhou");
pc.setEntity("entiry");
pc.setController("controller");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);

// 4.策略设置
// 策略配置
StrategyConfig strategy = new StrategyConfig();
// 重点,设置映射的表名
strategy.setInclude("user");
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
// 设置逻辑删除字段
strategy.setLogicDeleteFieldName("deleted");
// 自动填充字段
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
strategy.setTableFillList(List.of(createTime,updateTime));
// 乐观锁字段
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true); // Controller用rest风格
strategy.setControllerMappingHyphenStyle(true);

mpg.setStrategy(strategy);

mpg.execute();

}
}

执行该文件,就可以得到自动生成的各个结果,生成后注意以下几点

  1. 在接口加上

    @Repository

  2. 配置类的扫描需要扫描对

    @MapperScan("com.zhou")

    以下是自动生成结果

联表查询以及一对多,多对一查询

预备知识: 了解inner join

这部分需要我们自己写一个接口,并且需要自己实现sql语句,以下是实现步骤

先建立两张练习表,表示班级以及学生表,用以练习,两张表用class_id做一个连接,数据库中尽量不要出现外键,会大大降低查询速度,并且提高表之间的耦合度,不利于维护

class表

create table class
(
id int not primary key,
classname varchar(20) null
);
INSERT INTO `class` VALUES (1,'一班'),(2,'二班'),(3,'三班');

Student表

CREATE TABLE `student` (
`id` int(11) NOT NULL,
`name` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`class_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO `student` VALUES (1,'zhou',11,1),(2,'li',22,1),
(3,'wang',23,2),(4,'sam',23,2),(5,'jack',25,3);

随后我们自己定义自己需要的pojo类

class类 一对多

@Data
public class Class implements Serializable {
private static final long serialVersionUID=1L;
private Integer id;
private String classname;
// 用列表存储
private List<Student> studentList;

}

Student类 多对一

@Data

public class Student implements Serializable {
private static final long serialVersionUID=1L;
private Integer id;
private String name;
private Integer age;
private Integer classId;
// Class班级类
private Class classes;

}

MyStudentMapper和MyClassMapper接口,自己实现Basemapper接口,用注解可以直接写SQL

@Repository
public interface MyClassMapper extends BaseMapper<Class> {
@Select("select s.id,s.name,s.age,c.classname from student s inner join class c on s.class_id = c.id where c.id = #{id}")
List<Student> findAllStudentById(int id);
}
@Repository
public interface MyStudentMapper extends BaseMapper<Student> {
@Select("select s.id,s.name,s.age,c.classname from student s , class c where s.class_id = c.id")
List<Student> findAllStudent();

@Select("select s.id,s.name,s.age,c.classname from student s inner join class c on s.class_id = c.id where s.id = #{id}")
List<Student> findAllStudentById(int id);
}

我们直接将Sql写在注解内,用

#{ }
可以完成参数传递

@SpringBootTest
class SpringbootdemoApplicationTests {
@Autowired
MyStudentMapper mystudentMapper;

@Autowired
MyClassMapper myClassMapper;

@Test
void testFindAll() {
List<Student> allStudent = mystudentMapper.findAllStudent();
allStudent.forEach(System.out::println);
}

@Test
void testFindByIs() {
List<Student> allStudent = myClassMapper.findAllStudentById(1);
allStudent.forEach(System.out::println);
}

}

完成自动注入以及测试类编写,我们完成了多表查询以及一对多和多对一的操作,和传统的Mybatis相比,Mybatis Plus不需要配置复杂的Association映射关系以完成多对一或者一对多的配置,非常方便,以下是输出结果

查询学生班级信息


查询班级内的学生信息,例如一班

这个只是简单的表操作,复杂表也是同理

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: