0%

Mybatis-plus

mybatis-plus 开发

一、 配置工作

开发环境:IDEA 2019 jdk8 maven 3.6 mybatis-plus 3.5.2

1.创个数据库,和表 数据库名—–demo;表名—-User

1
2
3
4
5
6
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');

image-20220927165659398

2.全局配置文件-导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>

<!--lombok用于简化实体类开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<!--mySql的驱动-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

3.下载 lombok 插件

4.springboot 配置文件

application.yml

1
2
3
4
# 数据源的类型
spring.datasource.type=com.zaxxer.hikari.util.DriverDataSource
# 驱动类型 如是mysql是5版本,就用 com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
spring:  
# 数据源信息
datasource:
# 数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 连接数据的库的信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
username: root
password: root

# 配置日志,打印执行的sql语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置mybatis-plus的全局设置
global-config:
db-config:
# 设置统一的主键生成策略
# 让主键自增的两种方法:1.直接在pojo中加@TableId(type = IdType.AUTO)
# 2.在在·全局配置中添加如下字段,并打开数据库主键自增
id-type: auto
# 设置实体类唆对应的统一前缀;如:数据库是t_user,实体类是User;需要该配置自动加上所有前缀,或在实体类通过注释指定表
# table-prefix: t_
# 配置类型别名所对应的包(mapper中就不用写全路径了)
type-aliases-package: com.example.demomybatisplus.pojo
# 扫描通用枚举
type-enums-package: com.example.demomybatisplus.enums

type-aliases-package: xml文件中 不需要写pojo的全路径名称了,如:
com.example.demomybatisplus.pojo.User -> User

二、 实体类

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**  
* 无参构造
* 有参构造
* get方法
* set方法
* 重写equals和hashcode方法
*/
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
}

简化一下
@Data注释在编译后会自动添加所有方法(除了有参方法要自己加)

1
2
3
4
5
6
7
8
9
10
import lombok.*;

@Data
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
}
// 和上面结果是一样的

altimage-20220927165748934+7 打开类结构 没有有参构造

image-20220927165820550

@Accessors:存取器,@Accessors用于配置getter和setter方法的生成结果,下面介绍三个属性
链式更有一种取巧,方便我更好的调用方法;与传统get、set方法区别在于返回类型是当前对象,而非基础类型,并且set方法有return,传统的set是没有return的
流式一般要看情况而定,在序列化和反序列化中,支持的是传统的get、set方法,就不适用于流式;如果需要以流式的方式来处理,那么就使用流式

fluent:流式
设置为true,则getter和setter方法的方法名都是基础属性名,且setter方法返回当前对象

1
2
3
4
5
6
7
8
9
10
11
12
@Data 
@Accessors(fluent = true)
public class User {
private Long id;
private String name;

// 生成的getter和setter方法如下,方法体略
public Long id() {}
public User id(Long id) {}
public String name() {}
public User name(String name) {}
}

chain:链式
意为链式的,设置为true,则setter方法返回当前对象

1
2
3
4
5
6
7
8
9
10
@Data 
@Accessors(chain = true)
public class User {
private Long id;
private String name;

// 生成的setter方法如下,方法体略
public User setId(Long id) {}
public User setName(String name) {}
}

prefix
意为前缀,用于生成getter和setter方法的字段名会忽略指定的前缀名(遵守驼峰命名)

1
2
3
4
5
6
7
8
9
10
11
12
@Data 
@Accessors(prefix = "p")
class User {
private Long pId;
private String pName;

// 生成的getter和setter方法如下,方法体略
public Long getId() {}
public void setId(Long id) {}
public String getName() {}
public void setName(String name) {}
}

三、 接口类

UserMapper

1
2
3
// 继承 BaseMapper 中的各种方法   BaseMapper 是mybatis-plus内置的
public interface UserMapper extends BaseMapper<User> {
}

改写启动类,添加扫描mapper文件 @MapperScan

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {

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

}

在mapper中添加@Mapper或@Repository注释,将接口注释为持久层。
使用@Mapper需要在启动类中@MapperScan(“com.example.demo.mapper”)
使用@Repository可以不用加mapper扫描

1
2
3
4
// 2个均为将接口注释为持久层的意思  
// @Mapper 不需要配置扫描地址,通过xml中的namespace里的接口地址,生成Bean后注入到service中
//@Mapper 需要在spring中配置扫描地址,生成Bean后注入到service中
// @Repository

四、 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTest
public class MybatisPlusTest {

@Autowired
// usermapper可能会有下划线报错,不影响,因为IOC容器只接受类,不接受接口;但在编译过程中不影响
// 想把下划线消除,回mapper文件加注释 @Repository
private UserMapper userMapper;

@Test
public void testSelectList() {
// 通过条件构造器查询一个list集合,若没有条件,就写null
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println); // forEach方法打印全部
}
}

小技巧:自动补全变量名

打完后 Alt + Enter 选择 introduce local variable

在配置文件中加日志配置

application.yml

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

测试类打印后,看到自动调用的sql语句

image-20220927165921433

五、 BaseMapper和IService

BaseMapper是Mapper层或者叫Dao层的接口。

IService是业务逻辑层接口。

差别:两者功能差不多,都封装了很多方法,只是作用的层不同;IService是对BaseMapper的扩展
IService依赖于spring容器,而BaseMapper不依赖。BaseMapper 可以继承并添加新的数据库操作,IService 要扩展的话还是得调用 Mapper,显得有些多此一举。

IService支持实现批量操作,BaseMapper不行
批量操作的实现实际是循环多次执行BaseMapper语句(就很呆)

1. 继承BaseMapper的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@SpringBootTest  
public class MybatisPlusTest {

@Autowired
private UserMapper userMapper;

@Test
public void testSelectList(){
// 通过构造器查询一个list集合,若无需判断条件就写null
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}

@Test
public void testInsert(){
// 新增
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
User user = new User();
user.setName("张三");
user.setAge(18);
user.setEmail("zhangsan@123.com");
int res = userMapper.insert(user);
System.out.println("result:"+res);
// id号很长,是通过雪花算法自动添加的唯一值
System.out.println("id:"+user.getId());
}

@Test
public void testDelete(){
// 通过id删除用户信息
// DELETE FROM user WHERE id=?
// userMapper.deleteById(1557240268732936193L); // id号超出int范围了,加L变为长整型

// 根据map集合中的条件删除用户信息
// DELETE FROM user WHERE name = ? AND age = ?
// Map<String, Object> map = new HashMap<>();
// map.put("name","张三");
// map.put("age",18);
// userMapper.deleteByMap(map);

// 通过多个id实现批量删除
// DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> list = Arrays.asList(1L, 2L, 3L); // 数据库里id设的是Long型
userMapper.deleteBatchIds(list);
}

@Test
public void testUpdate(){
// 修改用户信息
// UPDATE user SET name=?, age=? WHERE id=?
User user = new User();
user.setId(2L);
user.setName("李斯");
user.setAge(20);
userMapper.updateById(user);
}

@Test
public void testSelect(){
// 通过id查询
// SELECT id,name,age,email FROM user WHERE id=?
// userMapper.selectById(1L);

// 通过多个id查询多个用户信息
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
// List<Long> list = Arrays.asList(1L, 2L, 3L);
// userMapper.selectBatchIds(list);

// 根据map集合中的条件查询信息
// SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
// Map<String, Object> map = new HashMap<>();
// map.put("name","Jack");
// map.put("age",20);
// userMapper.selectByMap(map);

// 自定义的方法
userMapper.selectMapById(1L);
}
}

UserMapper里的自定义方法

1
2
// 自定义方法,根据id查询为map集合  
Map<String, Object> selectMapById(Long id);

xml语句

1
2
3
<select id="selectMapById" resultType="map">  
select id,name,age,email from user where id = #{id}
</select>

2. 带条件构造器(Wrapper)的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
@SpringBootTest  
public class MyBatisPlusWrapperTest {
@Autowired
private UserMapper userMapper;

@Test
public void test01(){
// 查询用户名包含a,年龄在20~30之间,邮箱不为空的
// SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ? AND email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","a") // column的内容是数据库的字段名
.between("age",20,30) // 链式编程的写法
. isNotNull("email");
userMapper.selectList(queryWrapper);
}

@Test
public void test02(){
// 查询用户信息,按照年龄的降序排序,若年龄相同,按id升序排序
// SELECT id,name,age,email FROM user ORDER BY age DESC,id ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("id");
userMapper.selectList(queryWrapper);
}

@Test
public void test03(){
// 删除邮箱地址为空的用户信息
// DELETE FROM user WHERE (email IS NULL)
// 如果有isDeleted字段,则该语句是自动执行修改语句
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
userMapper.delete(queryWrapper);
}

@Test
public void test04(){
// 修改(年龄大于20并且名字中包含a的)或邮箱为空的
// UPDATE user SET name=?, email=? WHERE (age > ? AND name LIKE ? OR email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// gt: > lt: < ge: >= le: <=
queryWrapper.gt("age",20)
.like("name","a")
.or()
.isNull("email");
User user = new User();
user.setName("小明");
user.setEmail("test@qq.com");
userMapper.update(user,queryWrapper);
}

@Test
public void test05(){
// 修改用户命中包含a并且(年龄大于20或邮箱为null)的用户信息
// UPDATE user SET name=?, email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","a")
// lambda表达式的内容会优先执行
.and(i->i.gt("age",20).or().isNull("email"));
User user = new User();
user.setName("小hong");
user.setEmail("test@qq.com");
userMapper.update(user,queryWrapper);
}

@Test
public void test07(){
// 修改用户命中包含a并且(年龄大于20或邮箱为null)的用户信息
// UPDATE user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("name","a")
.and(i -> i.gt("age",20).or().isNull("email"));
updateWrapper.set("name","小黑").set("email","abc@123.com");
userMapper.update(null,updateWrapper);
}

@Test
public void test10(){
// 与上面实现相同,lambda的作用只是避免写错字段名
// UPDATE user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.like(User::getName,"a")
.and(i -> i.gt(User::getAge,20).or().isNull(User::getEmail));
updateWrapper.set(User::getName,"小黑").set(User::getEmail,"abc@123.com");
userMapper.update(null,updateWrapper);
}

@Test
public void test06(){
// 查询用户名
// SELECT name FROM user
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name");
userMapper.selectMaps(queryWrapper);
}


@Test
public void test08(){
// 公司业务中常用
// 先判断条件是否符合,再组装相对应的条件到sql语句中
// SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age <= ?)
String name = "a";
Integer ageBegin = null;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(name),"name","name")
// isNotBlank 不为空字符串,不为null
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null, "age", ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}

@Test
public void test09(){
// 和上面执行是一样的,只是写法不同;可防止把字段名写错
// SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age <= ?)
String name = "a";
Integer ageBegin = null;
Integer ageEnd = 30;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(name),User::getName, name)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
}

3. 实现IService接口的得到的方法

UserService

1
2
3
// IService mybatis-plus提供的service接口  
public interface UserService extends IService<User> {
}

UserServiceImpl

1
2
3
4
@Service  
// ServiceImpl<M,T> IService的实现方法
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

IService中的很多方法作用和BaseMapper的一样,但是方法名不一样

IService支持实现批量操作,BaseMapper不行
批量操作的实现实际是循环多次执行BaseMapper语句(就很呆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@SpringBootTest  
public class MybatisPlusServiceTest {

@Autowired
private UserService userService;

@Test
public void testGetCount(){
// 查询总记录数
// SELECT COUNT( * ) FROM user
userService.count();
}

@Test
public void testInsertMore(){
// 批量添加:实际是IService通过循环调用baseMapper的单条添加语句
// INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
List<User> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setName("lc"+i);
user.setAge(20+i);
list.add(user);
}
// 返回值是 boolean boolean b = userService.saveBatch(list);
System.out.println(b);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IService的方法
//查询所有
userService .list();
//查询数量
userService .count();
//根据ID查list集合
userService .listByIds());
//根据ID删除
userService .removeById();
userService .removeByIds();
//修改
userService .update();
//新增
userService .save();

4. 实体类中常用的注释和如何实现字段枚举(逻辑删除字段)

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Data  
// 可以设置实体类对应的表名
//@TableName("t_user")
// 前缀名可在配置文件中统一去掉
public class User {

// 将属性对应的字段指定为主键;比方主键名为“uid”(主键不叫id),需要该标签帮助指定主键
// @TableId

// 设置主键生成策略,不使用雪花算法自增。数据库打开自增
// 默认是IdType.ASSIGN_ID,即雪花递增,与数据库是否开启递增无关
// 可以在配置文件中进行全局配置
// @TableId(type = IdType.AUTO)


private Long id;
// 可指定属性所对应的字段名;(如果属性和数据库的对应不是同名的话)
// @TableField("user_name") 将数据库中的user_name对应为name
private String name;
private Integer age;
private String email;

// 要在枚举类和配置文件配置
private SexEnum sex;

// 逻辑删除字段注释
// 删除时实际语句是修改功能,把isDeleted默认值0改为1
// 用法:可用于用户数据恢复;原理:数据库的数据还在,但无法被识别(查询)
// @TableLogic
// private Integer isDeleted;
}

逻辑删除的解释:
在数据库中添加isDeleted字段,默认值设为0.可在删除后将该字段数值改为1;便于以后恢复数据。
当执行查询语句时,只会会查看逻辑删除字段为0的。sql语句中会自动添加if语句的
当执行删除语句时,实际上执行的是修改操作,是把逻辑删除字段的0改为1

配置文件:在mybatis-plus:下

1
2
# 扫描通用枚举  
type-enums-package: com.example.demomybatisplus.enums

Project 枚举类


SexEnum
对性别进行枚举;数据库中放1,2;分别代表 男、女

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Getter  
public enum SexEnum {
MALE(1,"男"),
FEMALE(2,"女");

@EnumValue // 将注解所标识的属性的值存储到数据库中
private Integer sex;
private String sexName;

// 构造方法
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest  
public class MybatisPlusEnumTest {
@Autowired
UserMapper userMapper;

@Test
public void test(){
User user = new User();
user.setName("admin");
user.setAge(21);
user.setSex(SexEnum.MALE);
userMapper.insert(user);
}
}

5. 分页功能

分页功能:
配置类:(插件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration  
// 扫描mapper接口所在包,一样的,有配置类后可以把扫描放在这里
@MapperScan("com.example.demomybatisplus.mapper")
public class MyBatisPlusApplication {

@Bean
// MybatisPlusInterceptor-分页插件的使用
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

}
}

UserMapper

1
2
3
4
5
6
7
@Repository  
public interface UserMapper extends BaseMapper<User> {

// 通过年龄查询用户信息并分页(第一个参数必须是page)
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);

}

xml

1
2
3
4
<!--    Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);-->  
<select id="selectPageVo" resultType="User">
select * from user where age > #{age}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@SpringBootTest  
public class MyBatisPlusPluginsTest {

@Autowired
UserMapper userMapper;

@Test
// 用内置方法实现分页
public void testPage(){
// current-当前页页码 size-每页显示的几条数据
// SELECT id,name,age,email FROM user LIMIT ?
// Page<User> page = new Page<>(1,3);
// SELECT id,name,age,email FROM user LIMIT ?,?
Page<User> page = new Page<>(2,3);
userMapper.selectPage(page,null);
System.out.println(page);

System.out.println(page.getRecords()); // 获得当前页数据
System.out.println(page.getPages()); // 获得总页数
System.out.println(page.getTotal()); // 获得总记录数
System.out.println(page.hasNext()); // 判断是否有下一页
System.out.println(page.hasPrevious()); // 判断是否有上一页

}

@Test
// 对自定义的sql语句使用分页插件实现分页功能
public void testPageVo(){
// select * from user where age > ? LIMIT ?
Page<User> page = new Page<>(1,3);
userMapper.selectPageVo(page,20);

System.out.println(page.getRecords()); // 获得当前页数据
System.out.println(page.getPages()); // 获得总页数
System.out.println(page.getTotal()); // 获得总记录数
System.out.println(page.hasNext()); // 判断是否有下一页
System.out.println(page.hasPrevious()); // 判断是否有上一页
}
}

6. 乐观锁

配置类:(在刚才的配置类中添加新的插件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration  
// 扫描mapper接口所在包
@MapperScan("com.example.demomybatisplus.mapper")
public class MyBatisPlusApplication {

@Bean
// MybatisPlusInterceptor-分页插件的使用
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}

模拟场景:原商品定价100,老板让小李先+50,再让小王-30,预期的价格是120
解释:两人都查询原价后,进行操作,如果没有锁机制,小王得到的不是小李操作后的价格,结果会直接覆盖,最后结果为100-30

实体类:Product

1
2
3
4
5
6
7
8
@Data  
public class Product {
private Long id;
private String name; // 商品名
private Integer price;
@Version // 标记乐观锁版本号字段
private Integer version;
}

ProductMapper继承BaseMapper

1
2
3
@Repository  
public interface ProductMapper extends BaseMapper<Product> {
}

取出记录时,获取当前version

1
SELECT id,`name`,price,`version` FROM product WHERE id=1

更新时,version + 1,如果where语句中的version版本不对,则更新失败

1
2
3
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND

`version`=1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@SpringBootTest  
public class MyBatisPlusPluginsTest {

@Autowired
ProductMapper productMapper;


// 乐观锁和悲观锁测试
// 模拟场景,原商品定价100,老板让小李先+50,再让小王-30,预期的价格是120,但没有锁机制,小王会覆盖小李的结果,最后结果为100-30
// 添加乐观锁之后,最后结果为150,因为小李操作成功后,版本号改变,小王就没法操作
// 优化后可实现期待结果
@Test
public void testProduct01(){
// 小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询的商品价格:"+productLi.getPrice());
// 小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询的商品价格:"+productWang.getPrice());
// 小李将价格+50
productLi.setPrice(productLi.getPrice() + 50);
productMapper.updateById(productLi);
// 小王将价格-30
productWang.setPrice(productWang.getPrice() - 30);
// productMapper.updateById(productWang);
// 优化
int result = productMapper.updateById(productWang);
if (result == 0) { // 影响行数为0,说明版本号已改变,需要重新查询后再操作
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice()-30);
productMapper.updateById(productNew);
}
// 老板查询商品价格
Product productBoss = productMapper.selectById(1);
System.out.println("老板查询的商品价格:"+productBoss.getPrice());

}
}

六、 代码生成器

一个自动生成项目的,MVC三层可自动生成。个人觉得比较鸡肋,没啥卵用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**  
* 代码生成器,直接生成全新的mybatis-plus项目,把MVC三层全部生成。生成的位置是自己配置的 如:D://mybatis_plus
*/public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus? characterEncoding=utf-8&userSSL=false","root","root")
.globalConfig(builder -> {
builder.author("lc") // 设置作者
//.enableSwagger() // 开启swagger模式
.fileOverride() // 覆盖已生成文件(重新生成代码,覆盖之前的)
.outputDir("D://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.example") // 设置父包名
.moduleName("demomybatisplus") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml,"D://mybatis_plus")); // 设置mapperxml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_user") // 设置需要生成的表名
.addTablePrefix("t_","c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认是Velocity引擎模板
.execute();
}
}

七、mybatisX 真正的代码生成器

依赖:mybatis-plus的启动器,lombok,mysql

Setting中Plugins添加MybatisX插件

添加数据库进去:

版本可能太高,把数据库调低一点


在url的后半部分加:?createDatabaseIfNotExist=true&useSSL=false
否则会SSL协议报错

新项目中自动生成表的MVC三成结构
直接自己建也可以;但这样也方便了很多,xml语句中会自动写入resultMap

配置基础的路径

自定义一些方法在Mapper中

条件写好后 alt+enter 选择
会自动在xml中写好sql语句

报错是因为我xml不是自动生成,里头没有BaseResultMap表

八、对mybatis-plus的总结

plus是对mybatis的加强,很多自动生成工具非常好用,但这都建立在对基本手写没问题后,否则无法掌控。虽然BaseMapper和IService封装了很多方法,但我个人更倾向于使用MybatisX的自动生成sql工具,有问题了直接在sql语句里改