MyBatis-Plus
MyBatis-Plus
ref:
官网👉 MyBatis-Plus (baomidou.com)
GitHub 👉 https://github.com/baomidou/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 操作智能分析阻断,也可自定义拦截规则,预防误操作
开始
新建SpringBoot项目
1️⃣ 新建项目 Create New Project
2️⃣ 选择 Spring Initializr 然后 Next ⚠️ 如果没有该选项,请下载Spring Boot 插件并启用
3️⃣ Type 选择 Maven Project
🆗
添加Maven依赖
pom.xml
<?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.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.learn</groupId>
<artifactId>mybatisplus</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>learnMyBatisPlus</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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring配置文件
application.properties
spring.datasource.driver-class-name:com.mysql.cj.jdbc.Driver
spring.datasource.url: jdbc:mysql://xxx
spring.datasource.username: xxx
spring.datasource.password: xxx
测试
数据库表名为user
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer roleid;
}
public interface UserMapper extends BaseMapper<User> {
}
@SpringBootTest
public class MyBatisPlusTests {
@Resource
private UserMapper userMapper;
@Test
public void test01() {
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
}
实体与表的映射@TableName/table-prefix
- 使用注解@TableName指定实体所对应的表
@Data
@TableName("t_user")
public class User {
private Integer id;
private String username;
private String password;
private Integer roleid;
}
- 全局配置表明统一前缀
mybatis-plus.global-config.db-config.table-prefix=t_
声明主键@TableId
mybatis默认主键为id
修改默认主键为类属性名uid,这样mybatisplus执行sql语句时,不再是 如select id ... 而是 select uid ...
@Data
public class User {
@TableId
private Integer uid;
private String username;
private String password;
private Integer roleid;
}
xxx为数据库表列名
@Data
public class User {
@TableId("xxx")
private Integer id;
private String username;
private String password;
private Integer roleid;
}
ID自增 IdType.AUTO/id-type
⚠️使用mybatisplus的id自动生成算法是,手动设置id无效
- type = IdType.AUTO
前提 数据库该表主键是自增的,否则无效
@Data
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private Integer roleid;
}
- 全局配置
mybatis-plus.global-config.db-config.id-type=auto
声明字段映射 @TableField
表列名 u_name 对应 类属性名 username
@Data
public class User {
private Integer id;
@TableField("u_name")
private String username;
private String password;
private Integer roleid;
}
逻辑删除 @TableLogic
- 携带此注解的类,执行查询语句时查询该值为0的数据
@TableLogic把对应字段做修改(设置成1)
-
isDeleted : 1 -> 已删除
-
isDeleted :0 -> 未删除(逻辑)
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer roleid;
@TableLogic
private Integer isDeleted;
}
BaseMapper
Wrapper
QueryWrapper
- QueryWrapper
select(String... columns)
查询指定列
queryWrapper.select("username","roleid");
- Children inSql(R column, String inValue)
效果 SELECT * FROM t_user WHERE column IN (inValue)
queryWrapper.inSql("id", "SELECT * FROM t_user WHERE `roleid` = 1");
UpdateWrapper
- Children set(R column, Object val)
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.isNotNull("username")
.eq("roleid", 3)
.or()
.eq("username", "admin");
updateWrapper.set("username", "u000x").set("roleid", 1);
userMapper.update(null, updateWrapper);
LambdaQueryWrapper
防止写错列名...User::getUsername获取到列名,不再手写
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(User::getUsername, "aa")
.gt(User::getRoleid, 3);
userMapper.selectList(lambdaQueryWrapper);
LambdaUpdateWrapper
防止写错列名...User::getUsername获取到列名,不再手写
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.isNotNull(User::getUsername)
.eq(User::getRoleid, 3)
.or()
.eq(User::getUsername, "admin");
lambdaUpdateWrapper.set(User::getUsername, "u000x").set(User::getRoleid, 1);
userMapper.update(null, lambdaUpdateWrapper);
组装条件
- 组装前
String username = "a";
Integer roleid = 0;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(username))
queryWrapper.like("username", username);
if (roleid != null)
queryWrapper.lt("roleid", roleid);
userMapper.selectList(queryWrapper);
- 组装后
String username = "a";
Integer roleid = 0;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), "username", username);
queryWrapper.like(roleid != null, "roleid", roleid);
userMapper.selectList(queryWrapper);
插入
- int insert(T entity);
public void testInsert() {
User user = new User();
user.setUsername("testInsert");
user.setPassword("123456");
user.setRoleid(2);
int i = userMapper.insert(user);
System.out.println("before insert ==> : " + user);
System.out.println("userMapper.insert result ==> : " + i);
System.out.println("after insert ==> : " + user);
}
==> Preparing: INSERT INTO user ( id, username, password, roleid ) VALUES ( ?, ?, ?, ? )
==> Parameters: 1673330689(Integer), testInsert(String), 123456(String), 2(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@771cbb1a]
before insert ==> : User(id=1673330689, username=testInsert, password=123456, roleid=2)
userMapper.insert result ==> : 1
after insert ==> : User(id=1673330689, username=testInsert, password=123456, roleid=2)
mybatis-plus根据算法自动生成id
删除
- int deleteById(Serializable id);
userMapper.deleteById(1052631042);
- int deleteByMap(@Param("cm") Map<String, Object> columnMap)
HashMap<String, Object> map = new HashMap<>();
map.put("username", "zhangsan");
map.put("rolid", 2);
userMapper.deleteByMap(map);
- int deleteBatchIds
List<Integer> idList = Arrays.asList(1, 2, 3);
userMapper.deleteBatchIds(idList);
- int delete(@Param("ew") Wrapper
queryWrapper);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("roleid", 3)
.isNotNull("username");
userMapper.delete(wrapper);
更新
- int updateById(@Param("et") T entity);
User user = new User();
user.setId(1);
user.setRoleid(3);
userMapper.updateById(user);
- int update(@Param("et") T entity, @Param("ew") Wrapper
updateWrapper);
User user = new User();
user.setUsername("admin");
user.setPassword("123456");
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("zhangsan", "123")
.gt("roleid", 3)
.or()
.lt("roleid", 1);
userMapper.update(user,updateWrapper);// 使用updateWrapper.set 等更新动作时 第一个参数应为null 详见UpdateWrapper
查询
- T selectById(Serializable id);
userMapper.selectById(1);
- List
selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<Integer> idList = Arrays.asList(1, 2, 3);
userMapper.selectBatchIds(idList);
- List
selectByMap(@Param("cm") Map<String, Object> columnMap);
HashMap<String, Object> map = new HashMap<>();
map.put("username", null);
userMapper.selectByMap(map);
- default T selectOne(@Param("ew") Wrapper
queryWrapper)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNull("username");
userMapper.selectOne(wrapper);
- boolean exists(Wrapper
queryWrapper)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNull("username");
userMapper.exists(wrapper);
-
Long selectCount(@Param("ew") Wrapper
queryWrapper); -
List
selectList(@Param("ew") Wrapper queryWrapper) -
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper
queryWrapper);
// 查出指定列到map
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("username","roleid");
userMapper.selectMaps(queryWrapper);
-
List<Object.> selectObjs(@Param("ew") Wrapper<T.> queryWrapper);
-
<.P extends IPage<T.>> P selectPage(P page, @Param("ew") Wrapper<T.> queryWrapper);
-
<.P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T.> queryWrapper);
自定义SQL
- 使用注解
@Select("select username,roleid from user where id = #{id}")
Map<String,Object> selectMapById(@Param("id") Integer id);
- 使用xml
与mybatis一致,但是需要声明位置,以下是默认位置
mybatis-plus.mapper-locations=
高优先级查询 Lambda
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// Consumer<Param> Param是当前条件构造器
// 优先执行lambda表达式内容
queryWrapper.isNotNull("username").and(i -> {
// i 是当前条件构造器
i.isNull("password")
.or()
.eq("username", "admin");
});
public interface Nested<Param, Children> extends Serializable {
default Children and(Consumer<Param> consumer) {
return this.and(true, consumer);
}
...
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Service CRUD 接口介绍
说明:
- 通用 Service CRUD 封装 IService 接口,进一步封装 CRUD 采用
get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分Mapper
层避免混淆, - 泛型
T
为任意实体对象 - 建议如果存在自定义通用 Service 方法的可能,请创建自己的
IBaseService
继承Mybatis-Plus
提供的基类 - 对象
Wrapper
为 条件构造器
interface IService<T.>
T为实体类
class ServiceImpl<M extends BaseMapper<T.>, T> implements IService<T.>
- M --> Mapper接口
- T --> 实体类
Service 拓展
- 在自带的Service上拓展
接口
public interface UserService extends IService<User> {
}
接口实现
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
Service 使用
查询
long l = userService.count();
System.out.println("count ==> " + l);
添加
ArrayList<User> list = new ArrayList<>();
...
list.add(user1);
list.add(user2);
list.add(user3);
boolean b = userService.saveBatch(list);
saveOrUpdate有id -> 修改,无id -> 添加
雪花算法
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
了解更多 👉 什么是雪花算法? - 袁子弹 - 博客园 (cnblogs.com)
- 背景
需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。 - 数据库分表
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在-台数据库服务器的一-张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
单表数据拆分有两种方式:垂直分表和水平分表。
分页插件
配置文件
@Configuration
//扫描mapper包下的接口
@MapperScan("com.learn.mybatisplus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
}
Page
Page属性
protected List<T> records; // 当前页数据
protected long total;// 总记录数
protected long size;
protected long current;
protected List<OrderItem> orders; // 排序字段信息
protected boolean optimizeCountSql;
protected boolean searchCount;
protected boolean optimizeJoinOfCountSql;
protected String countId;
protected Long maxLimit;
Test
// 第几页 每页多长
Page<User> page = new Page<>(1, 3);
userMapper.selectPage(page, null);// 这样page 就有东西了 ,第二个参数是查询条件,QueryWrapper
System.out.println("getPages ==> " + page.getPages());
System.out.println("getCurrent ==> " + page.getCurrent());
System.out.println("getSize ==> " + page.getSize());
System.out.println("getRecords ==> " + page.getRecords());
System.out.println("getTotal ==> " + page.getTotal());
System.out.println("hasNext ==> " + page.hasNext());
System.out.println("hasPrevious ==> " + page.hasPrevious());
System.out.println("optimizeCountSql ==> " + page.optimizeCountSql());
System.out.println("optimizeJoinOfCountSql ==> " + page.optimizeJoinOfCountSql());
System.out.println("searchCount ==> " + page.searchCount());
System.out.println("maxLimit ==> " + page.maxLimit());
System.out.println("getOrders ==> " + page.getOrders());// Deprecated
System.out.println("isOptimizeCountSql ==> " + page.isOptimizeCountSql());// Deprecated
System.out.println("isSearchCount ==> " + page.isSearchCount());// Deprecated
System.out.println("getCountId ==> " + page.getCountId());// Deprecated
Console
getPages ==> 3 // 页总数
getCurrent ==> 1 // 当前页码
getSize ==> 3 // 本页数据条数
getRecords ==> [User(id=1, username=zhangsan1, password=123, roleid=1), User(id=2, username=zhangsan2, password=123, roleid=1), User(id=3, username=zhangsan3, password=123, roleid=3)]
getTotal ==> 7 // 该表记录数 受searchCount影响
hasNext ==> true
hasPrevious ==> false
optimizeCountSql ==> true // 自动优化 COUNT SQL
optimizeJoinOfCountSql ==> true // 优化计数Sql的连接
searchCount ==> true // 是否进行 count 查询
maxLimit ==> null // 单页分页条数限制,默认无限制。
getOrders ==> [] // 排序字段后的信息 Deprecated
isOptimizeCountSql ==> true // 自动优化 COUNT SQL Deprecated
isSearchCount ==> true // Deprecated
getCountId ==> null // Deprecated
乐观锁与悲观锁
并发时防止出现脏数据,
场景:
A先拿到x=10准备进行x-1操作
A拿到x并在更新x前B拿到了x=10,将x+1
B先完成x+1
最后x=10,本应该是先x-1再x+1
解决:加锁,A拿到x时B排队等候
乐观锁更新时进行检查版本等(检查是否被修改过)
悲观锁是该表正在被使用时,其他操作者只能等待操作完成
MyBatis-Plus的乐观锁
配置文件 @Bean MybatisPlusInterceptor
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());// 添加乐观锁插件
添加字段version 并使用 @Version 注解
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer roleid;
@Version
private Integer version;
}
通用枚举
枚举添加@EnumValue,不然添加的时候默认放的字符,现在可以指定那个属性放到数据库
@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;
}
}
@Data
public class User {
private Integer id;
@EnumValue
private SexEnum sex;
private String username;
private String password;
private Integer roleid;
}
# 扫描通用枚举
mybatis-plus.type-enums-package=com.learn.mybatisplus.enums
@Test
public void test07(){
// 通用枚举
User user = new User();
user.setSex(SexEnum.FEMALE);
}
代码生成器
添加代码生成器核心和模板模板引擎依赖
pom.xml
<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.28</version>
</dependency>
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://", "username", "password")// 创建连接
.globalConfig(builder -> {// 全局配置
builder.author("ctp") // 作者
// .enableSwagger()// swagger 模式
.fileOverride() // 覆盖生成文件
.outputDir("E://mybatis_plus_out"); // 输出目录
})
.packageConfig(builder -> {// 设置包
builder.parent("com.learn.mybatisplus") //父包名
//.moduleName("mybatisplus")//设置父包模块名 生成的包全在这里
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "E://mybatis_plus_out"));// 映射文件生成路径
})
.strategyConfig(builder -> { // 策略配置
builder.addInclude("t_user")// 需要逆向生成的表名
.addTablePrefix("t_");//设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine())// 使用freemarker模板引擎
.execute();
}
多数据源
pom.xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
# 多数据源
# primary数据源 必须在下面定义 spring.datasource.dynamic.datasource.master
# 可以自由定义spring.datasource.dynamic.primary=xxx时就必须存在spring.datasource.dynamic.datasource.xxx
spring.datasource.dynamic.primary=master
# 严格匹配数据源,默认false. true未匹配到指定数据源(找不到该数据库)时抛异常, false使用默认数据源(找不到时使用默认,不报错)
spring.datasource.dynamic.strict=false
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://
spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.master.username=
spring.datasource.dynamic.datasource.master.password=
spring.datasource.dynamic.datasource.db_sims.url=
spring.datasource.dynamic.datasource.db_sims.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.db_sims.username=
spring.datasource.dynamic.datasource.db_sims.password=
# 日志输出 StdOutImpl是自带的
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=t_
mybatis-plus.global-config.db-config.id-type=auto
public interface UserService extends IService<User> {
}
public interface SIMSUserService extends IService<SIMSUser> {
}
@DS("master")
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
@DS("db_sims")
@Service
public class SIMSUserServiceImpl extends ServiceImpl<SIMSUserMapper, SIMSUser> implements SIMSUserService {
}
@TableName("tb_user")
@Data
public class SIMSUser {
@TableId("userid")
private Integer id;
@TableField("username")
private String name;
// ...
}
@Data
public class User {
private Integer id;
private String username;
// ...
}
System.out.println(userService.getById(1));
System.out.println(simsUserService.getById(1));
MyBatisX
MyBatis-Plus为我们提供了强大的mapper和service模板,能够大大的提高开发效率但是在真正开发过程中,MyBatis-Plus并不能为我们解决所有问题,例如一些复杂的SQL,多表联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可以使用MyBatisX插件MyBatisX-款基于IDEA的快速开发插件,为效率而生。
IDEA插件搜索MyBatisX安装启用并重启
接口与xml映射文件可以相互跳转
MyBatisX的功能需要IDEA先连接数据库才能使用
选中表让后选生成器
生成实体类信息选项
- module path:模块,点击输入框弹出当前模块,选中即可
- base package:例如com.learn
- base path:包所在的base路径
- superClass:父类
- relative package:存实体的包,如entity或model或bean
- ignore field prefix/suffix: 忽略字段的前/后缀
- ignore table prefix/suffix:忽略表的前/后缀
生成Service、Mapper等文件信息选项
- annotation:使用的MyBatis-Plus版本
- options:生成的各个组件的选项
- comment:注释
- toString/hashCode/equals:选择Lombok这个就不选
- Lombok:生成setter/getter等
- template:选择版本模板格式
- mapperInterface:mapper接口生成的位置
- mapperXML:映射文件生成的位置
- serviceImpl:业务实现类生成的位置
- serviceInterface:业务接口生成的位置
点击Finsh即可生成
快速生成CURD
alt + Enter
选中,对应xml映射文件自动生成代码
根据方法名设置参数