本测试项目涵盖了Mybatis-Plus框架的入门快速构建以及基本的CRUD相关的操作说明。望可以帮助到有相关需求的伙伴。
测试代码Github地址:https://github.com/yaokuku123/mybatis-demo
1 Mybatis-Plus快速搭建
本项目测试需要基于SpringBoot框架完成。
1.1 创建并初始化数据库
- 创建mybatis_plus数据库,并添加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)
);
- 向user表中添加初始的字段用于测试
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]');
1.2 初始化Maven工程,并引入依赖
注意:引入 MyBatis-Plus
无需再引入 MyBatis
以及 MyBatis-Spring
,以避免因版本差异导致的问题。
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
com.baomidou
mybatis-plus-boot-starter
3.0.5
mysql
mysql-connector-java
org.projectlombok
lombok
junit
junit
1.3 主配置类中的配置
#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
1.4 编写代码
- 主启动类
注意:在 Spring Boot 启动类中添加 @MapperScan
注解,扫描 Mapper 文件夹。
package com.yqj;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.yqj.mapper")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class,args);
}
}
- 实体类
package com.yqj.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- mapper
注意:让UserMapper接口继承mybatisplus中的BaseMapper
package com.yqj.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yqj.entity.User;
@Repository
public interface UserMapper extends BaseMapper {
}
1.5 测试
package com.yqj;
import com.yqj.entity.User;
import com.yqj.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectList(){
List users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
}
输出:说明成功实现基于Mybatis-Plus的快速入门操作
User(id=1, name=Jone, age=18, [email protected])
User(id=2, name=Jack, age=20, [email protected])
User(id=3, name=Tom, age=28, [email protected])
User(id=4, name=Sandy, age=21, [email protected])
User(id=5, name=Billie, age=24, [email protected])
2. Mybatis-Plus的CRUD相关操作
基于之前搭建的快速搭建的程序测试CRUD的相关操作
2.1 insert
- 插入操作
注意:数据库插入id值默认为:ID_WORKER 全局唯一id
//插入用户数据
@Test
public void testInsert(){
User user = new User();
user.setName("yorick");
user.setAge(18);
user.setEmail("[email protected]");
int result = userMapper.insert(user);
System.out.println(result);
}
- 主键生成策略
1.IdType.AUTO 主键自增策略
2.IdType.ID_WORKER 若主键为数值型,Mybatis-Plus默认使用的策略
3.IdType.ID_WORKER_STR 若主键为字符串型,Mybatis-Plus默认使用的策略
4.IdType.INPUT 主键id需要手动输入
5.IdType.NONE 默认跟随全局策略
6.IdType.UUID 生成UUID
在实体类的主键上增加主键生成策略的字段注解即可
@TableId(type = IdType.AUTO)
private Long id;
2.2 update
- 根据id更新
//更新用户数据
@Test
public void testUpdate(){
User user = userMapper.selectById(1L);
user.setName("alice");
int result = userMapper.updateById(user);
System.out.println(result);
}
- 自动填充
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作。
测试:
- 在数据库中添加两个datetime类型的新字段 create_time、update_time
ALTER TABLE `user` ADD COLUMN `create_time` datetime
ALTER TABLE `user` ADD COLUMN `update_time` datetime
- 在对应实体类的属性上添加注解
//在插入时自动填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//在插入和更新时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
- 实现元对象处理器接口
package com.yqj.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//设置执行插入时,自动填充的内容
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//设置执行更新时,自动填充的内容
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
- 测试
//插入用户数据
@Test
public void testInsert(){
User user = new User();
user.setName("yorick2");
user.setAge(19);
user.setEmail("[email protected]");
int result = userMapper.insert(user);
System.out.println(result);
}
- 乐观锁
当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
测试:
- 数据库添加version字段
ALTER TABLE `user` ADD COLUMN `version` INT
- 实体类的对应属性上添加注解
@Version
@TableField(fill = FieldFill.INSERT) //设置插入时自动填充
private Integer version;
- 元对象处理器接口添加version的insert默认值
@Override
public void insertFill(MetaObject metaObject) {
......
this.setFieldValByName("version", 1, metaObject);
}
- 注册乐观锁的配置插件
另外,将原来标注在主启动类上的 @MapperScan 注解转移到配置类上面
package com.yqj.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.yqj.mapper")
public class MybatisPlusConfig {
//乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
- 测试
将id为1的记录中version字段改为1,然后执行测试发现结果为2,说明乐观锁配置成功。
//更新用户数据
@Test
public void testUpdate(){
User user = userMapper.selectById(1L);
user.setName("alice2");
int result = userMapper.updateById(user);
System.out.println(result);
}
2.3 select
- 根据id查询记录
//根据id查询
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
- 多个id批量查询
//多个id批量查询
@Test
public void testSelectBatchIds(){
List users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
for (User user : users) {
System.out.println(user);
}
}
- map封装的条件查询
注意:map中的key对应的是数据库中的列名。例如数据库user_id,实体类是userId,这时map的key需要填写user_id
//map封装的条件查询
@Test
public void testSelctByMap(){
Map map = new HashMap<>();
map.put("name","yorick");
map.put("age",18);
List users = userMapper.selectByMap(map);
for (User user : users) {
System.out.println(user);
}
}
- 分页查询
最终通过page对象获取相关数据
测试:
- 在配置类中添加分页插件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
- 测试selectPage分页
//分页查询
@Test
public void testSelectPage(){
Page page = new Page<>(1, 3);
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println); //当前页的数据
System.out.println(page.getCurrent()); //当前页
System.out.println(page.getPages()); //总页数
System.out.println(page.getSize()); //每页显示的数据条目
System.out.println(page.getTotal()); //总共的数据条目
System.out.println(page.hasNext()); //是否有下一页
System.out.println(page.hasPrevious()); //是否有上一页
}
2.4 delete
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
- 根据id删除
//根据id删除
@Test
public void testDeleteById(){
int result = userMapper.deleteById(5L);
System.out.println(result);
}
根据id批量删除 deleteBatchIds()
根据map删除 deleteByMap()
逻辑删除
测试:
- 数据库中添加 deleted字段,用于表示逻辑删除
ALTER TABLE `user` ADD COLUMN `deleted` boolean
- 实体类的对应属性上面添加逻辑删除的注解
@TableLogic
@TableField(fill = FieldFill.INSERT) //插入时自动填充
private Integer deleted;
- 元对象处理器接口添加deleted的insert默认值
@Override
public void insertFill(MetaObject metaObject) {
......
this.setFieldValByName("deleted", 0, metaObject);
}
- 在配置类中注册逻辑删除的插件
//逻辑删除
@Bean
public ISqlInjector iSqlInjector(){
return new LogicSqlInjector();
}
- 测试逻辑删除
将id为4的记录中deleted字段改为0,然后执行测试发现其变为1,说明逻辑删除成功。
- 测试后发现,数据并没有被删除,deleted字段的值由0变成了1
- 测试后分析打印的sql语句,是一条update
- MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断
- 注意:被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
//根据id删除
@Test
public void testDeleteById(){
int result = userMapper.deleteById(4L);
System.out.println(result);
}
2.5 性能分析
性能分析拦截器,用于输出每条 SQL 语句及其执行时间
SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题
测试:
- 在配置类中添加性能分析的插件
//性能分析
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
//参数:maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题
//参数:format: SQL是否格式化,默认false
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(500);//ms,超过此处设置的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
- SpringBoot中设置为dev开发环境
#环境设置:dev、test、prod
spring.profiles.active=dev
- 测试
//插入用户数据
@Test
public void testInsert(){
User user = new User();
user.setName("yorick");
user.setAge(20);
user.setEmail("[email protected]");
int result = userMapper.insert(user);
System.out.println(result);
}
结果:显示出插入的性能为4ms
Time:4 ms - ID:com.yqj.mapper.UserMapper.insert
Execute SQL:
INSERT
INTO
user
( id, name, age, email, create_time, update_time, version, deleted )
VALUES
( 1298135534432567298, 'yorick', 20, '[email protected]', '2020-08-25 13:50:04', '2020-08-25 13:50:04', 1, 0 )
2.6 复杂查询
//复杂查询
@Test
public void testComplexSelect(){
QueryWrapper wrapper = new QueryWrapper<>();
//ge,gt,le,lt,isNull,isNotNull
//查询年龄不小于20岁的用户
//wrapper.ge("age",20);
//eq,ne
//查询姓名为yorick的用户
//wrapper.eq("name","yorick");
//between,notBetween
//查询年龄在20~30之间的用户,包含20和30边界
//wrapper.between("age",20,30);
//like
//查询姓名包含yorick的用户
//wrapper.like("name","yorick");
//orderByDesc,orderByAsc
//按id降序排序
//wrapper.orderByDesc("id");
//last 直接拼接sql到最后,有sql注入风险
//wrapper.last("limit 1");
//指定要查找的列
wrapper.select("id","name");
List users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}