MyBatis-Plus
1.MyBatis-Plus简介
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2.MyBatis-Plus的特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
3.MyBatis-Plus的框架结构
4.MyBatis-Plus入门案例
- 创建数据库并创建表,向表中插入数据
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, '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');
- 利用初始化向导搭建SpringBoot环境
项目结构
- 引入相关依赖
<!-- SpringBoot场景启动器-->
<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>
<!-- MyBatisPlus场景启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- Mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!-- Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
- 创建application.yaml配置文件并配置数据源和连接数据库的相关信息
spring:
datasource:
# 配置SpringBoot默认的数据源
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库的各个信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
- 创建实体类
@Data
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- 创建mapper接口并继承BaseMapper接口(接口可以继承接口)
public interface UserMapper extends BaseMapper<User> {
}
在BaseMapper类中,已经有定义好的增删改查SQL
- 在主程序类中添加MapperScan注解用于批量扫描Mapper
- 测试
在测试类中自动注入创建的接口动态生成的代理类,并调用mybatis-plus中提供的方法
查询出user列表
@Autowired(required = false)
private UserMapper userMapper;
@Test
public void findAllUser(){
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
5.添加日志功能
在SpringBoot的配置文件当中添加mybatis-plus的日志功能
选择默认日志功能就行
添加日志功能后会显示sql语句
6.BaseMapper分析
实体类Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
public interface BaseMapper<T> extends Mapper<T> {
/* 插入一条记录*/
int insert(T entity);
/*** 根据 ID 删除*/
int deleteById(Serializable id);
/*** 根据实体(ID)删除*/
int deleteById(T entity);
/*** 根据 columnMap 条件,删除记录*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/*** 根据 entity 条件,删除记录*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/*** 删除(根据ID或实体 批量删除)*/
int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList);
/*** 根据 ID 修改*/
int updateById(@Param(Constants.ENTITY) T entity);
/*** 根据 whereEntity 条件,更新记录*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
/*** 根据 ID 查询*/
T selectById(Serializable id);
/*** 查询(根据ID 批量查询)*/
List<T> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);
/*** 查询(根据 columnMap 条件*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/*** 根据 entity 条件,查询一条记录*/
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
List<T> ts = this.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(ts)) {
if (ts.size() != 1) {
throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records");
}
return ts.get(0);
}
return null;
}
/* 根据 Wrapper 条件,判断是否存在记录*/
default boolean exists(Wrapper<T> queryWrapper) {
Long count = this.selectCount(queryWrapper);
return null != count && count > 0;
}
/*** 根据 Wrapper 条件,查询总记录数*/
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/*** 根据 entity 条件,查询全部记录*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/*** 根据 Wrapper 条件,查询全部记录 */
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/*** 根据 Wrapper 条件,查询全部记录*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/*** 根据 entity 条件,查询全部记录(并翻页*/
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/*** 根据 Wrapper 条件,查询全部记录(并翻页)*/
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
7.实现最基本的CRUD功能
7.1添加功能
@Test
public void addUser(){
// 创建user对象
User user = new User(null,"班尼特",18,"2533694604@qq.com");
int i = userMapper.insert(user);
System.out.println(i);
}
日志打印出sql语句
7.2删除功能
根据id删除
@Test
public void deleteUser(){
int i = userMapper.deleteById(6);
System.out.println(i);
}
日志打印出sql语句
根据Map集合中所设置的条件进行删除
@Test
public void deleteUser2(){
Map<String,Object> map = new HashMap();
map.put("name","钟离");
map.put("age",24);
int i = userMapper.deleteByMap(map);
System.out.println(i);
}
根据List集合中的id实现批量删除
@Test
@Transactional
public void deleteUser3(){
List<Long> list = Arrays.asList(1l,2l,3l);
int i = userMapper.deleteBatchIds(list);
System.out.println(i);
}
7.3修改功能
@Test
@Transactional
public void updateUser(){
User user = new User(5L,"钟离",22,"2533694604@qq.com");
int i = userMapper.updateById(user);
System.out.println(i);
}
7.4查询功能
@Test
public void selectUser(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
其他查询功能和其他功能类似
8.自定义功能
有些时候BaseMapper接口提供的CRUD方法并不能满足我们的需求,那这样的情况下就需要我们自定义功能
- 首先创建mapper映射文件夹,并且在映射文件夹里创建接口的映射文件
- 在接口中添加方法,并且在mapper映射文件中添加相应的映射
public interface UserMapper extends BaseMapper<User> {
// 根据id查询用户信息为map集合
public Map<String,Object> selectMapById(Long id);
}
<mapper namespace="cn.edu.pdsu.mybatisplus.mapper.UserMapper">
<!--public Map<String,Object> selectMapById();-->
<select id="selectMapById" resultType="java.util.Map">
select id,name,age,email from user where id = #{id}
</select>
</mapper>
9.通用的Service CRUD接口
9.1对IService的概述
说明:
通用 Service CRUD 封装IService接口,进一步封装 CRUD,采用 get 查询单行,remove删除,list 查询集合,page分页。前缀命名方式区分 Mapper层避免混淆,泛型T为任意实体对象。
IService:MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑 详情查看源码IService和ServiceImpl
Service层接口可以继承IService接口,IService是顶级接口。里面同样有很对多方法
userSerivce继承了IService,userServiceImpl实现了userService,由于IService接口中含有很多方法,userServiceImpl就要实现IService中的全部方法吗?
UserService接口继承IService接口(添加实体类泛型)
public interface UserService extends IService<User> {
}
UserServiceImpl实现类实现UserService接口,并继承IService的实现类ServiceImpl(添加实体类泛型)
答案是继承顶级接口IService的实现类ServiceImpl
ServiceImpl类中需要定义泛型<Mapper接口,JavaBean>
9.2 查询表中的记录数
创建测试类,自动化注入UserService,并测试表中的记录数
使用聚合函数sum求和
public void testSum() {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.select("IFNULL(sum(score),0) as totalScore")
.eq("playlist_id", 1);
Map<String,Object> map = ranksService.getMap(queryWrapper);
System.out.println(map.get("totalScore"));
}
SELECT IFNULL(sum(score),0) as totalScore
FROM ranks WHERE (playlist_id = ?)
9.3 批量添加
在BaseMapper中没有批量添加的功能
而在IService中含有批量添加的功能,泛型为List集合
测试
@Test
@Transactional
public void insertBatch(){
List list = new ArrayList<User>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setName("宵宫"+i);
user.setAge(20+i);
list.add(user);
}
boolean b = userService.saveBatch(list);
System.out.println(b);
}
批量插入成功
10.mybatis-plus常用注解
10.1@TableName
作用:@TableName注解主要是实现实体类型和数据库中的表实现映射。
首先提出一个问题,MyBatisPlus是如何根据JavaBean来对应数据库中的表名呢,看看Mapper接口
数据库中的表名
可以看出BaseMapper类定义的泛型的表名一致,MyBatisPlus就是这样对应数据库中的表名的
但当JavaBean与数据库表名不一致时
解决方案:
- @TableName注解,可以指定JavaBean对应的表名
- 在配置文件中全局配置表前缀
即每次在mapper中操作表时,都会添加统一前缀“t_”
# 配置全局表统一前缀
global-config:
db-config:
table-prefix: t_
10.2 @TableId
作用:@TableId注解是专门用在主键上的注解
- 若表中的主键名不是id,则可以使用@TableId在实体类中指定主键
- @Tableid注解的value属性
如果数据库中的主键字段名和实体中的属性名,不一样且不是驼峰之类的对应关系,可以在实体中表示主键的属性上加@TableId注解,并指定@Tableid注解的value属性值为表中主键的字段名既可以对应上。
- @Tableid注解的type属性
mybatis-plus默认生成主键的策略是“雪花算法”,而可以通过type属性来更改默认生成策略
常用的主键策略:
值 | 描述 |
---|---|
ldType.ASSIGN_ ID (默认) | 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 |
ldType.AUTO | 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增, 否则无效 |
即要在实体类中设置主键自动递增,则在表中也需要设置主键自动递增
- 通过全局配置主键生成策略
则实体类中不需要再添加type属性
10.3 @TableField注解
作用:@TableField注解是用来解决实体类中的属性名和字段名不一致的情况
如实体类中的其中的一个属性名为userName,而数据库表中的字段为user_name
mybatis-plus中会默认配置实体类驼峰对应数据库表中字段的下划线,而在mybatis中要进行手动配置
而如果实体类中属性名与数据库表中字段名不一样的情况应该采用@TableField注解来解决
10.4 @TableLogic注解
作用:@TableLogic注解表示逻辑删除
逻辑删除和物理删除的概念
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
使用场景:可以进行数据恢复
11.条件构造器wrapper
11.1Wrapper简介
BaseMapper接口中的条件构造器wrapper,用来封装条件。功能是select …from … where 中的where部分
- Wrapper :条件构造抽象类,最顶端父类
- AbstractWrapper :用于查询条件封装,生成sql的where条件
- QueryWrapper :查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper :使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda更新封装Wrapper
11.2 条件构造器queryWrapper(常用查询删除功能)
11.2.1组装查询条件
调用selectList方法,其参数是queryWrapper类型
设置查询条件
@Test
public void test01(){
// 查询用户名包含叶,年龄在20-30之间,邮箱信息不为空的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","叶").
between("age",10,20).
isNotNull("email");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
生成的SQL语句
SELECT uid AS id,name AS userName,
age,email FROM user
WHERE (name LIKE ?
AND age BETWEEN ? AND ?
AND email IS NOT NULL)
11.2.2 组装排序条件
@Test
public void test02(){
// 查询用户信息,按照年龄降序排列,若年龄相同,则按照id升序排列
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.orderByDesc("age").
orderByAsc("uid");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
生成的SQL语句
SELECT uid AS id,name AS userName,age,email
FROM user
ORDER BY age DESC,uid ASC
11.2.3组装删除条件
参数同样是queryWrapper类型
@Test
public void test03() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int delete = userMapper.delete(queryWrapper);
System.out.println(delete);
}
DELETE FROM user WHERE (email IS NULL)
11.2.4组装修改条件
第一个参数:实体类对象设置要修改的字段和修改的值
@Test
public void test04() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 修改年龄大于18,并且用户名中含有叶,或则邮箱为空的用户
queryWrapper.gt("age",20)
.like("name","叶")
.or()
.isNull("email");
User user = new User();
user.setUserName("叶天帝");
int update = userMapper.update(user, queryWrapper);
System.out.println(update);
}
UPDATE user SET name=?
WHERE (age > ? AND name LIKE ? OR email IS NULL)
11.2.5组装select子查询
@Test
public void test05(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name","age");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
SELECT name,age FROM user
11.2.6组装子查询
@Test
public void test06(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid","select uid from user where age < 20");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
sql语句
SELECT uid AS id,name AS userName,age,email
FROM user WHERE
(uid IN (
select uid from user where age < 20))
11.3 条件构造器updateWrapper
11.3.1修改功能
其有两个功能,可以设置修改的条件和设置修改的字段
@Test
public void test07(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper();
// 修改年龄大于18,并且用户名中含有叶,或则邮箱为空的用户
updateWrapper.gt("age",18)
.like("name","叶")
.or()
.isNull("email");
updateWrapper.set("name","枫原万叶");
int update = userMapper.update(null, updateWrapper);
System.out.println(update);
}
UPDATE user SET name=? WHERE
(age > ? AND name LIKE ? OR email IS NULL)
11.4模拟开发中组装条件的情况(动态拼接SQL)
11.4.1常规方式
@Test
public void test08(){
String username = "";
Integer ageBegin = 10;
Integer ageEnd = 20;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(username)){
// isNotBlank判断某个字符串是否为空或则空白符
queryWrapper.like("name",username);
}
if (ageBegin != null){
queryWrapper.ge("age",ageBegin);
}
if (ageEnd != null){
queryWrapper.le("age",ageEnd);
}
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
sql语句
SELECT uid AS id,name AS userName,age,email
FROM user WHERE (age >= ? AND age <= ?)
11.4.1使用condition组装条件的方式
从以上可以看出,每个函数都有两种形式,第二种方式的参数condition用于条件判断,若为true则执行,false不执行。
简化常规方式
@Test
public void test09(){
String username = "";
Integer ageBegin = 10;
Integer ageEnd = 20;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),"name",username)
.ge(ageBegin != null,"age",ageBegin)
.le(ageEnd != null,"age",ageEnd);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
sql语句
SELECT uid AS id,name AS userName,age,email
FROM user WHERE (age >= ? AND age <= ?)
11.3 条件构造器LambdaQueryWrapper
用于防止字段名写错的情况
@Test
public void test10(){
String username = "";
Integer ageBegin = 10;
Integer ageEnd = 20;
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
queryWrapper.like(StringUtils.isNotBlank("username"),User::getUserName,username)
.ge(ageBegin != null,User::getAge,ageBegin)
.le(ageEnd != null,User::getAge,ageEnd);
List list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
11.4 条件构造器LambdaUpdateWrapper
12.分页插件的配置和使用
12.1分页插件的配置步骤
- 创建mybatis-plus分页插件的配置类
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// DbType表示数据库类型
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
page参数
12.2 对所有数据进行查询分页(selectPage函数的第二个参数为null)
其中selectPage方法
根据 entity 条件,查询全部记录(并翻页)
Params:
- page – 分页查询条件
- queryWrapper – 实体对象封装操作类(可以为 null)
@Test
public void testPage() {
Page<User> page = new Page<>(1, 3);
userMapper.selectPage(page, null);
// 当前页码
System.out.println(page.getCurrent());
// 总页数
System.out.println(page.getPages());
// 每页显示的条数
System.out.println(page.getSize());
// 总记录数
System.out.println(page.getTotal());
// 获取当前页数据
System.out.println(page.getRecords());
// 判断是否有上一页
System.out.println(page.hasPrevious());
// 判断是否有下一页
System.out.println(page.hasNext());
}
12.3 采用条件构造器进行条件分页(selectPage函数的第二个参数为条件构造器)
功能需求:查询指定年龄范围内的user,并且分页
public void testPage() {
Page<User> page = new Page<>(1, 3);
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.gt("age",18);
userMapper.selectPage(page, queryWrapper);
// 当前页码
System.out.println(page.getCurrent());
// 总页数
System.out.println(page.getPages());
// 每页显示的条数
System.out.println(page.getSize());
// 总记录数
System.out.println(page.getTotal());
// 获取当前页数据
System.out.println(page.getRecords());
// 判断是否有上一页
System.out.println(page.hasPrevious());
// 判断是否有下一页
System.out.println(page.hasNext());
}
13.自定义分页插件
功能需求:查询指定年龄范围内的user,并且分页
首先在Mapper接口中定义方法
// 自定义分页插件
Page\<User\> getPage(Page\<User\> page,Integer age);
返回值为Page对象
- 参数1:mybatis-plus提供的分页对象
- 参数2:年龄参数
Mapper映射文件
<!-- Page<User> getPage(Page<User> page,Integer age);-->
<select id="getPage" resultType="cn.edu.pdsu.mybatisplus.pojo.User">
select * from user where age > #{age}
</select>
测试:
public void test01(){
//创建分页对象page
Page<User> page = new Page<>(1,3);
Page<User> userPage = userMapper.getPage(page, 18);
// 当前页码
System.out.println(userPage.getCurrent());
// 总页数
System.out.println(userPage.getPages());
// 每页显示的条数
System.out.println(userPage.getSize());
// 总记录数
System.out.println(userPage.getTotal());
// 获取当前页数据
System.out.println(userPage.getRecords());
// 判断是否有上一页
System.out.println(userPage.hasPrevious());
// 判断是否有下一页
System.out.println(userPage.hasNext());
}
14.乐观锁插件
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现原理:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
14.1场景引入
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。 现在商品价格是70元,比成本价低10元。
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
环境模拟
创建Product实体类
创建实体类接口,并继承BaseMapper接口。在测试类中进行模拟
@Autowired(required = false)
UserMapper userMapper;
@Autowired(required = false)
ProductMapper productMapper;
@Test
public void test01() {
// 小李查询商品价格
Product product1 = productMapper.selectById(1);
System.out.println("小李查询到的商品价格为:" + product1.getPrice());
// 小李查询商品价格
Product product2 = productMapper.selectById(1);
System.out.println("小王查询到的商品价格为:" + product2.getPrice());
// 小李将商品价格加50
product1.setPrice(product1.getPrice() + 50);
productMapper.updateById(product1);
// 小王将商品价格减去30
product2.setPrice(product2.getPrice() - 30);
productMapper.updateById(product2);
// 老板查询价格
Product product3 = productMapper.selectById(1);
System.out.println("老板查询到的商品价格为:" + product3.getPrice());
}
14.2乐观锁的实现流程
- 在要操作的数据实体类中为版本号字段添加@Version注解
- 在配置类中添加乐观锁插件的配置项
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
@Test
public void test01() {
// 小李查询商品价格
Product product1 = productMapper.selectById(1);
System.out.println("小李查询到的商品价格为:" + product1.getPrice());
// 小李查询商品价格
Product product2 = productMapper.selectById(1);
System.out.println("小王查询到的商品价格为:" + product2.getPrice());
// 小李将商品价格加50
product1.setPrice(product1.getPrice() + 50);
productMapper.updateById(product1);
// 小王将商品价格减去30
product2.setPrice(product2.getPrice() - 30);
int i = productMapper.updateById(product2);
if (i == 0){
// 小王重新获取
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice() - 30);
productMapper.updateById(productNew);
}
// 老板查询价格
Product product3 = productMapper.selectById(1);
System.out.println("老板查询到的商品价格为:" + product3.getPrice());
}
首先小李和小王同时获取商品信息,版本号version初始值为0,在小李修改商品信息后,被@Version注解标注的字段version就会加1,。轮到小王时version的值不对应,小王就重新获取商品信息,再对商品进行更改。
15.通用枚举
所谓的枚举,就是规定好指定的取值范围,所有的内容只能从指定的范围中取得。简单地说就是枚举是一个被命名的整数常数的集合,用于声明一组带标识符的常数
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现
一:在数据库表中添加gender字段,并将其类型设置为int类型,规定1为男,2为女。
二:创建枚举
public enum genderEnum {
MALE(1,"男"),
FEMALE(2,"女");
// 将注解所标识的属性的值存储到数据库中
@EnumValue
private Integer gender;
private String sexName;
genderEnum(Integer gender, String sexName) {
this.gender = gender;
this.sexName = sexName;
}
}
测试:
@Test
public void test01(){
User user = new User();
user.setUserName("温迪");
user.setEmail("2533694604@qq.com");
user.setGender(GenderEnum.MALE);
int insert = userMapper.insert(user);
System.out.println(insert);
}
因为采用了 @EnumValue注解,所以是将gender的属性的值存储到数据库中
16.代码生成器
- 添加依赖
<!-- 代码生成器的核心依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generate</artifactId>
<version>2.3.3</version>
</dependency>
<!-- freemarker模板依赖-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
- 创建测试类
public static void main (String[]args){
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatisplus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai", " root", "root")
.globalConfig(builder -> {
builder.author("xuhuaiang") // 设置作者
//.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("cn.edu.pdsu") // 设置父包名
.moduleName("mybatisPlus_codeAuto") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus"));
// 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine())
// 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
17.多数据源
从两个数据库中分别查询数据
- 引入依赖
<!-- 动态数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
- 配置数据源
spring:
datasource:
dynamic:
# 设置默认的数据源,默认值为master
primary: master
# 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatisplus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
- 创建实体类Product和User,并创建对应的接口和Service层,在UserServiceImpl和ProductServiceImpl中指定数据源
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
@Service
@DS("slave_1")
public class ProductServiceImpl extends ServiceImpl<ProductMapper,Product> implements ProductService {
}
- 测试
@Test
public void test01(){
User user = userService.getById(1);
System.out.println("我是mybatisplus数据库中user表中的数据:"+user);
Product product = productService.getById(1);
System.out.println("我是mybatis_plus_1数据库中product表中的数据:"+product);
}
18.MyBatisX
MyBatis-Plus为我们提供了强大的mapper和service模板,能够大大的提高开发效率 但是在真正开发过程中,MyBatis-Plus并不能为我们解决所有问题,例如一些复杂的SQL,多表 联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可 以使用MyBatisX插件 MyBatisX一款基于 IDEA 的快速开发插件,为效率而生。
18.1MyBatisX代码快速生成
- 创建新工程,添加依赖,在配置文件中添加数据源,配置数据库信息
使用idea中的数据库模块连接上数据库
点击表名,配置MyBatisX-Generator选项
项目生成成功
18.2使用MyBatis快速生成CRUD
- 删除功能
在接口中使用delete关键字选择方法
单条件删除
多条件删除
使用快捷键Alt+Enter生成完整方法
在映射文件中生成的SQL语句
其他方法类似