# MyBatisPlus从入门到精通
# 入门案例
# 创建数据库及表
# 创建表
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, '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工程
官网的https://start.spring.io/ 最低支持JDK17
中文社区搭建https://start.springboot.io,最低支持JDK17
阿里云代理搭建http://start.aliyun.com,支持JDK8创建
# 引入依赖
<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>
<version>1.16.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
<scope>runtime</scope>
</dependency>
并在IDEA中安装lombok插件
# 编写代码
# 配置application.yml文件
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库的信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT%2B8
username: root
password: 123456
连接地址:
MySQL5.0
jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8$useSSL=false
MySQL8.0,存在时区问题
jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8$useSSL=false&serverTimeZone=GMT%2B8
# 启动类
添加注解
@SpringBootApplication
// 用于扫描指定包下的mapper接口
@MapperScan("com.rong.hui.mybatisplus.mapper")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
# 添加实体
// lombok注解可以生成无参构造、getter、setter、hashCode、toString、canEqual
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
# 添加mapper
BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的实体类型
public interface UserMapper extends BaseMapper<User> {
}
# 测试
@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);
}
}
# 添加日志
在application.yml中添加
# 添加日志打印
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 基本CRUD
# 添加
@Test
public void testInsert(){
// 新增用户信息
User user = new User();
user.setName("张三");
user.setAge(23);
user.setEmail("zhangsan@163.com");
int result = userMapper.insert(user);
System.out.println(result);
System.out.println(user.getId());
}
# 修改
@Test
public void testUpdate(){
// 修改用户信息,UPDATE user SET name=?, age=?, email=? WHERE id=?
User user = new User();
user.setId(4L);
user.setName("李四");
user.setAge(23);
user.setEmail("test@163.com");
int i = userMapper.updateById(user);
System.out.println(i);
}
# 删除
@Test
public void testDelete(){
// 通过ID删除用户信息
int result = userMapper.deleteById(1808416315389857794L);
System.out.println(result);
// 根据map定义的sql条件进行删除, 匹配map的属性值,DELETE FROM user WHERE name = ? AND age = ?
Map<String,Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",23);
int i = userMapper.deleteByMap(map);
System.out.println(i);
// 批量删除,DELETE FROM user WHERE id IN ( ? , ? )
//List<Long> list = Arrays.asList(1L, 2L);
//int result1 = userMapper.deleteBatchIds(list);
//System.out.println(result1);
}
# 查询
@Test
public void testSelect(){
// 根据ID查询
//User user = userMapper.selectById(3L);
//System.out.println(user);
// 根据多个ID查询
//List<Long> list = Arrays.asList(3L, 4L, 5L);
//List<User> users = userMapper.selectBatchIds(list);
//users.forEach(System.out::println);
// 根据Map中设置的条件查询
HashMap<String, Object> map = new HashMap<>();
map.put("name","Tom");
map.put("age",28);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
# 自定义
# 添加mapper映射文件路径
# 添加自定义方法
Map<String,Object> selectMapById(Long id);
# 创建mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rong.hui.mybatisplus.mapper.UserMapper">
<select id="selectMapById" resultType="map">
select id,name,age,email from user where id = #{id}
</select>
</mapper>
# 测试
@Test
public void testCustomSelect(){
Map<String, Object> map = userMapper.selectMapById(3L);
System.out.println(map);
}
# 通用Service
# 创建Service接口和实现类
MyBatis提供了默认的IService
接口和ServiceImpl
的实现类,使用默认方法需要实现接口并继承默认实现类
创建service接口
public interface UserService extends IService<User> {
}
创建实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
# 测试查询记录数
@Test
public void testCount(){
// 查询总记录数
long count = userService.count();
System.out.println(count);
}
# 测试批量插入
@Test
public void testBatchSave(){
List<User> list = new ArrayList<>();
for(int i=1;i<=10;i++){
User user = new User();
user.setName("huyadish"+i);
user.setAge(20+i);
user.setEmail("123456@qq.com");
list.add(user);
}
boolean b = userService.saveBatch(list);
System.out.println(b);
}
# 常用注解
# @TableName
用于设置实体类对应的表名称,可以解决实体类名和数据库表名称不一致的情况
// 设置实体类对应表名
@TableName("t_user")
public class User {
...
}
当数据库表存在统一前缀的时候,也可以在yml配置文件中进行统一配置
mybatis-plus:
# mybatis-plus全局配置
global-config:
db-config:
# 设置实体类对应表的统一前缀
table-prefix: "t_"
# @TableID
Myabtis plus默认支持以id
作为主键,当数据表使用其他字段如uid
作为主键的时候,就需要添加@TableID注解进行标识
@TableId
private Long uid;
Mybatis plus默认以雪花算法生成ID,如果想使用数据库自增ID,首先需要在数据库表中设置ID自增,然后需要指定注解值的类型,默认是ASSIGN_ID
雪花算法
@TableId(value = "uid",type = IdType.AUTO)
private Long id;
也可在yml文件中进行统一设置
mybatis-plus:
# mybatis-plus全局配置
global-config:
db-config:
# 设置统一的主键生成策略
id-type: auto
雪花算法
由Twitter公布的分布式主键生成算法,能够保证不同表的主键不重复,并且相同表的主键有序,适合分布式数据库系统
核心思想:
- 长度共64bit(long型)
- 首先是1bit的符号位
- 41bit时间戳,存储时间戳的差值
- 10bit是机器的ID
- 12bit作为毫秒内的流水号(每个毫秒产生4096个ID)
# @TableField
实体类属性名和字段名不一致的情况
MyBatis-Plus会自动将下划线转化为小驼峰,例如表字段user_name
,实体类属性userName
但是当实体类属性为name
,数据表属性为user_name
时,就会出错了
@TableField(value = "user_name")
private String name;
可以使用@TableField注解处理这种特殊情况
# @TableLogic
# 逻辑删除
物理删除:真实删除,将对应数据从数据库删除,之后查询不到这条数据
逻辑删除:假删除,将对应数据的状态修改为“被删除”,之后在数据库中仍能看到这条数据
使用场景:数据恢复
# 实现逻辑删除
首先在数据表添加int类型删除标识字段is_delete
,默认为0,随后在实体类添加注解
@TableLogic
private Integer isDelete;
执行删除操作实际执行的语句
UPDATE t_user SET is_delete=1 WHERE user_name = ? AND age = ? AND is_delete=0
执行查询也不会查到逻辑删除的数据
SELECT uid AS id,user_name AS name,age,email,is_delete FROM t_user WHERE is_delete=0
# 条件构造器和常用接口
# wapper介绍
# QueryWapper
- 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("user_name","张三")
.between("age",22,25)
.isNotNull("email");
- 组装排序条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
- 组装删除条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int i = userMapper.delete(queryWrapper);
- 条件的优先级
//将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
.like("user_name","o")
.or()
.isNull("email");
User user = new User();
user.setEmail("huyadish@qq.com");
user.setName("rong");
int i = userMapper.update(user, queryWrapper);
and和or方法支持传入lambda表达式,用于控制条件优先级
//将年龄大于20并且(用户名中包含有a或邮箱为null)的用户信息修改
// lambda中的条件优先执行
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
.and(i->i.like("user_name","o").or().isNull("email"));
- 查询部分字段
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("user_name","age","email");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
- 实现子查询
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid","select uid from t_user where uid <= 6");
List<User> list = userMapper.selectList(queryWrapper);
# UpdateWrapper
// UPDATE t_user SET user_name=?,email=? WHERE is_delete=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("user_name","a")
.and(i->i.gt("age",20).or().isNull("email"));
updateWrapper.set("user_name","小黑").set("email","dish@4396.com");
int i = userMapper.update(null, updateWrapper);
注意:updateWrapper和queryWrapper区别是,updateWrapper除了可以设置查询条件,还可以设置修改的属性
# condition条件
在特定条件下,需要先对用户提供的参数进行判断,再选择性组装查询条件。
@Test
public void testCondition(){
String username = "a";
Integer beginAge = 20;
Integer endAge = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),"user_name",username)
.ge(beginAge != null,"age",beginAge)
.le(endAge != null,"age",endAge);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
# LambdaQueryWrapper
在构造查询条件时,需要写数据表字段名,为防止写错字段名,可使用方法引用,如User::getName来获取字段名
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
.ge(beginAge != null, User::getAge, beginAge)
.le(endAge!=null,User::getAge,endAge);
# LambdaUpdateWrapper
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
.ge(beginAge != null, User::getAge, beginAge)
.le(endAge!=null,User::getAge,endAge);
# 小结
条件构造器就是通过函数式API构造条件,同时具备以下功能:
queryWrapper
可实现查询、修改和删除- 为防止字段名写错,可使用方法引用如
User::getName
,替代直接写user_name
- 通过condition判断是否添加条件,
queryWrapper.like(condition,column,value)
updateWrapper
相比queryWrapper
,多了set方法,可设置更新后的值
# 插件
# 分页插件
# 添加配置类
@Configuration
// 可以将主类的注解移到此处
@MapperScan("com.rong.hui.mybatisplus.mapper")
public class MybatisplusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 这里可以指定不同的数据源
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
# 使用
@Test
public void testPlugins(){
// 设置分页参数
Page<User> page = new Page<>(2,3);
userMapper.selectPage(page,null);
// 获取分页数据
System.out.println(page.getRecords());
System.out.println(page.getCurrent());
System.out.println(page.getPages());
System.out.println(page.getTotal());
System.out.println(page.hasPrevious());
System.out.println(page.hasNext());
}
# 自定义分页功能
创建接口方法
/**
* 通过年龄查询用户信息并分页
* @param page MP提供的分页对象,xml中可从中取值,传递参数必须为第一位
* @param age 年龄
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
创建映射文件查询语句
<select id="selectPageVo" resultType="User">
select * from t_user where age > #{age}
</select>
测试
@Test
public void testPageVo(){
Page<User> page = new Page<>(1,3);
userMapper.selectPageVo(page,20);
}
# 乐观锁
乐观锁原理:使用版本号机制,每条数据都有一个版本号。读取数据时,会一并读取该记录的版本号。更新数据时,检查当前版本号与读取时的版本号是否一致。如果一致,说明这段时间内,没有其他事务修改过记录,可以安全更新,并将版本号+1。如果不一致,说明有其他事务修改过改记录,可以重试或回滚。
作用:
- 提高并发性能:允许多个事务并发操作数据,不需要在一开始就进行锁定,减少等待时间
- 避免死锁:不在一开始就锁定数据,减少死锁发生的机会
- 减少锁的开销:不需要进行锁定和解锁的操作
悲观锁:在操作数据之前先对数据进行绑定,防止其他事务对数据进行修改
乐观锁插件的原理:每次更新时,先判断version是否与查询的时候一致;每次更新后,为指定@Version字段+1
代码实现:
实体类添加注解
@Data
public class Product {
...
@Version
private Integer version;
}
配置类添加插件
@Configuration
// 可以将主类的注解移到此处
@MapperScan("com.rong.hui.mybatisplus.mapper")
public class MybatisplusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试:
@Test
public void testProduct01(){
// 小李查询商品价格
Product productLi = productMapper.selectById(1L);
System.out.println("小李查询的商品价格"+productLi.getPrice());
// 小王查询商品价格
Product productWang = productMapper.selectById(1L);
System.out.println("小王查询的商品价格"+productWang.getPrice());
// 小李将商品价格+50
productLi.setPrice(productLi.getPrice()+50);
productMapper.updateById(productLi);
// 小王将商品价格-30
productWang.setPrice(productWang.getPrice()-30);
int result = productMapper.updateById(productWang);
// 如果版本号不一致,受影响的行数为0,重新查询数据
if(result == 0){
Product product1 = productMapper.selectById(1L);
product1.setPrice(product1.getPrice()-30);
productMapper.updateById(product1);
}
// 老板查询商品价格
Product productBoss = productMapper.selectById(1L);
System.out.println("老板查询的商品价格"+productBoss.getPrice());
}
此时原始数据为100,小王+50,小李-30后,查询的结果为120,
# 通用枚举
定义枚举
@Getter
public enum SexEnum {
// 枚举成员之间逗号分隔
MALE(1,"男"),
FEMALE(2,"女");
@EnumValue // 使用 @EnumValue 注解标识枚举的值
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
添加配置文件
mybatis-plus:
# 扫描通用枚举的包
type-enums-package: com.rong.hui.mybatisplus.enums
测试
@Test
public void test(){
User user = new User();
user.setName("admin");
user.setAge(33);
user.setSex(SexEnum.FEMALE);
userMapper.insert(user);
}
# 代码生成器
可以通过代码生成器,使用函数API方式配置生成信息,可以生成实体类、Mapper接口、Mapper.xml、Service接口和实现类
# 添加依赖
<!--MP框架提供的代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<!--文本输出模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
# 代码生成
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false","root","123456")
.globalConfig(builder -> {
builder.author("oldstag") // 设置作者
//.enableSwagger() // 开启swagger模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.rong.hui") // 设置父包名
.moduleName("mybatisPlus") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml,"D://mybatis_plus")); // 设置mapper.xml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_user") // 需要生成的表名
.addTablePrefix("t_","c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用freemaker作为模板引擎,默认是Velocity模板引擎
.execute();
}
}
# 多数据源
# 创建数据库及表
在mybatis_plus_1数据库中创建
DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称',
`price` int NULL DEFAULT NULL COMMENT '价格',
`version` int UNSIGNED NULL DEFAULT 0 COMMENT '版本号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
在mybatis_plus数据库中创建
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`uid` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '姓名',
`age` int NULL DEFAULT NULL COMMENT '年龄',
`email` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`is_delete` int NULL DEFAULT 0,
`is` int NULL DEFAULT 0,
`sex` int NULL DEFAULT NULL COMMENT '性别',
PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
# 引入依赖
<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/mybatis_plus?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
# 创建service
Product 接口和实现类 在实现类上使用@DS
注解标识数据源
public interface ProductService extends IService<Product> {
}
@Service
@DS("slave_1")
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}
User 接口和实现类 在实现类上使用@DS
注解标识数据源
public interface UserService extends IService<User> {
}
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
# 测试
@Test
public void test(){
System.out.println(userService.getById(3));
System.out.println(productService.getById(1));
}