【MyBatisPlus】一文带你快速上手MyBatisPlus
文章目录
- MyBatisPlus学习笔记
-
- 前言
- 1、MyBatisPlus概述
- 2、快速体验
- 3、CRUD接口
-
- 3.1 Mapper层CRUD接口
-
- 3.1.1 Insert
- 3.1.2 Delete
- 3.1.3 Update
- 3.1.4 Select
- 3.2 Service层CRUD接口
-
- 3.2.1 Save
- 3.2.2 Remove
- 3.2.3 Update
- 3.2.4 Get
- 3.3 自定义SQL接口
- 4、常用注解和配置
-
- 4.1 @TableId
- 4.2 @TableField
- 4.3 @TableLogic
- 5、条件构造器和常用接口
-
- 5.1 QueryWrapper
- 5.2 UpdateWrapper
- 5.3 使用Condition动态组装条件
- 5.4 LamdaQueryWrapper
- 5.5 LamdaUpdateWrapper
- 6、插件
-
- 6.1 MyBatisPlus分页插件
- 6.2 悲观锁与乐观锁
- 6.3 MyBatisX插件
- 7、通用枚举
- 8、代码生成器
-
- 8.1 快速生成
- 8.2 交互式生成
- 9、多数据源
MyBatisPlus学习笔记
前言
本文是参考MyBatisPlus官网对MyBatisPlus的一个学习笔记,主要是对MyBatisPlus的一个简单的入门学习,大致对MyBatisPlus有一个整体认知,熟悉使用MyBatisPlus提供的各种API(比如MyBatisPlus提供的增删改查接口),以及各种便利的特性和插件(比如自动生成代码、MyBatisPlus分页插件)。当然主要目的是想利用费曼学习方法将学到的知识进行一个输出,这样能够加深我对知识有一个更加深刻的认知,因为这些知识在官方是都能够查看到的,我用自己的认知、自己的排版对这些知识进行重复输出。
1、MyBatisPlus概述
-
MyBatisPlus是什么?
MyBatisPlus(简称MP)是MyBatis的增强版,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。和MyBatis一样是一款数据访问层的框架,可以大大简化对SQL的操作
-
官方文档地址:MyBatis-Plus
-
Gitee开源地址:mybatis-plus: mybatis
-
GitHub开源地址:baomidou/mybatis-plus
-
创始人:青苗
-
-
MyBatisPlus的t特点:
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
-
支持的数据库
- 国外:MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb,informix,TDengine,redshift
- 国内:达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库
-
框架结构
2、快速体验
-
Step1:搭建环境
1)建表(使用的是MySQL)
建立一张 user 表
DROP TABLE IF EXISTS user;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) );
往表中添加数据:
DELETE FROM user;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');
2)导入依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><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></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency>
3)编写配置文件
application.yml
server:port: 8080spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTCusername: rootpassword: 32345678mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启标准日志
-
Step2:编写实体类
@Data public class User {private Long id;private String name;private Integer age;private String email; }
-
Step3:编写Mapper
package com.hhxy.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.hhxy.entity.User; import org.apache.ibatis.annotations.Mapper;@Mapper public interface UserMapper extends BaseMapper<User> {/*说明: BaseMapper<T> BaseMapper是MP封装了通用CRUD的接口,Mapper只需要集成BaseMapperT为任意实体类,只要让Mapper集成BaseMapper并且关联对应的实体类,就能使用Mapper对象操作实体类对应的表了*/ }
-
Step4:扫描Mapper
在SpringBoot的启动类上添加
@MapperScan("com.hhxy.mapper")
注解PS:如果我们在Mapper接口上添加了
@Mapper
注解,则这个注解可以省略 -
Step5:编写测试类
@SpringBootTest public class SampleTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelect() {System.out.println(("----- selectAll method test ------"));List<User> userList = userMapper.selectList(null);Assertions.assertEquals(5, userList.size());userList.forEach(System.out::println);}}
知识拓展:关于MP中的SQL是如何确定要操作的表是谁
我们在使用MP时,我们自己编写的Mapper必须继承BaseMapper,通过这一步,我们MP底层会自动将T映射为SQL操作表,举个例子吧😄:当我们的T是User时,那么SQL操作的表就是user
那如果数据库中的表是tb_user,如何确保SQL操作的表是tb_user呢?
方案一:最直接的方法肯定是修改entity的格式,比如可以将entity修改成tb_user、TbUser、tbUser、Tb_User、Tb_User。
方案二:通过
@TableName
注解映射表名,比如@TableName("tb_user")
方案二:通过编写配置文件,给user添加一个前缀
mybatis-plus: global-config:db-config:table-prefix: tb_
需要注意的是,通过配置文件是全局有效的,也就是所有的实体类都会增加一个前缀
tb_
3、CRUD接口
MP在MyBatis的基础上做了增强,底层封装了大量通用的SQL,主要有
BaseMapper<T>
和IService<T>
两个CRUD接口,其中IService的实现类是ServiceImpl<M, T>
,BaseMapper中的方法以insert
、delete
、update
、select
开始,IService中的方法以save
、remove
、get
开始
- insert、delete、update的返回值都是int类型,返回值为0说明无数据更新,此时SQL执行失败
- save、remove的返回值都是boolean类型,返回值为false说明SQL执行失败
3.1 Mapper层CRUD接口
参数说明:
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
Wrapper | wrapper | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
Collection<? extends Serializable> | idList | 主键 ID 列表(不能为 null 以及 empty) |
Serializable | id | 主键 ID(可以是任何实现了序列化接口的数据类型) |
Map<String, Object> | columnMap | 表字段 map 对象 |
IPage | page | 分页查询条件(可以为 RowBounds.DEFAULT) |
备注:基本数据类型的包装类都实现了序列化接口,String也实现了序列化接口
3.1.1 Insert
int insert(T entity)
:插入一条记录,返回值为更新数据的条数
@Testpublic void testInsert(){User user = new User("张三", 23, "123@qq.com");
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );int result = userMapper.insert(user);System.out.println("insert = " + result); // result = 1// MP很贴心,它会将Insert后自动生成的id,重新赋值给user对象(不用向MyBatis还要进行配置才能获取生成id)System.out.println("MP自动生成的id为: "+user.getId());}
备注:不止是BaseMapper的Insert方法能够通过getId直接获取自动生成的Id,IService的save方法也能够通过getId直接获取自动生成的Id。总的来讲MP真的简化了MyBatis,让开发变得更简单😄
3.1.2 Delete
-
int deleteById(Serializable id)
:根据id删除一条记录,返回值为更新数据的条数 -
int deleteById(T entity)
:根据entity的id删除一条记录,返回值为更新数据的条数注意事项:entity必须具有
getId()
这个方法,否则会报错PS:其实可以通过
@TableId
配置自定义的主键,详情键第四节【常用注解和配置】 -
int deleteByMap(Map<String, Object> columnMap)
:以map的value作为条件进行删除注意事项:map的key必须要与表中的字段进行对应(不区分大小写)
-
int deleteBachIds(Cellection<?> idList)
:批量删除 -
int delete(Wrapper<T> queryWrapper)
:条件删除
int result = 0;// DELETE FROM user WHERE id=?;
result = userMapper.deleteById(1613825708092678146L);User user = new User(1L,"张三", 23, "123@qq.com");
// DELETE FROM user WHERE id=?;
result = userMapper.deleteById(user);Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 23);
// DELETE FROM user WHERE name=? AND age=?;
result = userMapper.deleteByMap(map);List<Long> idList = Arrays.asList(1L, 2L, 3L);
// DELETE FROM user WHERE id IN (?, ?, ?);
result = userMapper.deleteBatchIds(idList);
3.1.3 Update
-
int updateById(T entity)
:根据id进行修改entity必须具有
getId()
这个方法,否则会报错 -
int update(T entity, Wrapper<T> updateWrapper)
:根据条件进行修改
int result = 0;User user = new User(1L,"张三", 23, "123@qq.com");
// UPDATE user SET name=?, age=?, email=? WHERE id=?;
result = userMapper.updateById(user);
3.1.4 Select
-
T selectById(Serializable id)
:根据id进行查询 -
List<T> selectBatchIds(Collection<? extends Serializable> idList)
:批量查询 -
List<T> selectByMap(Map<String, Object> columnMap)
:根据map的value进行条件查询注意事项:map的key必须要与表中的字段进行对应(不区分大小写)
-
List<T> selectList(Wrapper<T> queryWrapper)
:条件查询
// SELECT id,name,age,email FROM user WHERE id=?;
User user = userMapper.selectById(1L);List<Long> idList = Arrays.asList(1l, 2l, 3l);
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? );
List<User> users = userMapper.selectBatchIds(idList);Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 23);
// SELECT id,name,age,email FROM user WHERE name = ? AND age = ?;
List<User> users = userMapper.selectByMap(map);// SELECT id,name,age,email FROM user;
List<User> users1 = userMapper.selectList(null);
3.2 Service层CRUD接口
类型 | 参数名 | 描述 |
---|---|---|
int | batchSize | 插入批次数量 |
T | entity | 实体对象 |
Wrapper | updateWrapper | 实体对象封装操作类 UpdateWrapper |
Collection | entityList | 实体对象集合 |
Collection<? extends Serializable> | idList | idList |
Function<? super Object, V> | mapper | 转换函数 |
3.2.1 Save
-
boolean save(T entity)
:新增一条记录 -
boolean saveBatch(Collection<T> entityList)
:批量添加温馨提示:
-
使用saveBatch,最好在数据库连接的url中添加一个
rewriteBatchedStatements=true
参数,实现高性能的批量插入 -
使用saveBatch,底层使用了事务,执行多条新增只会提交一次事务;但是如果在for循环中使用,会提交多次事务(不建议在循环中使用saveBatch方法)
-
saveBatch(Collection entityList)底层就是调用saveBatch(Colection entityList, int batchSize),只是前一个他设置了默认值batchSize=1000,也就是一个批次会插入1000条,超过一千条需要等待下一批次执行
备注:saveBatch底层是通过JDBC的executeBatch实现的。批次只针对增删改操作,它是指将执行SQL的语句的请求先存起来,等到达到一定数量,就当作一批发送给MySQL服务器,让MySQL服务器一次将这些SQL执行完,这样做可以避免频繁和MySQL数据库交互,造成效率低下
参考文章:
- MyBatis-plus批量写入数据方法saveBatch速度很慢原因排查
- Mybatis-Plus批量插入的简单自测
- Mybatis Plus saveBatch批量插入如何高效
- Java-Mysql之批处理_veejaLiu的博客-CSDN博客
- 什么是批处理?
-
-
boolean saveBatch(Colection<T> entityList, int batchSize)
:批量添加当batchSize=1时,都是一条一条执行单一的insert语句
当batchSize>=list.size()时,底层通过executeBatch方法将list中的数据拼接成一条SQL,最终执行性一条SQL
-
boolean saveOrUpdate(T entity)
: -
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper)
: -
boolean saveOrUpdateBatch(Collection<T> entityList)
: -
boolean saveOrUpdateBatch(Collection<T>entityList, int batchSize)
:
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
User user = new User("张三", 22, "123@qq.com");
boolean result = userService.save(user);List<User> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {User user = new User("张三", 18, "123@qq.com");users.add(user);
}
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? ),( ?, ?, ?, ? ),( ?, ?, ?, ? );
boolean result = userService.saveBatch(users);// 执行三次: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
boolean result = userService.saveBatch(users, 1);
3.2.2 Remove
boolean removeById(Serializable id)
:根据id删除数据boolean removeById(T entity)
:根据id删除数据boolean removeById(Serializable id, boolean useFill)
:boolean removeBatchByIds(Collection<?> list)
:更具id进行批量删除boolean removeBatchByIds(Collection<?> list, int batchSize)
:boolean removeBatchByIds(Collection<?> list, boolean useFill)
:boolean removeBatchByIds(Collection<?> list, int batchSize, boolean useFill)
:
3.2.3 Update
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
3.2.4 Get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
3.3 自定义SQL接口
这个和MyBatis中是高度重合的,我就不在这里继续赘述了,如果想了解可以参考这篇文章:初识MyBatisPlus
MP充分践行了它的理念:只做增强,不做修改
4、常用注解和配置
此外还有
@TableName
注解比较常用,这个已经在前面学习过了
4.1 @TableId
-
@TableId
:用于映射主键MP默认将id作为注解,如果数据库中主键非id,会报错。比如我们数据库中的注解为uid,实体类的字段也为uid,此时需要在实体类的uid上添加一个@TableId注解,告诉MP uid是主键
-
value属性
前面是数据库的主键为uid,实体类中的属性也是uid。但如果数据库中主键是uid,实体类中的主键是id呢?此时就需要使用@TableId注解的value属性了。具体使用方式:
给实体类的uid属性上添加
@TableId(value = "uid")
,只有value一个属性,还可以省略为@TableId(“uid”) -
type属性
IdType 说明 AUTO 使用id自增策略(前提是数据库也要开启id自增) NONE 不手动设置主键值,MP将默认给出一个 Long 类型的字符串作为主键 INPUT 手动设置主键值(不设置就没有) ASSIGN_ID 使用雪花算法生成主键值 ASSIGN_UUID 使用uuid生成主键值 MP的id生成策略默认是雪花算法,如果我们不想使用雪花算法,需要使用@TableId注解的type属性进行配置
给实体类的id属性上添加
@TableId(type = idType.AUTO)
注意:手动设置id的优先级要高于MP的id生成策略
知识拓展:
-
拓展一:通过配置文件配置主键生成策略
mybatis-plus:global-config:db-config:id-type: auto # assign_id、assign_uuid、input、none
-
拓展二:雪花算法
简介:雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的 主键的有序性
特点:全局唯一性(生成的id与当前时间相关)、递增性、高可用性(能够确保任何时候都能生成正确的id)、高性能(特别适合高并发场景)
核心思想:
长度共64bit(一个long型)。
首先是一个符号位,
1bit
标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负 数是1,所以id一般是正数,最高位是0。41bit
时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。10bit
作为机器的ID(5个bit是数据中心,5bit的机器ID,可以部署在1024个节点)。12bit
作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)为了应对数据规模增长导致的压力,我们需要对数据库进行扩展。常见的扩展方式有:业务分库、主从复制、数据库分表
优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高
1)数据库分表
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务 继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据, 如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进 行拆分。
单表数据拆分有两种方式:垂直分表和水平分表。示意图如下:
垂直分表:拆分字段。垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。 例如,前面示意图中的 nickname 和 description 字段,假设我们是一个婚恋网站,用户在筛选其他用 户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展 示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外 一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。
水平分表:拆分记录。水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以 作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过 1000 万就要分表了;而对于一些简单的表,即使存储数据超过 1 亿行,也可以不分表。 但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性 能瓶颈或者隐患。 水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理
体验分表:以主键id为例
1)方式一:通过分段的方式分表。
选取适当的分段范围,比如可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1中, 1000000 ~ 1999999 放到表2中,以此类推。
复杂点:分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会 导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适 的分段大小。
优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万, 只需要增加新的表就可以了,原有的数据不需要动
缺点:分布不均匀。假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而 另外一个分段实际存储的数据量有 1000 万条
2)方式二:通过取模的方式分表。假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来 表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号 为 6 的子表中。
复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题
优点:表分布比较均匀
缺点:扩充新的表很麻烦,所有数据都要重分布
-
-
4.2 @TableField
前面我们遇到主键不一致,我们可以使用@TableId注解进行映射,如果我们的其它属性和表的字段不一致,我们则需要使用@TableField注解进行映射
-
情况一:MP自动映射驼峰和下划线
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格。例如实体类属性userName,表中字段user_name ,此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格(这个在MyBatis中需要手动配置,而MP中则默认配置了)
-
情况二:使用
@TableField
注解映射字段若实体类中的属性和表中的字段不满足情况一,例如实体类属性name,表中字段username,此时需要在实体类属性上使用
@TableField("username")
设置属性所对应的字段 -
情况三:使用
@TableField
注解自动填充在项目开发时,对于一些字段(比如:create_time、update_time……),每次执行添加、修改操作时,都需要去填充,如果使用注入的方式会显得比较麻烦,所以我们需要设置字段自动填充,当进行该操作时,就自动为这些字段进行赋值
字段自动填充主要使用到了
@TableField
注解的fill
属性,该属性有以下取值:-
Step1:环境搭建
……
-
Step2:创建实体类,并在实体类需要填充的字段添加上
@TableField
@TableField(fill = FieldFill.INSERT_UPDATE) // 当进行更新(插入、删除、修改)操作时,就会进行自动填充private LocalDateTime updateTime;@TableField(fill = FieldFill.INSERT) // 当进行插入操作时,就会进行自动填充private LocalDateTime createTime;
-
Step3:编写元数据处理器,用于设置填充的数据
@Component public class MyMetaObjectHandler implements MetaObjectHandler {/* 插入时填充字段* @param metaObject*/@Overridepublic void insertFill(MetaObject metaObject) {if (metaObject.hasSetter("createTime")){metaObject.setValue("createTime", LocalDateTime.now());/* this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); */ //也可以使用这个,这是官方给出的自动填充方式}if (metaObject.hasSetter("updateTime")){metaObject.setValue("updateTime", LocalDateTime.now());}}/* 更新时填充字段(包括修改、删除)* @param metaObject*/@Overridepublic void updateFill(MetaObject metaObject) {if(metaObject.hasSetter("updateTime")){metaObject.setValue("updateTime", LocalDateTime.now());}} }
备注:如果实体类中只存在updateTime一个属性时,会报错,必须要同时具有updateTime和createTime两个属性(在实体类中添加@TableField注解进行自动填充,SpringBoot会自动调用MyMetaObjectHandler中所有的方法)。为了解决这个问题,我们可以添加一个if判断,判断该类是否具有该属性,然后再进行赋值操作。
注意事项
-
填充的数据类型要和实体类的数据类型保持一致,否则填充结果为null
-
注意区别官方给出的三个填充的方法
// 这个是通用的,插入和更新都可以使用 但是当字段存在值 的时候不进行填充 this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 这个是insert的时候用的,插入的时候时候强制进行填充 this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // update的时候使用,更新的时候强制进行填充 this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
备注:以上方法都需要MyBatisPlus3.3.0版本才能进行使用,值得一提的是fillStrategy再3.3.0版本中有bug,需要使用更高的版本(PS:反正我现在使用的是MP3.5😄问题不大)
-
-
4.3 @TableLogic
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
如果我们想要被删除的数据能够恢复,就需要使用到逻辑删除
示例:
-
Step1:创建一个逻辑删除字段
is_deleted
,同时设置默认值为0(0表示未删除,1表示删除) -
Step2:实体类中也添加一个逻辑删除的属性
isDeleted
-
Step3:给实体类的逻辑删除属性上添加一个
@TableLogic
注解 -
Step4:测试
int result = userMapper.deleteById(1613825708092678146L);
可以看到此时,删除的SQL并不是以前的
DELETE FROM user WHERE id=?;
了,而是换成了UPDATE tb_user SET is_deleted=1 WHERE id=? AND is_deleted=0;
要恢复逻辑删除的数据也能简单,直接在数据库中将is_deleted的值改成0就好了,需要注意的是逻辑删除后的数据,查询语句是无法查到的,同时update语句也是无法进行修改的
5、条件构造器和常用接口
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
5.1 QueryWrapper
-
QueryWrapper
示例一:使用QueryMapper进行查询
@Testpublic void test1() {// 查询姓张的,年龄在20~30之间,并且email字段不为空的用户信息QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.like("name", "张").between("age", 20, 30).isNotNull("email");/*SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND(name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)*/List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}
进行指定字段查询:
@Testpublic void test6(){// 查询指定字段(用户名、年龄、邮箱)QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.select("name", "age", "email"); // SELECT name,age,email FROM userList<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);maps.forEach(System.out::println);}
使用QueryMapper实现子查询:
@Testpublic void test7(){// 查询id小于100的用户信息QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.inSql("id","select id from user where id < 100"); // SELECT id,name,age,email,is_deleted FROM user WHERE (id IN (select id from user where id < 100))List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);maps.forEach(System.out::println);}
示例二:使用QueryMapper进行条件删除
@Testpublic void test2(){// 查询用户信息,按照年龄降序排序,如果年龄相同则按照id升序排序QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.orderByDesc("age").orderByAsc("id"); // SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 ORDER BY age DESC,id ASCList<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}
示例三:使用QueryMapper进行条件修改
@Testpublic void test4(){// 修改(年龄大于20并且姓张)或邮箱为null的用户信息QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.gt("age", 20).like("name", "张").or() // QueryMapper的连接条件默认是 AND.isNull("email");User user = new User("张三", 22, "123@qq.com"); // UPDATE user SET name=?, age=?, email=? WHERE (age > ? AND name LIKE ? OR email IS NULL)userMapper.update(user, queryWrapper);}
@Testpublic void test5(){// 修改年龄大于20并且(姓张或邮箱为null)的用户信息QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.gt("age", 20).and(qw->qw.like("name", "张").or().isNull("email"));User user = new User("张三", 22, "123@qq.com"); // UPDATE user SET name=?, age=?, email=? WHERE (age > ? AND (name LIKE ? OR email IS NULL))userMapper.update(user, queryWrapper);}
5.2 UpdateWrapper
示例一:
@Testpublic void test8(){// 修改 用户名中含有张并且(年龄大于20或者邮箱null)的用户信息UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();updateWrapper.like("name", "张").and(uw -> uw.gt("age", 20).or().isNull("email"));updateWrapper.set("name", "李四").set("age", 22);
// UPDATE user SET name=?,age=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))int result = userMapper.update(null, updateWrapper);System.out.println("result = " + result);}
某个字段自增或自减,MyBatiPlusm目前只有两种方式实现
使用
UpdateWrapper
拼接// 使用UpdateWrapper UpdateWrapper wrapper = new UpdateWrapper(); wrapper.eq("id",【实体类对象】.getBid()); wrapper.setSql("'自增字段' = '自增字段' + 1"); // 注意SQL中没有 += 这个符号 【service对象】.update(wrapper); // 直接调用update方法 【service对象】.update().setSql("stock = stock -1").eq("voucher_id", voucherId).update();
直接写原生sql到xml中
略……
方式三:自定义一个自增自减字段的方法,参考文章:mybatis-plus 自定义UpdateWrapper(一)实现列自增
// 库存充足,秒杀券库存减一// 方式一:使用Service对象的update方法/*boolean flag = seckillVoucherService.update().setSql("stock = stock -1").eq("voucher_id", voucherId).update();*/// 方式二:使用LambdaUpdateWrapper的setSql方法UpdateWrapper updateWrapper = new UpdateWrapper();updateWrapper.eq("voucher_id", voucherId);updateWrapper.setSql("stock = stock -1");boolean flag = seckillVoucherService.update(updateWrapper);// 方式三:使用自定义的LambdaUpdateWrapper/*CustomLambdaUpdateWrapper<SeckillVoucher> updateWrapper = new CustomLambdaUpdateWrapper<>();updateWrapper.descField(SeckillVoucher::getStock, 1).eq(SeckillVoucher::getVoucherId, voucherId);boolean flag = seckillVoucherService.update(updateWrapper);*/// 方式四:在xml中编写SQL语句
备注:相关完整代码请参考博主Gitee仓库中的hmdp项目
5.3 使用Condition动态组装条件
前面我们学习了QueryWrapper的常用方法,在这些方法的基础上,我们可以都可以通过添加一个condition条件,进行判断 是否需要添加改条件,这个类似于MyBatis中的动态SQL的标签,只有满足condition条件,才能够将改条件组装到SQL上
@Testpublic void test9(){// 模拟从前端接收参数String username = "";Integer ageBegin = 20;Integer ageEnd = 30;// 模糊查询姓某某的人,同时20<age<30的用户信息QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.like(StringUtils.isNotBlank(username), "name", username).ge(ageBegin != null, "age", ageBegin).le(ageEnd != null, "age", ageEnd);// SELECT id,name,age,email,is_deleted FROM user WHERE (age >= ? AND age <= ?)List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}
5.4 LamdaQueryWrapper
前面我们使用QueryWrapper和UpdateWrapper时,字段都是自己来编写的,如果你尝试过就一定知道,自己通过字符串的形式编写是没有一点提示的,这样很容易出现字段名写错,导致SQL执行失败的情况。而MP的开发者也是知道这一点的,于是就提供了LamdaQueryWrapper,这样我们可以通过lamda表达式的形式确定字段名,这在IDEA中是有提示的,基本上可以杜绝字段写错的问题。这个完全可以替代QueryWrapper,前提是你的JDK必须大于1.8,因为Lamda表达式是JDK1.8引入的
@Testpublic void test10(){// 模拟从前端接收参数String username = "";Integer ageBegin = 20;Integer ageEnd = 30;// 模糊查询姓某某的人,同时20<age<30的用户信息LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.like(StringUtils.isNotBlank(username), User::getName, username).ge(ageBegin != null, User::getAge, ageBegin).le(ageEnd != null, User::getAge, ageEnd);// SELECT id,name,age,email,is_deleted FROM user WHERE (age >= ? AND age <= ?)List<User> users = userMapper.selectList(lambdaQueryWrapper);users.forEach(System.out::println);}
5.5 LamdaUpdateWrapper
同样的这个也可以完全替代UpdateWrapper,推荐使用这个😃
@Testpublic void test11(){// 修改 用户名中含有张并且(年龄大于20或者邮箱null)的用户信息LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();lambdaUpdateWrapper.like(User::getName, "张").and(uw -> uw.gt(User::getAge, 20).or().isNull(User::getEmail));lambdaUpdateWrapper.set(User::getName, "李四").set(User::getAge, 22);
// UPDATE user SET name=?,age=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))int result = userMapper.update(null, lambdaUpdateWrapper);System.out.println("result = " + result);}
6、插件
分页插件和乐观锁插件都是内置再MP中的,只需要配置以下就能使用了,而MyBatisX插件需要到插件商店中进行下载
6.1 MyBatisPlus分页插件
-
Step1:搭建环境
请参考第二节【快速体验】
-
Step2:创建MP配置类,配置拦截器
@Configuration public class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){// 1、创建MP拦截器对象MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 2、将MP的分页插件添加到拦截器对象中interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;} }
备注:如果没有加
@Configuration
注解,则需要在启动类上添加@MapperScan
注解扫描该类 -
Step3:测试
@Testpublic void testPage(){// 创建分页构造器Page<User> page = new Page<>(2,3);// 执行SQL:分页查询所有条件(还可以使用QueryWrapper对象进行条件过滤)userMapper.selectPage(page, null);// 这里和前面获取生成id是一样的,MP将查询的数据重新赋值给了Page对象System.out.println("当前页码 = " + page.getCurrent());System.out.println("当前页展示的记录条数 = " + page.getSize());System.out.println("当前页展示的记录: ");page.getRecords().forEach(System.out::println);System.out.println("分页查询的总页数 = " + page.getPages());System.out.println("分页查询的总记录数 = " + page.getTotal());System.out.println("是否存在上一页: " + page.hasPrevious());System.out.println("是否存在下一页: " + page.hasNext());}
知识拓展:XML自定义分页
如果在我们像自定义一个分页查询的SQL,并且需要使用MP内置的分页插件
UserMapper:
/* 通过年龄查询用户信息并进行分页* @param page 如果想使用MP内置的分页插件,则分页对象必须放在第一个* @param age* @return*/Page<User> selectPageByAge(@Param("page") Page<User> page, @Param("age") Integer age);
<select id="selectPageByAge" resultType="User">select id, name, age, emailfrom userwhere age > #{age}</select>
备注:这里配置了实体类的别名
测试结果:
6.2 悲观锁与乐观锁
在学习悲观锁和乐观锁之前,我们先模拟一个场景:
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。 现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。
像上面那样,不加锁,就会导致操作并发时出现严重故障,所以我们对于这种事务性操作是一定要进行枷锁校验的,这样才能保证数据的一致性,不然系统的使用者蒙受巨大损失
- 乐观锁:对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,一种是通过版本号,一种是通过时间戳。
- 悲观锁:对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定。通常依靠数据库提供的锁机制实现,比如mysql的排他锁,select … for update来实现悲观锁。需要注意的是使用悲观锁时,需要关闭数据库自动提交功能,即:set autocommit = 0;
如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。
示例一:
模拟数据冲突
数据冲突包括:丢失修改、不可重复读、脏读
-
Step1:环境搭建
1)建表
2)插入数据
3)导入依赖
4)编写配置文件
-
Step2:编码
1)编写实体类
2)编写Mapper
-
Step3:测试
@Testpublic void testDataConflict(){// 小李查看价格Product p1 = productMapper.selectById(1L);System.out.println("小李取出的价格:" + p1.getPrice());// 小王查看价格Product p2 = productMapper.selectById(1L);System.out.println("小王取出的价格:" + p2.getPrice());// 小李将价格加了50元,存入了数据库p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1);System.out.println("小李修改结果:" + result1);// 小王将商品减了30元,存入了数据库p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);System.out.println("小王修改结果:" + result2);// 获取商品最终的结果Product p3 = productMapper.selectById(1L);System.out.println("最后的结果:" + p3.getPrice());}
示例二:
使用悲观锁解决数据冲突
-
Step1:搭建环境
略……
-
Step2:给表添加一个version字段。
-
Step3:在编写实体类时,使用
@Version
注解标记version属性。取出记录时,获取当前version;更新时,version + 1,如果where语句中的version版本不对,则更新失败 -
Step4:编写配置类
@Configuration public class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}}
-
Step5:测试
测试代码和示例一相同,略……
小李进行操作时,此时小王还没进行更新操作,version字段不变;当小王操作时,因为前面小李进行了更新操作,所以version+1了,和小王获取的version不同,更新操作失败!
示例三:
使用悲观锁解决数据冲突
-
Step1:搭建环境
略……
-
Step2:
6.3 MyBatisX插件
MyBatisX插件主要具有以下功能
- 通过Mapper接口快速在Mapper配置文件中生成statement
- 快速生成代码
7、通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举 来实现
-
Step1:搭建环境
1)建表(将sex的类型设置为int)
2)插入数据
3)导入依赖
4)编写配置文件
-
Step2:编码
1)编写枚举类
@Getter // 需要提供get方法,让外界能够获取到枚举对象 public enum SexEnum {MALE(1, "男"),FEMALE(2, "女");@EnumValue // 将注解所标识的值存储到数据库中(不添加这个注解,则添加的值是MALE或FEMALE)private Integer sex;private String sexName;SexEnum(Integer sex, String sexName) {this.sex = sex;this.sexName = sexName;}}
备注:MP3.5.2版本以前(3.5.2开始就已经废弃了,不需要配置就能直接使用通用没觉了),还需要再配置文件中进行配置,才能成功使用通用枚举
2)编写实体类
@Data @NoArgsConstructor @AllArgsConstructor public class User {private Long id;private String name;private Integer age;private String email;private SexEnum sex; }
3)编写Mapper
-
Step3:测试
@Testpublic void testEnum(){User user = new User("张三", 23, "123@qq.com", SexEnum.MALE);int result = userMapper.insert(user);System.out.println("result = " + result);}
8、代码生成器
在MyBatis中我们可以通过逆向工程快速生成代码,从而节省大量的时间,但是MyBatis的逆向工程配置起来较为麻烦,所以MP简化了配置,让代码生成变得更加简单,堪称码农神器。让我们不必过多地重复哪些CRUD操作,只专注于核心业务。
代码生成器模板结构:
FastAutoGenerator.create("url", "username", "password")//2、全局配置.globalConfig(...)//3、包配置.packageConfig(...)//4、策略配置.strategyConfig(...)//5、模板引擎配置.templateEngine(...)//6、执行.execute();
8.1 快速生成
示例:
-
Step1:搭建环境
1)建库、建表
2)创建SpringBoot项目
3)引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--knife4j对应的starter,用于生成接口文档(knife4j集成了Swagger)--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.2</version></dependency><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.31</version></dependency>
4)编写配置文件
# 配置端口 server:port: 8080# Spring相关配置 spring:# 数据源相关配置datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTCusername: rootpassword: 32345678# MyBatisPlus相关配置 mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启标准日志type-aliases-package: com.hhxy.entity # 配置类型别名
-
Step2:编写生成器
注意:一定要使用绝对路径
public class FastAutoGeneratorTest {public static void main(String[] args) {// 1、配置数据源FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTC", "root", "32345678")// 2、全局配置.globalConfig(builder -> {builder.author("ghp") // 设置作者.enableSwagger() // 开启 swagger 模式.fileOverride() // 覆盖已生成文件.outputDir("/src/main/java"); // 指定输出目录})// 3、包配置.packageConfig(builder -> {builder.parent("com.hhxy") // 设置父包名.moduleName("") // 设置父包模块名.pathInfo(Collections.singletonMap(OutputFile.xml, "/src/main/resources/mapper")); // 设置mapperXml生成路径})// 4、策略配置.strategyConfig(builder -> {builder.addInclude("user") // 设置需要生成的表名(数据库中必须存在该表).addTablePrefix("tb_", "t_", "tbl_"); // 设置过滤表前缀})// 5、模板引擎配置.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板// 6、执行.execute();} }
备注:MyBatisPlus5.2开始,
fileOverride
方法被弃用了 -
Step3:运行方法
使用MyBatisX插件生成自定的SQL
我们需要通过insert
、delete
、update
、select
等关键次开头,IDEA会有提示
8.2 交互式生成
public class InteractiveAutoGeneratorTest {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.println("=====================数据库配置=======================");System.out.println("请输入 URL");String url = scan.next();System.out.println("请输入 username");String username = scan.next();System.out.println("请输入 password");String password = scan.next();FastAutoGenerator.create(url, username, password)// 全局配置.globalConfig((scanner, builder) -> builder.author(scanner.apply("=====================全局配置=======================\\n请输入作者名称")).outputDir(System.getProperty("user.dir") + "/src/main/java").commentDate("yyyy-MM-dd hh:mm:ss").dateType(DateType.TIME_PACK).enableSwagger().fileOverride().enableSwagger().disableOpenDir())// 包配置.packageConfig((scanner, builder) -> builder.parent(scanner.apply("=====================包配置=======================\\n请输入包名?")).moduleName(scanner.apply("请输入父包模块名?")).entity("entity").service("service").serviceImpl("serviceImpl").mapper("mapper").xml("mapper").other("utils").pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir")+"/src/main/resources/mapper")))// 策略配置.strategyConfig((scanner, builder) -> {builder.addInclude(getTables(scanner.apply("=====================策略配置=======================\\n请输入表名,多个参数使用逗号分隔"))).serviceBuilder().formatServiceFileName("%sService").formatServiceImplFileName("%sServiceImpl").entityBuilder() //实体类策略配置.enableLombok() //开启 Lombok.disableSerialVersionUID().logicDeleteColumnName("deleted") //逻辑删除字段.naming(NamingStrategy.underline_to_camel).columnNaming(NamingStrategy.underline_to_camel).addTableFills(new Column("create_time", FieldFill.INSERT), new Column("modify_time", FieldFill.INSERT_UPDATE)).enableTableFieldAnnotation() // 开启生成实体时生成字段注解.controllerBuilder().formatFileName("%sController").enableRestStyle().mapperBuilder().superClass(BaseMapper.class).formatMapperFileName("%sMapper").enableMapperAnnotation() //@mapper.formatXmlFileName("%sMapper");}).templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板.execute();}// 处理 all 情况protected static List<String> getTables(String tables) {return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));}
}
9、多数据源
一个项目中经常会使用到多个数据库,比如主从复制,主库来进行更新操作,从库用来进行查询操作,从而有效降低数据库库的负担,一定程度提高系统的效率。
关于主从复制相关配置可以参考:MySQL学习笔记
多数据源主要存在以下三种配置方式:
# 多主多从 纯粹多库(记得设置primary) 混合配置
spring: spring: spring:datasource: datasource: datasource:dynamic: dynamic: dynamic:datasource: datasource: datasource:master_1: mysql: master:master_2: oracle: slave_1:slave_1: sqlserver: slave_2:slave_2: postgresql: oracle_1:slave_3: h2: oracle_2:
主从复制相关配置:
spring:
# 配置数据源信息datasource:dynamic:# 设置默认的数据源或者数据源组, 默认值即为master
# primary: master# 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
# strict: falsedatasource:names:master,slave # 设置别名,可以随便取,但要与后面的前缀对应(可以配置多个从库)master:url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456slave:url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456
@Service
@DS("master") // 使用主库
public class UserServiceImpl implements UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;public List selectAll() {return jdbcTemplate.queryForList("select * from user");}@Override@DS("slave") // 使用从库public List selectByCondition() {return jdbcTemplate.queryForList("select * from user where age >10");}
}