只过了一年,很多教学视频和笔记里的有些方法和配置已经过时。多看官方文档,跟着官方文档敲代码,同时灵活应变才是应对快速迭代的框架的最佳方案。
MyBatisPlus可以节省大量工作时间,所有的CRUD代码可以通过自动化完成。
来自官网:
官方文档地址:https://mybatis.plus/guide/quick-start.html#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B
mybatis_plus
使用的user
表以及对应的字段如下:
id | name | age | |
---|---|---|---|
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] |
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)
);
-- 真实开发中,version(乐观锁)、deleted(逻辑删除)、gmt_create、gmt_modified
DELETE FROM user;
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]');
编写项目,初始化项目!
用IDE快速创建一个springboot项目后,进行以下步骤。
在pom.xml
中引入 spring-boot-starter
、spring-boot-starter-test
、mybatis-plus-boot-starter
和数据库连接依赖。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
使用mybatis-plus可以节省大量的代码,尽量不要同时导入mybatis和mybatis-plus,以免版本冲突。因为MyBatisPlus的starter会自动导入MyBatis。MySQL依赖的版本根据自己的MySQL版本进行修改。
老生常谈了。本文的配置全部在application.yml
中进行配置,因为配置的更有结构性。在application.properties
也可。
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
# 如果使用 MySQL8 驱动不同,url增加时区的配置 serverTimezone=GMT%2B8
传统方式: pojo-dao(连接MyBatis,配置mapper.xml文件)-service-controller
使用MyBatisPlus后,不用书写mapper便可直接进行CRUD的操作。
建立了User类,并使用lombok
插件进行快速建立方法和构造器。使用lombok前需要引入相关依赖。lombok入门资料:lombok实战指南。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* 主键ID
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 邮箱
*/
private String email;
}
直接继承MyBatisPlus内的BaseMapper
即可,里面自带了大量的CRUD接口。
// 在对应的Mapper上面实现基本的接口 BaseMapper
@Repository // 持久层
public interface UserMapper extends BaseMapper<User> {
// 所有的CRUD操作已经编写完成
// 不需要像以前的配置一大堆文件
}
// 扫描mapper文件夹,如果mapper上使用@mapper注解,则可以不用写此注解
@MapperScan("com.sjh.mybatis_plus.mapper")
@SpringBootApplication
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
另外提一嘴,使用了@mapper
注解进行注解Mapper后,不需要写@Repository也能被Spring IOC容器进行管理。
直接使用从BaseMapper继承过来的基础CRUD方法,selectList可以获取数据库里的对象列表。
但是需要写入一个参数,参数类型为Wrapper,为条件构造抽象类。当值为null时,没有添加额外条件,即查询所有的用户。Wrapper在文章末尾有详细说明。
代码如下:
@Test
void selectList() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
测试结果:
震惊,没有写任何的sql!MyBatisPlus帮忙写好了SQL语句和方法。
所有的sql是不可见的,为了了解执行的sql,因此需要搭配日志。
日志在配置文件里进行配置,配置如下:
# 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新运行测试方法,运行结果如下,SQL语句,数据源等详细信息都有展示
配置完日志后,自己观察自动生成的SQL,对后面的学习有很大的帮助。
最详细的CRUD方法在官方文档 https://mybatis.plus/guide/crud-interface.html
书写User插入测试代码,如下所示:
@Test
void insertTest() {
User user = new User();
user.setName("sjh");
user.setAge(1);
user.setEmail("[email protected]");
int insert = userMapper.insert(user); // 自动生成了id
System.out.println("insert = " + insert); // 受影响的行数
System.out.println("user = " + user);
}
运行结果如下,可以发现,没有设置id的时候,insert方法自动生成了id
原因:使用插入方法插入的id的默认值为全局的唯一ID
在主键上使用不同的注解,将会产生不同的ID。如果没有添加注解且主键为Number子类,默认自动生成雪花算法计算得到的ID。因此在主键属性上添加注解至关重要。添加的注解名称为@TableId
,添加内容如下。
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 主键字段名 |
type | Enum | 否 | IdType.NONE | 主键类型 |
值 | 描述 |
---|---|
AUTO | 数据库ID自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert前自行set主键值 |
ASSIGN_ID | 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator 的方法nextUUID (默认default方法) |
分布式全局唯一ID 长整型类型(please use ASSIGN_ID ) |
|
32位UUID字符串(please use ASSIGN_UUID ) |
|
分布式全局唯一ID 字符串类型(please use ASSIGN_ID ) |
默认生成的方式为NONE,当插入对象ID为空且类型为Number,使用雪花算法自动补充。
@Getter
public enum IdType {
/**
* 数据库ID自增
* 该类型请确保数据库设置了 ID自增 否则无效
*/
AUTO(0),
/**
* 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
*/
NONE(1),
/**
* 用户输入ID
* 该类型可以通过自己注册自动填充插件进行填充
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 分配ID (主键类型为number或string),
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
*
* @since 3.3.0
*/
ASSIGN_ID(3),
/**
* 分配UUID (主键类型为 string)
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
*/
ASSIGN_UUID(4);
private final int key;
IdType(int key) {
this.key = key;
}
}
@TableId
注解@TableId(type = IdType.AUTO)
private Long id;
数据库设置为主键自增
删除原本字段后,修改自动递增数为正常值,原本的自动递增数如下图所示。将自动递增修改为正常值(设置还原为了6)。
到此,主键自增方案设置成功。
分布式系统唯一ID生成:https://www.cnblogs.com/haoxinyue/p/5208136.html
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。
更新测试代码如下:
@Test
void updateTest() {
User user = new User();
user.setId(6L);
user.setName("John");
// 传入一个对象进行更新
int i = userMapper.updateById(user);
System.out.println("i = " + i);
}
运行结果:
**注意:**虽然方法是updateById,但是传入的参数是一个对象
创建时间修改时间等操作一般都是自动化完成的,并不希望手动更新。
阿里巴巴开发手册里说明,几乎所有的数据库表都得有字段:gmt_create和gmt_modified。并且需要自动化,即数据的创建时间和最后更新时间都得由程序来记录而不是人工设置。
在表中新增字段 create_time
,update_time
在Navicat中设置如下,类型和默认值都需要设置,将create_time和update_time的默认值设置为时间戳更新。
再次测试插入方法,同时实体类同步
private Date createTime;
private Date updateTime;
重新运行更新方法,在数据表中即可看到创建时间和更新时间生成
**不推荐原因:**数据库并没有资格随便更改。
删除数据库的默认值
在实体类字段属性上需要添加注释@TableField
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
自定义实现类MyMetaObjectHandler
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
// 更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
**注意:**MySQL不支持LocalDateTime转换,跟着开发文档里的LocalDateTime来没有效果。使用Date对象是最保险方法。
如果需要从蛇形命名映射为驼峰式命名,配置里进行配置。(默认开启,不配置也行)
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
设置完后,创建时间和更新时间都可以自动生成,而不必修改数据库表设计。
在面试过程中,经常被问到乐观锁和悲观锁。乐观锁和悲观锁在多线程中十分重要。
乐观锁:顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁。如果出现问题就再次更新值测试。(版本号version)
悲观锁:顾名思义十分悲观,它总是认为总会出现问题,无论干什么都会上锁后再去操作。
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
例:先查询获得版本号并更新版本号
-- A update user set name = "sjh", version = version + 1 where id = 2 and version = 1 -- B 如果该线程抢先完成,这个时候version = 2,会导致A修改失败 update user set name = "sjh", version = version + 1 where id = 2 and version = 1
前置操作:
数据库添加version字段,默认值为1**(记得数据库修改时,相应的实体类也得进行修改!!)**
乐观锁配置需要两步:
创建config包,编写配置类。插件配置后记得写@Bean
,将插件交给IOC容器进行托管。
@Configuration
@MapperScan("com.sjh.mybatis_plus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
@Version
注解@Version
private Integer version;
说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
newVersion
会回写到entity
中- 仅支持
updateById(id)
与update(entity, wrapper)
方法- 在
update(entity, wrapper)
方法下,wrapper
不能复用!!!
乐观锁正常运行情况
测试代码:
@Test
void optimisticLockerTest() {
// 1. 查询用户信息
User user = userMapper.selectById(1L);
// 2. 修改用户信息
user.setName("test01");
user.setEmail("[email protected]");
// 3. 执行更新操作
userMapper.updateById(user);
}
测试结果可得,数据成功被更改的同时,version自增1
查看日志,可以看到使用的SQL语句如下:
乐观锁运行失败情况
测试代码:
@Test
void optimisticLockerTest2() {
// 线程1
User user = userMapper.selectById(1L);
user.setName("test02");
user.setEmail("[email protected]");
// 模拟线程2插队
User user2 = userMapper.selectById(1L);
user2.setName("test0222222");
user2.setEmail("[email protected]");
userMapper.updateById(user2);
// 如果没有乐观锁就会覆盖插队线程的值
userMapper.updateById(user);
}
测试结果可得,user因为version不匹配,无法更新。
// 测试查询
@Test
void selectById() {
User user = userMapper.selectById(1L);
System.out.println("user = " + user);
}
// 测试批量查询
@Test
void selectByBatchId() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
// 按条件查询之使用map操作
@Test
void selectByMap() {
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
// 自定义查询条件
stringObjectHashMap.put("name", "test0222222");
List<User> users = userMapper.selectByMap(stringObjectHashMap);
users.forEach(System.out::println);
}
常用分页方法:
- 原始的limit进行分页
- pageHelper等第三方插件
- MyBatisPlus也有相应的配置插件
下文使用MyBatisPlus的分页插件进行分页查询。
具体步骤:
@Configuration
@MapperScan("com.sjh.mybatis_plus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
注意:有些网上的方法是将
MybatisPlusInterceptor
和OptimisticLockerInnerInterceptor
分开单独放在IOC容器中,增加可读性。实际测试表明,在这个版本的MyBatisPlus下并不能正常运行。
测试代码:
@Test
void pageTest() {
// 参数1:当前页;参数2:页面大小
Page<User> userPage = new Page<>(2, 5);
// 参数1:page对象;参数2:wrapper
userMapper.selectPage(userPage, null);
userPage.getRecords().forEach(System.out::println);
}
运行的SQL语句和结果。
@Test
void deleteById() {
int i = userMapper.deleteById(3L);
System.out.println("i = " + i);
}
直接删除一个id对应的User,并且返回受删除SQL影响的行数。
@Test
void deleteBatchIds() {
int i = userMapper.deleteBatchIds(Arrays.asList(4, 5, 7));
System.out.println("i = " + i);
}
运行结果:
@Test
void deleteByMap() {
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("name", "sfds");
int i = userMapper.deleteByMap(stringObjectHashMap);
System.out.println("i = " + i);
}
官方文档:https://mybatis.plus/guide/logic-delete.html
物理删除:从数据库中直接移除
逻辑移除:在数据库汇总没有被移除,而是通过一个变量来让它失效。deleted = 0 -> deleted = 1
管理员可以查看被删除的记录,防止数据的丢失,类似于回收站。
在数据库中添加一个deleted字段,默认值为0
在配置文件中进行配置逻辑删除,但是新版本的MyBatisPlus默认配置了,所以这个步骤不用配置也可以。
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
实体类中增加deleted字段,并增加注解@TableLogic
@TableLogic
private Integer deleted;
测试类进行测试
测试代码
@Test
void deleteById() {
int i = userMapper.deleteById(1L);
System.out.println("i = " + i);
}
测试结果如下,逻辑删除本质是update操作
并且可以观察得到,指定id删除后的user的deleted字段变为了1,标记成了被删除。
在平时的开发中,会遇到一些慢SQL。慢SQL极大程度影响了运行速度。
MyBatisPlus拥有SQL分析与打印的拓展,依赖 p6spy
组件,完美的输出打印 SQL 及执行时长 3.1.0
以上版本。
步骤:
引入p6spy
依赖
<dependency>
<groupId>p6spygroupId>
<artifactId>p6spyartifactId>
<version>3.9.1version>
dependency>
application.yml 配置,修改driverClass和URL
# DataSource Config
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8
在resources文件夹中添加spy.properties,配置内容如下
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
测试
使用selectById方法进行测试,可以看到console里面新增了SQL的运行时间记录。如果超过了慢SQL记录标准,运行时将会报错。
官方文档:https://mybatis.plus/guide/wrapper.html#abstractwrapper
当书写复杂SQL时,可以使用wrapper进行书写并运行。
测试数据表如下:
测试代码
// 查询name不为空的用户并且邮箱不为空的用户,年龄大于12岁
@Test
void selectList() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.isNotNull("name")
.isNotNull("email")
.ge("age", 12);
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}
console日志打印如下
==> Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name IS NOT NULL AND email IS NOT NULL AND age >= ?)
==> Parameters: 12(Integer)
Consume Time:8 ms 2021-07-08 16:10:47
Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name IS NOT NULL AND email IS NOT NULL AND age >= 12)
<== Columns: id, name, age, email, version, deleted, create_time, update_time
<== Row: 2, test0222222, 20, [email protected], 3, 0, null, 2021-07-08 10:09:29.0
<== Row: 10, Jack, 18, [email protected], 1, 0, 2021-07-08 16:05:07.0, null
<== Row: 11, Bobby, 14, [email protected], 1, 0, 2021-07-08 16:05:57.0, null
<== Total: 3
可以看到,需要符合条件的user才能被查询出来。被逻辑删除的数据尽管符合条件,但也不会被查询出来。
// 查询用户名为Maria的用户
@Test
void test02() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("name", "Maria");
System.out.println(userMapper.selectOne(userQueryWrapper));
}
运行结果日志
==> Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name = ?)
==> Parameters: Maria(String)
Consume Time:5 ms 2021-07-08 17:16:04
Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name = 'Maria')
<== Columns: id, name, age, email, version, deleted, create_time, update_time
<== Row: 12, Maria, 22, null, 1, 0, 2021-07-08 16:09:16.0, null
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5583098b]
User(id=12, name=Maria, age=22, email=null, version=1, deleted=0, createTime=Thu Jul 08 16:09:16 CST 2021, updateTime=null)
// 查询年龄在20-30岁之间的用户数量
@Test
void test03() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(userQueryWrapper);
System.out.println("count = " + count);
}
运行结果日志
==> Preparing: SELECT COUNT( * ) FROM user WHERE deleted=0 AND (age BETWEEN ? AND ?)
==> Parameters: 20(Integer), 30(Integer)
Consume Time:5 ms 2021-07-08 17:13:42
Execute SQL:SELECT COUNT( * ) FROM user WHERE deleted=0 AND (age BETWEEN 20 AND 30)
<== Columns: COUNT( * )
<== Row: 2
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4e83a98]
count = 2
// 查询名字里不含"e",邮箱后缀为"@qq.com"的用户
@Test
void test04() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.notLike("name", "e")
.likeLeft("email", "@qq.com");
List<Map<String, Object>> maps = userMapper.selectMaps(userQueryWrapper);
maps.forEach(System.out::println);
}
注意,likeLeft表示的是左边模糊右边确认。看下面的源码便可明白。
运行结果日志:
==> Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name NOT LIKE ? AND email LIKE ?)
==> Parameters: %e%(String), %@qq.com(String)
Consume Time:6 ms 2021-07-08 17:36:23
Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name NOT LIKE '%e%' AND email LIKE '%@qq.com')
<== Columns: id, name, age, email, version, deleted, create_time, update_time
<== Row: 10, Jack, 18, [email protected], 1, 0, 2021-07-08 16:05:07.0, null
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@56f730b2]
{deleted=0, create_time=2021-07-08 16:05:07.0, name=Jack, id=10, version=1, age=18, [email protected]}
@Test
void test05() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.inSql("id", "select id from user where id < 11");
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}
运行结果日志:
==> Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (id IN (select id from user where id < 11))
==> Parameters:
Consume Time:4 ms 2021-07-08 17:46:35
Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (id IN (select id from user where id < 11))
<== Columns: id, name, age, email, version, deleted, create_time, update_time
<== Row: 2, test0222222, 20, [email protected], 3, 0, null, 2021-07-08 10:09:29.0
<== Row: 6, John222, 1, [email protected], 1, 0, 2021-07-07 21:35:04.0, 2021-07-08 10:24:26.0
<== Row: 10, Jack, 18, [email protected], 1, 0, 2021-07-08 16:05:07.0, null
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7ce85af2]
User(id=2, name=test0222222, age=20, [email protected], version=3, deleted=0, createTime=null, updateTime=Thu Jul 08 10:09:29 CST 2021)
User(id=6, name=John222, age=1, [email protected], version=1, deleted=0, createTime=Wed Jul 07 21:35:04 CST 2021, updateTime=Thu Jul 08 10:24:26 CST 2021)
User(id=10, name=Jack, age=18, [email protected], version=1, deleted=0, createTime=Thu Jul 08 16:05:07 CST 2021, updateTime=null)
@Test
void test06() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.orderByDesc("id");
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}
运行结果日志:
==> Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 ORDER BY id DESC
==> Parameters:
Consume Time:6 ms 2021-07-09 00:28:11
Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 ORDER BY id DESC
<== Columns: id, name, age, email, version, deleted, create_time, update_time
<== Row: 12, Maria, 22, null, 1, 0, 2021-07-08 16:09:16.0, null
<== Row: 11, Bobby, 14, [email protected], 1, 0, 2021-07-08 16:05:57.0, null
<== Row: 10, Jack, 18, [email protected], 1, 0, 2021-07-08 16:05:07.0, null
<== Row: 6, John222, 1, [email protected], 1, 0, 2021-07-07 21:35:04.0, 2021-07-08 10:24:26.0
<== Row: 2, test0222222, 20, [email protected], 3, 0, null, 2021-07-08 10:09:29.0
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2a39aa2b]
User(id=12, name=Maria, age=22, email=null, version=1, deleted=0, createTime=Thu Jul 08 16:09:16 CST 2021, updateTime=null)
User(id=11, name=Bobby, age=14, [email protected], version=1, deleted=0, createTime=Thu Jul 08 16:05:57 CST 2021, updateTime=null)
User(id=10, name=Jack, age=18, [email protected], version=1, deleted=0, createTime=Thu Jul 08 16:05:07 CST 2021, updateTime=null)
User(id=6, name=John222, age=1, [email protected], version=1, deleted=0, createTime=Wed Jul 07 21:35:04 CST 2021, updateTime=Thu Jul 08 10:24:26 CST 2021)
User(id=2, name=test0222222, age=20, [email protected], version=3, deleted=0, createTime=null, updateTime=Thu Jul 08 10:09:29 CST 2021)
上面只讲述了mapper层面的方法不用书写,但是按照现在流行软件架构,还有Service
层和Controller
层的代码需要书写。
Controller层的代码肯定是得自己根据业务需求来书写了,不用想。
而Service层,MyBatisPlus封装了一系列的方法,不用自己手写。下文将会实战几种常用的Service层方法。完整的Service CRUD接口请查看官方文档 https://mybatis.plus/guide/crud-interface.html。
下面为Service CRUD接口的说明:
说明:
- 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用
get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分Mapper
层避免混淆,- 泛型
T
为任意实体对象- 建议如果存在自定义通用 Service 方法的可能,请创建自己的
IBaseService
继承Mybatis-Plus
提供的基类- 对象
Wrapper
为 条件构造器
Wrapper在这里又出现了,先不管,文章的后面会详细讲述Wrapper的使用方法。
上文已经构造好了UserMapper,下面继续构造Service层和Controller层,通过网页来查看Service层的效果。
创建service包后创建UserService接口,然后该接口继承IService接口,结束。简单吧,就是这么简单,基础CRUD的方法都被封装到了IService中。
public interface UserService extends IService<User> {
}
需要说明的是,IService必须指明泛型,泛型为要操作的实体类。
UserServiceImpl
需要实现UserService
接口,这是常规操作。但是稍微想想都知道,完成这步后,还需要自己实现接口里的方法,这对于高效率(爱偷懒)的程序员是不友好的。
MyBatisPlus当然想到了这个问题,所以设计了IService
的实现类ServiceImpl
。UserServiceImpl
继承了ServiceImpl
后,便继承了大量的基础CRUD方法,就不需要自己写常规的Service层的CRUD代码了。
UserServiceImpl
代码如下:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
一样的,ServiceImpl
也需要输入泛型。第一个参数为对应的Mapper层接口,第二个参数为要操作的实体类。
代码书写后,查看UserServiceImpl
的结构,可以看到来自ServiceImpl
的CRUD方法都被顺利继承。
其实Service层方法直接在测试类进行测试即可,但是写Controller的原因在于,从浏览器直接一条龙到数据库比较有感觉,也顺便当做练练手。
创建controller包后,创建UserController类,详细代码如下,默认看这个资料的学过SpringMVC。
UserController
代码如下;
@Controller
public class UserController {
@Autowired
UserService userService;
@ResponseBody
@GetMapping("/allUser")
public List<User> allUser() {
return userService.list();
}
@PostMapping("/addUser")
public String addUser(User newUser) {
userService.save(newUser);
return "redirect:allUser";
}
}
额外说明:
@ResponseBody
标注返回的不是路径,而是直接返回json数据(说的直白一点,我比较懒,不想写网页)。测试期望:
- 通过访问路径/allUser,期望获取数据库里user的所有数据
- 通过表格进行post到/addUser路径后,成功存储一个新的用户后,重定向到/allUser路径,获取所有用户,看新用户有没有被成功添加。
用HTML写一个简易的表格,通过post方法测试用户添加方法。如果懒惰的同学可以直接用postman测试。
HTML代码如下。命名为index.html
,同时放到resources文件夹内,当做主页(Springboot基础知识)。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="/addUser" method="post">
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
邮箱:<input type="email" name="email"><br>
<input type="submit" value="提交">
form>
body>
html>
测试!
启动springboot项目后,访问http://localhost:8080/allUser,成功获得数据库里的所有用户数据。第一个期望成功实现。
访问http://localhost:8080/ 到表格添加页面,添加以下数据并提交
可以看到表格提交后重定向到了allUser页面。用户狗剩儿被成功添加,数据库里也可以看到狗剩儿被添加。第二个期望达成。
能够不用重复性地写简单的mapper层和service层代码已经是非常优秀了,然而更懒的程序员肯定是不会止步于此的。很明显的原因在于,实体类,mapper层和service层的简单代码甚至是类的创建本身就是重复性工作,按理来说有更简单的方法。
确实,MyBatisPlus有对应的代码生成器,只要有数据库,便可以一键生成相应的所有基础代码。
添加代码生成器依赖和模板引擎依赖。
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.3version>
dependency>
除了之前的user表外,我另外创建了一个数据库,名称为shop,具体字段如下。
建表语句:
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`version` int(10) DEFAULT '1' COMMENT '乐观锁字段',
`deleted` int(1) DEFAULT '0' COMMENT '逻辑删除',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
创建一个generator包,书写代码自动生成器代码,具体代码如下。有非常详细的注释,稍微修改后便可直接使用。
public class CodeGenerator {
public static void main(String[] args) {
// 创建一个代码自动生成器对象
AutoGenerator autoGenerator = new AutoGenerator();
// 1. 全局配置
// 注意:导入的包为 com.baomidou.mybatisplus.generator.config.GlobalConfig
GlobalConfig globalConfig = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
// 文件输出目录
globalConfig.setOutputDir(projectPath + "/src/main/java");
// 设置作者名
globalConfig.setAuthor("John");
// 是否打开输出目录(默认为true)
globalConfig.setOpen(false);
// 是否覆盖已有文件
globalConfig.setFileOverride(true);
// 去除生成的Service接口默认的"I"前缀
globalConfig.setServiceName("%sService");
// 指定生成的主键ID类型,这里一样的,设置为常规的主键自增
globalConfig.setIdType(IdType.AUTO);
// 全局配置注入代码生成器
autoGenerator.setGlobalConfig(globalConfig);
// 2. 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
// 设置连接的数据库
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=false&characterEncoding=utf8");
dataSourceConfig.setDriverName("com.mysql.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("123456");
autoGenerator.setDataSource(dataSourceConfig);
// 3. 包的配置
PackageConfig packageConfig = new PackageConfig();
// 设置模块名称
packageConfig.setModuleName("generated");
// 设置父包名
packageConfig.setParent("com.sjh");
autoGenerator.setPackageInfo(packageConfig);
// 4. 策略配置
StrategyConfig strategy = new StrategyConfig();
// 需要包含的表明(支持正则表达式)
strategy.setInclude("user", "shop");
// 设置数据库表和字段映射到实体的命名策略,这里设置为蛇形命名转化为驼峰式
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 不设置为lombok模型(默认为false,如果需要,改为true)
strategy.setEntityLombokModel(false);
// 设置逻辑删除属性
strategy.setLogicDeleteFieldName("deleted");
// 自动填充策略设置
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmtCreate);
tableFills.add(gmtModified);
strategy.setTableFillList(tableFills);
// 乐观锁设置
strategy.setVersionFieldName("version");
autoGenerator.setStrategy(strategy);
// 运行自动生成器
autoGenerator.execute();
}
}
可以看到,有四大基本配置:全局配置、数据源配置、包配置和策略配置。此外,还有一个模板引擎配置,在此处没有体现。引擎配置可以自己制定模板引擎、生成路径和某些代码是否需要生成,这些并没有必要进行修改。如果需要更改的话查看官方文档和源码即可。
必须要修改的地方有:
globalConfig.setAuthor("作者")
设置作者名称;strategy.setInclude("表名称")
用来指明要生成的代码的数据表来源。命名策略、自动填充字段设置、逻辑删除属性设置、乐观锁字段设置等,根据实际情况自行修改。运行代码后,所有的基础代码都生成了,并且有很好的注释和代码规范,以后自己写都懒得写了。
以上的流程走完后,MyBatisPlus的使用基本没有问题。可以说,MyBatisPlus是一个可以大幅提高开发效率的工具,让开发者脱离重复的简单的CRUD代码书写,虽然有一定的局限性,但是学会了准没错。