目录
一.简介
1、Mybatis-Plus介绍
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
官网:https://mybatis.plus/ 或 https://mp.baomidou.com/
2、特性
简介 | MyBatis-Plus (baomidou.com)
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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、搭建环境
a、数据库
创建表
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
添加数据
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]');
b、创建springboot项目
需要依赖:mysql、mybatis-plus、lombok
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
User类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapper接口
//继承 BaseMapper 泛型参数是pojo(要操作的类)
@Repository
public interface UserMapper extends BaseMapper<User> {
}
c、测试
@SpringBootTest
public class MyBatisPlusTest {
@Autowired(required = false)
UserMapper userMapper;
@Test
void selectList() {
// 通过条件构造器查询一个list集合,若没有条件,则可以设置null为参数
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
d、 mybatis-plus 日志
# m-p 日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.框架结构
二、基本crud
1.BaseMapper 方法
/**
* Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
* <p>这个 Mapper 支持 id 泛型</p>
*
* @author hubin
* @since 2016-01-23
*/
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 ID 删除
*
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据实体(ID)删除
*
* @param entity 实体对象
* @since 3.4.4
*/
int deleteById(T entity);
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 删除(根据ID或实体 批量删除)
*
* @param idList 主键ID列表或实体列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<?> idList);
/**
* 根据 ID 修改
*
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 whereEntity 条件,更新记录
*
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/**
* 查询(根据 columnMap 条件)
*
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,查询一条记录
* <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
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 条件,判断是否存在记录
*
* @param queryWrapper 实体对象封装操作类
* @return
*/
default boolean exists(Wrapper<T> queryWrapper) {
Long count = this.selectCount(queryWrapper);
return null != count && count > 0;
}
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值</p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
2. 简单 CRUD测试
a. insert
@Test
void insert() {
User cc = new User(null, "臭臭", 3, "[email protected]");
int count = userMapper.insert(cc);
System.out.println("count = " + count);
System.out.println("cc = " + cc.getId());
}
b、delete
@Test
void delete() {
// deleteById id后面加个 L (lang类型)
int i = userMapper.deleteById(1672633330522226690L);
// 通过多个条件删除,并且的关系 and
HashMap<String, Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",12);
int count1 = userMapper.deleteByMap(map);
// 根据 id 批量删除 where id in(....)
List<Long> list = Arrays.asList(1L, 2L, 3L, );
int count2 = userMapper.deleteBatchIds(list);
}
c、update
@Test
void update(){
// 通过实体修改 updata user set name=?,age=? where id=?
User user = new User();
user.setId(1L);
user.setName("李四");
user.setAge(23);
int i = userMapper.updateById(user);
}
d、select
@Test
void select() {
//根据id查询用户信息
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(4L);
//根据多个id查询多个用户信息
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
List<Long> idList = Arrays.asList(4L, 5L);
List<User> list = userMapper.selectBatchIds(idList);
//通过map条件查询用户信息
//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 22);
map.put("name", "admin");
List<User> users = userMapper.selectByMap(map);
//查询所有用户信息
//SELECT id,name,age,email FROM user
List<User> alluser = userMapper.selectList(null);
}
3、自定义功能
mapper:
//继承 BaseMapper 泛型参数是pojo(要操作的类)
@Repository
public interface UserMapper extends BaseMapper<User> {
Map<String,Object> selectByUID(Long id);
}
mapper.xml
<select id="selectByUID" parameterType="String" resultType="map">
SELECT * FROM user WHERE id = ${id}
</select>
test:
// 自定义查询
Map<String, Object> map = userMapper.selectByUID(1L);
System.out.println("map = " + map);
4.Service接口
a、说明
- 接口 public interface IService<T> {} 里面定义了很多常用方法
- IService<T> 的实现类:(M 是指 mapper,T 是指要操作的类(查询的表))
-
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {}
b、创建 UserService
1. 新建UserService 接口 继承 IService,这样就能使用 IService 中的方法了
public interface UserService extends IService<User> {
}
2. 新建 UserService 的实现类 UserServiceImpl 实现 UserService
public class UserServiceImpl implements UserService {
}
3. 这个时候 UserServiceImpl 还是会报错,因为需要实现 IService中的方法,我们不去实现方法,而是继承 IService 的实现类 ServiceImpl(这里面实现了方法)
public class UserServiceImpl
extends ServiceImpl<UserMapper, User>
implements UserService {
}
c、service方法测试
1.首先注入 service
@Service
public class UserServiceImpl
extends ServiceImpl<UserMapper, User>
implements UserService {}
2. 测试
@SpringBootTest
public class MPServiceTest {
@Autowired
private UserServiceImpl userService;
@Test
void testGetCount() {
// 查询总记录数
// SELECT COUNT( * ) FROM user
long count = userService.count();
System.out.println("count = " + count);
}
@Test
void batchInsert() {
// 批量插入
// 循环 insert into 单条sql语句
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i <10; i++) {
User user = new User();
user.setAge(i+1);
user.setName("batch"+i);
users.add(user);
}
boolean b = userService.saveBatch(users);
}
}
三.常用注解
1. @TableName
当实体类名为 User,数据库表名为 t_user 的时候
就可以使用 此注解表明该类对应的表是 xxx
@TableName("t_user")
public class User {
}
2. @TableId
- mybatis-plus 默认将 字段名为 id 的字段当成主键
- 如果字段名为 uid 就会报错,就可以使用 此注解显示指定主键
@TableId
private Long uid;
a. value属性:
实体类 和 数据库字段名不一样,使用 value 属性表示数据库字段名
// 一个属性的时候 可以省略value 直接写字符串
@TableId(value = "uid")
private Long id;
b. type属性:
设置主键生成策略
IdType.ASSIGN_ID | 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 |
IdType.AUTO | 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效 |
c. 全局配置
# m-p 日志
mybatis-plus:
# 全局配置
global-config:
db-config:
# 实体类对应表的统一前缀
table-prefix: # t_
# 设置统一的主键生成策略
id-type: auto # 数据库递增
3. 雪花算法
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。.
a.数字意义
长度共64bit (一个ong型)
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截- 开始时间截),结果约等于69.73年。10bit作为机器的ID (5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号 (意味着每个节点在每毫秒可以产生 4096 个D)。
b.优点
整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
4. @TableField
指定属性所对应的 数据库字段名
m-p 中默认可以驼峰对应下划线(类字段名:userName,数据库字段名:user_name)
// 和数据库中的 user_name 字段对应
@TableField("user_name")
private String name;
5.@TableLogic
a.逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
- 使用场景:可进行数据恢复
b.实现逻辑删除
step1:数据库中 创建逻辑删除状态列,设置默认值为0
step2:实体类中新增对应的字段 、并且添加 @TableLogic
@TableLogic
private int isDeleted;
c、执行删除测试
使用了逻辑删除后。所有的查询都会添加 is_deleted = 0 条件
删除操作 不会去删除数据,而是将这个字段的值改成 0 ,删除sql如下:
删除后再查询就查不出来被逻辑删除的数据了 ,查询sql如下
四、Wrapper、条件构造器
1.简介
2.QueryWrapper
a、wrapper查询
@Test
void test01() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "o") //用户名包含 o
.between("age", 20, 30) // 年龄 20 - 30
.isNotNull("email"); // 邮箱不为 null
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
sql:
b、wrapper排序
@Test
void testSort() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("age") // 按照年龄的降序排序
.orderByAsc("id"); // 若年龄相同按照 id 升序排序
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
sql:
c、wrapper删除
@Test
void testDelete() {
// 删除邮箱为 null 的数据
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNull("email");
int count = userMapper.delete(wrapper);
System.out.println("count = " + count);
}
使用了逻辑删除,所以是 update
sql:
d、wrapper修改
@Test
void testUpdate() {
// 修改 (年龄大于20且用户名中包含a) 或邮箱为 null 的用户信息
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age",20)
.like("name","a")
.or() // 和前面的两个条件是或者关系
.isNull("email");
// 修改以下内容
User user = new User();
user.setName("小明");
user.setAge(18);
int result = userMapper.update(user, wrapper);
System.out.println("result = " + result);
}
sql
e、条件优先级
lambda中的条件会优先执行
@Test
void testCondition() {
// 修改 用户名中包含a 且 (年龄大于20且 或 邮箱为 null) 的用户信息
// lambda中的条件会优先执行
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","a")
// lambda 表达式
.and(wrapper -> wrapper.gt("age",20).or().isNull("email"));
// 修改以下内容
User user = new User();
user.setName("小明");
user.setAge(18);
int result = userMapper.update(user, queryWrapper);
System.out.println("result = " + result);
}
sql
f、wrapper select
@Test
void testSelect() {
// 查询用户名是 臭臭 的 用户名、年龄、邮箱
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("name","age","email")
.eq("name","臭臭");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
sql
==> Preparing: SELECT name,age,email FROM user WHERE (name = ?)
g、wrapper 子查询
演示
@Test
// 演示 子查询
void test03() {
// 查询id小于等于100 的用户信息
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id <= 100");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
//==> Preparing: SELECT id,name,age,email FROM user WHERE (id IN (select id from user where id <= 100))
}
3、update Wrapper
@Test
void testUpdateWrapper() {
// 将用户名中包含 a 并且 (年龄大于20或者邮箱为 null) 的用户信息修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 修改条件
updateWrapper.like("name","a")
.and(
wrapper -> wrapper.gt("age",20)
.or().isNull("email")
);
// 修改内容
updateWrapper.set("name","小黑").set("email","[email protected]");
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);
// UPDATE user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
}
4、condition 动态sql
a、if 判断
@Test
void testDynamic() {
// 动态sql
String name = "";
Integer ageStar = 20;
Integer ageEnd = 30;
QueryWrapper<User> wrapper = new QueryWrapper<>();
// com.baomidou.mybatisplus.core.toolkit.StringUtils
if (StringUtils.isNotBlank(name)){
// isNotBlank 某个字段不为空、不为 null,不为空白符
wrapper.like("name",name);
}
if(ageStar != null){
wrapper.ge("age",ageStar);
}
if (ageEnd != null){
wrapper.le("age",ageEnd);
}
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
// SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
}
b、condition
.ge() 方法多一个条件参数 (ageStar != null)
@Test
void testDynamic02() {
// 动态sql
String name = "";
Integer ageStar = 20;
Integer ageEnd = 30;
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(name),"name",name)
.ge(ageStar != null,"age",ageStar)
.le(ageEnd != null,"age",ageEnd);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
// SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
}
5、lambdaQueryWrapper
@Test
// 这样字段名不会写错
void testLambda() {
String name = "";
Integer ageStar = 20;
Integer ageEnd = 30;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// User::getName 表示 User 类的 name属性
wrapper.like(StringUtils.isNotBlank(name),User::getName,name)
.ge(ageStar != null,User::getAge,ageStar)
.le(ageEnd != null,User::getAge,ageEnd);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
// SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
}
6、LambdaUpdateWrapper
@Test
void testLambdaUpdateWrapper() {
// 将用户名中包含 a 并且 (年龄大于20或者邮箱为 null) 的用户信息修改
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
// 修改条件
updateWrapper.like(User::getName,"a")
.and(
wrapper -> wrapper.gt(User::getAge,20)
.or().isNull(User::getEmail)
);
// 修改内容
updateWrapper.set(User::getName,"小黑").set(User::getEmail,"[email protected]");
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);
// UPDATE user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
}
五、插件
1、分页插件
a、配置
@Configuration
// 可以把 MapperScan 写在配置类里
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MPConfig {
// 配置 mp 分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加一个内部插件
// 设置数据库类型 mysql
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
b、测试
@Test
void testPage() {
// 1:当前页起始下标,3: 每页数据条数
// User 是需要查询的类
Page<User> page = new Page<>(2, 3);
// null是 wrapper参数
userMapper.selectPage(page, null);
// 查询出的数据
System.out.println(page.getRecords());
// ==> Preparing: SELECT id,name,age,email FROM user LIMIT ?,?
// ==> Parameters: 3(Long), 3(Long)
}
2、自定义分页
a、userMapper 接口中定义方法
/**
* 根据年龄查询用户信息 并且分页
* @param page page 参数sql中用不到,但是如果想要使用分页功能第一个参数必须是Page
* @param age
* @return 返回值必须是 Page<T>
* VO=view object
*/
Page<User> selectPageVO(@Param("page") Page<User> page, @Param("age") Integer age);
b、userMapper.xml 中编写sql
<!--Page<User> selectPageVO(@Param("page") Page<User> page, @Param("age") Integer age);-->
<select id="selectPageVO" resultType="User">
select id,name,age,email from user where age > #{age}
</select>
c、测试
@Test
void testPageVO() {
Integer age = 12;
// 和内置的分页一样使用
Page<User> page = new Page<>(2, 3);
userMapper.selectPageVO(page,age);
System.out.println(page.getRecords());
// ==> Preparing: select id,name,age,email from user where age > ? LIMIT ?,?
// ==> Parameters: 12(Integer), 3(Long), 3(Long)
}
3、乐观锁
a、场景
b、乐观锁与悲观锁
c、模拟冲突修改
c.1 新建表
CREATE TABLE t_product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
c.2 添加数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
c.3 添加实体
package com.atguigu.mybatisplus.pojo;
import lombok.Data;
@Data
@TableName("t_product")
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
c.4 添加mapper
public interface ProductMapper extends BaseMapper<Product> {
}
c.5 测试
@Test
public void testConcurrentUpdate() {
//1、小李查询商品价格
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//2、小王查询商品价格
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//3、小李将价格加了50元,存入了数据库
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改结果:" + result1);
//4、小王将商品减了30元,存入了数据库
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改结果:" + result2);
//最后的结果
Product p3 = productMapper.selectById(1L);
//价格覆盖,最后的结果:70
System.out.println("最后的结果:" + p3.getPrice());
}
d、乐观锁实现流程
e、mybatis_plus 实现乐观锁
e.1 实体类 version 字段添加 @Version 注解
@Version // 标识乐观锁版本号字段
private Integer version;
e.2 配置类添加乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
// 设置数据库类型 mysql
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
e.3 修改代码逻辑
在小王修改后 添加 if 判断:最后结果:120
@Test
public void testConcurrentUpdate() {
//1、小李查询商品价格
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//2、小王查询商品价格
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//3、小李将价格加了50元,存入了数据库
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改结果:" + result1);
//4、小王将商品减了30元,存入了数据库
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改结果:" + result2);
if (result2 == 0){
// 操作失败、重试
Product pNew = productMapper.selectById(1L);
pNew.setPrice(pNew.getPrice() - 30);
productMapper.updateById(pNew);
}
//最后的结果
Product p3 = productMapper.selectById(1L);
//价格覆盖,最后的结果:70
System.out.println("最后的结果:" + p3.getPrice());
}
六、通用枚举
a、数据库添加sex字段方便测试
b、创建枚举类型
@Getter、@EnumValue
@Getter
public enum SexEnum {
MALE(1,"男"),
FEMALE(0,"女");
@EnumValue // 此注解可以将属性的值存到数据库而不是存名称(MALE)
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
c、包扫描配置
mybatis-plus:
# 扫描通用枚举的包
type-enums-package: com.atguigu.mybatisplus.enums
d、测试
@Test
void testEnum() {
User user = new User();
user.setName("赵云");
user.setAge(122);
user.setSex(SexEnum.MALE);
int result = userMapper.insert(user);
System.out.println("result = " + result);
}
七、代码生成器
a、引入依赖
版本要和 m-p 一致
<!--m-p 代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
b、复制代码
根据需要修改
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", "1234")
.globalConfig(builder -> {
builder.author("atguigu") // 设置作者
//.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("E://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.atguigu") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "E://mybatis_plus"));// 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
c、生成的目录
八、多数据源
场景
新建测试环境:
创建两个库,分别为: mybatis_plus (以前的库不动) 与mybatis_plus_1 (新建),
将mybatis_plus库的 product 表移动到mybatis_plus_1库,
mybatis_plus:有 user表、mybatis_plus_1有 product 表
这样每个库一张表,通过一个测试用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功
1、创建数据库和表
-- 建库建表
CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus_1`;
CREATE TABLE product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
-- 插入数据
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
--删除原有的表 (如果有)
use mybatis_plus;
DROP TABLE IF EXISTS product;
创建完成后删除 原 mybatis_plus 数据库中的 product表
2、新建项目
注意检查创建完成后的 jdk版本,这里我把springboot版本也修改成了 2.7.12 和原项目一样
3、引入依赖
<!--多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!-- 还需要复制 mysql、lombok、mybatis-plus-boot-starter 依赖 -->
4、配置多数据源
spring:
# 配置数据源信息
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
# 是否严格匹配数据源,默认false.,如果设定是false未匹配到就使用默认数据源master ,true未匹配到指定数据源时抛异常
strict: false
datasource:
# master数据源
master: # 这个名字和上面的要一样
url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 1234
# 从数据源
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 1234
5、创建service
目录:与原项目一样
a、创建 userservice
@DS("")
@Service
@DS("master") //指定所操作的数据源 user 表在 master 数据源中
public class UserServiceImpl
extends ServiceImpl<UserMapper, User>
implements UserService {
}
b、创建 productService
@Service
@DS("slave_1") //指定所操作的数据源 product 表在 slave_1 数据源中
public class ProductServiceImpl
extends ServiceImpl<ProductMapper, Product>
implements IProductService {
}
6、测试成功
@Autowired
private UserService userService;
@Autowired
private IProductService productService;
@Test
void contextLoads() {
// service 层的查询方法是以 get 开头、mapper是以 select开头
System.out.println("userService.getById(1) = " + userService.getById(1));
System.out.println("productService.getById(1) = " + productService.getById(1));
// userService.getById(1) = User(id=1, name=Jone, age=18, [email protected], sex=null)
// productService.getById(1) = Product(id=1, name=外星人笔记本, price=100, version=0)
}
九、MyBatisX 插件
MybatisX快速开发插件 | MyBatis-Plus (baomidou.com)
1、安装
2、使用
a、idea连接数据
b、点击表右键—>MybatisX-Generator
c、配置
生成的代码:
3、CRUD
MybatisX 会根据我们写的方法名称自动创建 sql 语句
但是方法名称要见名知意,查以select开头、删以delete... 如下的智能提示
组合键:alt + enter 、选择第二个
创建号的方法: 如果是自己随意写的方法名会报错没有 sql 文件
自动写的sql :