目录
一、概念
1、Mybatis-Plus介绍
官⽹: https://mybatis.plus/ 或 https://mp.baomidou.com/
MyBatis-Plus (简称 MP )是⼀个 MyBatis 的增强⼯具,在 MyBatis 的基础上只做增强不做改变,-为简化开发、提⾼效率⽽⽣。
2、特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗⼩:启动即会⾃动注⼊基本 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 操作智能分析阻断,也可⾃定义拦截规则,预防误操作
二、基本使用
1、导入maven
<dependencies>
<!-- web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库链接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
注意:尽量不要同时导入mybatis 和 mybatis_plus,避免版本差异
2、yml或者properties配置
配置(连接数据库)
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/employees?characterEncoding=utf8&useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
username: root
password: 123456
配置日志(可选)
所有的SQL都是不可见的,所以在后台是希望看到SQL是怎么执行的,就必须要配置日志。
在.yml配置文件中配置日志:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3、使用
在 spring boot 启动类中添加 @MapperScan 注解,扫描Mapper文件夹:
@SpringBootApplication
@MapperScan("com.mp.mapper") //扫描mapper
public class MyMpApplication{
public static void main(String[] args) {
SpringApplication.run(MyMpApplication.class, args);
}
}
在对应的 mapper 上面添加 @Mapper 注解,并继承 BaseMapper<> 类:
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
//所有的CRUD都已经完成
//不需要像以前一样配置一大堆文件:pojo-dao(连接mybatis,配置mapper.xml文件)==>service-controller
}
开始使用:
编写单元测试:
@Test
public void selectByIdTest() {
List<Employee> employees = employeeMapper.selectList(null);
employees.forEach(System.out::println);
}
三、CRUD
实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("employee")
public class Employee {
@TableId(type = IdType.AUTO)
private Long empId;
private String name;
private String empGender;
private Integer age;
private String email;
}
如果实体类名称和表名称不⼀致,可以在实体类上添加注解 @TableName(" 指定数据库表名 ")
如果字段名称和属性名称不一致,可以在属性上添加注解@TableField(value = " ")指定字段名
自定义ID生成器:
@TableId(type = IdType)
- AUTO:数据库ID自增
- NONE:该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
- INPUT:该类型可以通过自己注册自动填充插件进行填充
- ASSIGN_ID:分配ID (主键类型为 number 或 string ) 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
- ASSIGN_UUID:分配UUID (主键类型为 string) 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace(“-”,“”))
1、BaseMapper
BaseMapper提供了许多公共的方法,需继承BaseMapper
public interface EmployeeMapper extends BaseMapper<Employee>
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
int delete(@Param("ew") Wrapper<T> queryWrapper);
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
T selectOne(@Param("ew") Wrapper<T> queryWrapper);
Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
<E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
}
1.1、插入
@Test
public void insertTest() {
Employee employee = new Employee();
employee.setName("李振成");
employee.setAge(23);
employee.setEmail("asdsds");
employee.setEmpGender("女");
employeeMapper.insert(employee);
System.out.println(employee.getEmpId());
}
1.2、删除
1.2.1、通过id删除记录
@Test
public void testDelete(){
int result = employeeMapper.deleteById(3L);
System.out.println("result = " + result);
}
1.2.2、通过id批量删除记录
@Test
public void testDelete(){
List<Long> list = Arrays.asList(1L, 2L, 3L);
int result = employeeMapper.deleteBatchIds(list);
System.out.println("result = " + result);
}
1.2.3、 通过map条件删除记录
@Test
public void testDelete(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",23);
int result = employeeMapper.deleteByMap(map);
System.out.println("result = " + result);
}
1.3、更新
1.3.1、根据 id 更新
@Test
public void updateByIdTest() {
Employee employee = new Employee();
employee.setEmpId(1l);
employee.setName("kawasaki");
employee.setAge(18);
employee.setEmail("chuanqi");
employee.setEmpGender("女");
employeeMapper.updateById(employee);
}
1.3.2 、根据条件更新
@Test
public void updateTest() {
Employee employee = new Employee();
employee.setName("王蔡蔡");
employee.setAge(18);
employee.setEmail("chuanqi");
employee.setEmpGender("男");
employeeMapper.update(employee,new UpdateWrapper<Employee>().eq("name","王彩彩"));
}
1.4、 查询
1.4.1、根据Id查询用户信息
@Test
public void selectByIdTest() {
Employee employee = employeeMapper.selectById(1l);
System.out.println(employee);
}
1.4.2、根据多个id查询多个用户信息
@Test
public void testSelect(){
List<Long> list = Arrays.asList(1L, 2L, 3L);
List<Employee> employees = employeeMapper.selectBatchIds(list);
employees.forEach(System.out::println);
}
1.4.3、通过map条件查询用户信息
@Test
public void selectMapsTest() {
Map<String,Object> map = new HashMap<>();
map.put("name", "李振成");
map.put("emp_gender", "男");
List<Employee> employees = employeeMapper.selectByMap(map);
System.out.println(employees);
}
1.4.4、查询所有数据
@Test
public void testSelect(){
List<Employee> employees = employeeMapper.selectList(null);
employees.forEach(System.out::println);
}
1.5、自定义功能
resource目录下新建mapper/EmployeeMapper.xml文件
- UserMapper接口
/**
* 根据 id 查询用户信息为 map 集合
* @param id
* @return
*/
Map<String, Object> selectMapById(Long id);
- UserMapper映射文件
<?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.mp.mapper.EmployeeMapper">
<!-- Map<String, Object> selectMapById(Long id); -->
<select id="selectMapById" resultType="map">
select emp_id,name,emp_gender,age,email from employee where emp_id = #{id}
</select>
</mapper>
- 测试
@Test
public void testSelect(){
Map<String, Object> map = employeeMapper.selectMapById(1L);
System.out.println(map);
}
2、IService
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑
/**
* EmployeeService继承IService模板提供的基础功能
*/
public interface EmployeeService extends IService<Employee> {
}
/**
* EmployeeImpl实现了IService,提供了IService中基础功能的实现
* 若ServiceImpl无法满足业务需求,则可以使用自定的EmployeeService定义方法,并在实现类中实现
*/
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}
2.1、测试查询记录数
@Autowired
private EmployeeService employeeService;
@Test
public void test(){
long count = employeeService.count();
System.out.println(count);
}
2.2、测试批量插入
@Test
public void testInsertMore(){
ArrayList<Employee> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Employee employee= new Employee();
employee.setName("jingchao"+i);
employee.setAge(18+i);
employee.setEmail("jingchao"+i+"@gmail.com");
list.add(employee);
}
boolean b = employeeService.saveBatch(list);
System.out.println(b);
}
四、条件构造器
Wrapper:条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
1、基本⽐较操作
- eq 等于 =
- ne 不等于 <>
- gt ⼤于 >
- ge ⼤于等于 >=
- lt ⼩于 <
- le ⼩于等于 <=
- between BETWEEN 值 1 AND 值 2
- notBetween NOT BETWEEN 值 1 AND 值 2
- in 字段 IN (value.get(0), value.get(1), ...)
- notIn 字段 NOT IN (v0, v1, ...)
测试:
@Test
public void testQuery(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//SELECT emp_id,name,emp_gender,age,email FROM employee WHERE (email = ? AND age >= ? AND name IN (?,?,?))
wrapper.eq("email", "asdasd")
.ge("age", 15)
.in("name", "jack", "zm", "mr");
List<Employee> employees = this.employeeMapper.selectList(wrapper);
for (Employee employee: employees) {
System.out.println(employee);
}
}
2、模糊查询
- like LIKE '%值%'
- notLike NOT LIKE '%值%'
- likeLeft LIKE '%值'
- likeRight LIKE '值%'
测试:
@Test
public void testLike(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//SELECT emp_id,name,emp_gender,age,email FROM employee WHERE (name LIKE ?)
//Parameters: %sen%(String)
wrapper.like("name", "sen");
List<Employee> employees = this.employeeMapper.selectList(wrapper);
for (Employee employee : employees) {
System.out.println(employee);
}
}
3、排序
- orderBy ORDER BY 字段, ...
- 例: orderBy(true, true, "emp_id", "name") ---> order by emp_id ASC,name ASC
- orderByAsc ORDER BY 字段, ... ASC
- 例: orderByAsc("emp_id", "name") ---> order by emp_id ASC,name ASC
- orderByDesc ORDER BY 字段, ... DESC
- 例: orderByDesc("emp_id", "name") ---> order by emp_id DESC,name DESC
测试:
@Test
public void testOrder(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//SELECT emp_id,name,emp_gender,age,email FROM employee ORDER BY age DESC
wrapper.orderByDesc("age");
List<Employee> employees = this.employeeMapper.selectList(wrapper);
for (Employee employee : employees) {
System.out.println(employee);
}
}
4、逻辑查询
- or 拼接 OR
- and AND 嵌套
测试:
@Test
public void testOr(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//SELECT emp_id,name,emp_gender,age,email FROM employee WHERE (name = ? OR age = ?)
wrapper.eq("name","jack").or().eq("age", 14);
List<Employee> employees = this.employeeMapper.selectList(wrapper);
for (Employee employee : employees) {
System.out.println(employee);
}
}
5、select
在 MP 查询中,默认查询所有的字段,如果有需要也可以通过 select ⽅法进⾏指定字段。
测试:
@Test
public void testQuerySelect(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//SELECT emp_id,name,age FROM employee WHERE (name = ? OR age = ?)
//jack(String), 14(Integer)
wrapper.eq("name", "jack")
.or()
.eq("age", 14)
.select("emp_id", "name", "age");
List<Employee> employees = this.employeeMapper.selectList(wrapper);
for (Employee employee : employees) {
System.out.println(employee);
}
}
五、ActiveRecord
什么是 ActiveRecord ?
ActiveRecord 也属于 ORM (对象关系映射)层,由 Rails 最早提出,遵循标准的 ORM 模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很⼤程度的快速实现模型的操作,⽽且简洁易懂。
ActiveRecord 的主要思想是:
- 每⼀个数据库表对应创建⼀个类,类的每⼀个对象实例对应于数据库中表的⼀⾏记录;通常 表的每个字段在类中都有相应的Field;
- ActiveRecord同时负责把⾃⼰持久化,在ActiveRecord中封装了对数据库的访问,即 CURD;
- ActiveRecord是⼀种领域模型(Domain Model),封装了部分业务逻辑;
开启AR
- 实体对象继承 Model
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("employee")
public class Employee extends Model<Employee> {
@TableId(type = IdType.AUTO)
private Long empId;
private String name;
private String empGender;
private Integer age;
private String email;
}
1、查询
1.1、根据主键id查询
// AR 根据id查询
@Test
public void testARById(){
Employee employee = new Employee();
employee.setEmpId(1l);
Employee employee1 = employee.selectById();
System.out.println(employee1);
}
1.2、查询所有
//查询所有
@Test
public void testARSelectAll(){
Employee employee = new Employee();
List<Employee> employees = employee.selectAll();
employees.forEach(System.out::println);
}
1.3、查询计数
//查询计数
@Test
public void testARSelectCount(){
Employee employee = new Employee();
Integer integer = employee.selectCount(null);
System.out.println("总人数:"+integer);
}
2、新增
//新增数据
@Test
public void testARInsert(){
Employee employee = new Employee();
employee.setName("张三");
employee.setEmail("[email protected]");
employee.setAge(23);
employee.setEmpGender("女");
boolean insert = employee.insert();
System.out.println(insert);
}
3、更新操作
//更新数据
@Test
public void testARUpdataById(){
Employee employee = new Employee();
employee.setEmpId(3l);
employee.setName("李玉田");
employee.setEmail("[email protected]");
employee.setAge(23);
employee.setEmpGender("女");
boolean insert = employee.updateById();
System.out.println(insert);
}
4、删除操作
//根据主键删除
@Test
public void testARDeleteById(){
Employee employee = new Employee();
employee.setEmpId(3l);
boolean b = employee.deleteById();
System.out.println(b);
}
六、自动填充
1、什么是自动填充
在常用业务中有些属性需要配置一些默认值,MyBatis-Plus 提供了实现此功能的插件,也就是自动填充功能。
比如创建时间、修改时间这些操作一般都是自动化完成的,是不用去手动更新的。
2、自动填充方式
2.1、方式一:数据库级别(不建议)
2.1.1、添加时间字段
- 在使用的时候该字段留空即可。
2.1.2、在实体类中添加创建时间(create_time)以及修改时间(update_time)
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("employee")
public class Employee extends Model<Employee> {
@TableId(type = IdType.AUTO)
private Long empId;
private String name;
private String empGender;
private Integer age;
private String email;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
2.2、方式二:代码级别(建议)
- 修改数据库字段
数据库级别中的默认设置为null或者不写,还有去掉根据当前时间更新这个框
- 在实体类的字段属性增加注解
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("employee")
public class Employee extends Model<Employee> {
@TableId(type = IdType.AUTO)
private Long empId;
private String name;
private String empGender;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
- 编写处理器来处理注解
package com.kuang.handle;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Date;
@Slf4j
@Component
public class MyMetaObjectHander implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill.....");
//setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)
//第一个参数 字段名
//第二个参数 值
//第三个参数 metaObject
// this.setFieldValByName("createTime",new Date(),metaObject);
// this.setFieldValByName("updateTime",new Date(),metaObject);
// 上面的方法是旧版本,新版本的是使用下面的strictInsertFill方法
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill.....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
测试:
@Test
public void testARInsert(){
Employee employee = new Employee();
employee.setName("aaaaaaaaa");
employee.setEmail("[email protected]");
employee.setAge(23);
employee.setEmpGender("女");
boolean insert = employee.insert();
System.out.println(insert);
}
七、插件
1、分页插件
1.1、添加配置类
@Configuration
@MapperScan("com.jingchao.mybatisplus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
1.2、测试:
//分页查询
@Test
public void testPage(){
Page<Employee> page = new Page<>(2,2);
employeeMapper.selectPage(page, null);
//获取分页数据
List<Employee> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
八、逻辑删除
- 物理删除:从数据库中直接删除
- 逻辑删除:在数据库中没有被删除,而是通过一个变量来让它失效
- deleted=0 ——》deleted=1
- 管理员可以查看被删除的记录,防止数据丢失,相当于回收站
- 在数据表中增加一个 deleted 字段
- 同步实体类,在实体类上加上 @TableLogic 注解
测试:
- 测试删除:删除功能被转变为更新功能
-- 实际执行的SQL
update employee set is_deleted=1 where emp_id = 1 and is_deleted=0
- 测试查询:被逻辑删除的数据默认不会被查询
-- 实际执行的SQL
select emp_id,name,is_deleted from user where is_deleted=0