MyBatis-Plus
是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
1. 特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
2. 入门
2.1. 依赖
<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>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
2.2. 数据库配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///blog?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimeZone=GMT%2B8
username: root
password: root
2.3. 增加注解
在Spring Boot启动类上增加Mapper扫描注解
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
或者在Mapper类上增加@Mapper
注解
2.4. 编写代码
编写对应的Entity、Mapper即可开始测试。
3. 使用代码生成器
代码生成器可以自动生成Entity, Mapper, Service, Controller模块的代码。
3.1. 过程
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
代码
package person.rootwhois.blog;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @Author: 陈广生
* @Date: 2022/01/01/4:34 PM
* @Description:
*/
public class MbgGeneratorMain {
public static void main(String[] args) {
List<String> tables = new ArrayList<>();
tables.add("admin");
tables.add("article");
tables.add("article_sorts");
tables.add("article_tags");
tables.add("comment");
tables.add("history");
tables.add("sort");
tables.add("tag");
tables.add("web");
FastAutoGenerator.create("jdbc:mysql:///blog?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true","root","root")
.globalConfig(builder -> {
builder.author("陈广生") //作者
.outputDir(System.getProperty("user.dir")+"/src/main/java") //输出路径(写到java目录)
.enableSwagger() //开启swagger
.commentDate("yyyy-MM-dd")
.fileOverride(); //开启覆盖之前生成的文件
})
.packageConfig(builder -> {
builder.parent("person.rootwhois.blog")
.moduleName("")
.entity("entity")
.service("service")
.serviceImpl("service.impl")
.controller("controller")
.mapper("mapper")
.xml("mapper")
.pathInfo(Collections.singletonMap(OutputFile.mapperXml,System.getProperty("user.dir")+"/src/main/resources/mapper"));
})
.strategyConfig(builder -> {
builder.addInclude(tables)
.addTablePrefix("")
.serviceBuilder()
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
.entityBuilder()
.addTableFills(new Column("create_time", FieldFill.INSERT))
.addTableFills(new Column("updateTime", FieldFill.INSERT))
.versionColumnName("version")
.enableLombok()
.logicDeleteColumnName("deleted")
.enableTableFieldAnnotation()
.controllerBuilder()
.formatFileName("%sController")
.enableRestStyle()
.mapperBuilder()
.superClass(BaseMapper.class)
.formatMapperFileName("%sMapper")
.enableMapperAnnotation()
.formatXmlFileName("%sMapper")
.enableBaseColumnList()
.enableMapperAnnotation()
.enableBaseResultMap();
})
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}
生成到其他目录,选择性拷贝,或者不开启覆盖选项,这样不会覆盖之前已经修改过的类。
4. 注解
https://baomidou.com/pages/223848/#tablename
注解 | 描述 |
---|---|
@TableName | 作用在实体类上,表名注解,标识实体类对应的表。 |
@TableId | 作用在实体类的属性中,主键注解。 |
@IdType | 作用在实体类的属性中,设置主键的类型(如自增,无,雪花算法等)。 |
@TableField | 作用在实体类的属性中,字段注解(非主键)。 |
@Version | 乐观锁注解,标记在字段上。 |
@EnumValue | 通枚举类注解(注解在枚举字段上)。 |
@TableLogic | 表字段逻辑处理注解(逻辑删除) |
@KeySequence | 序列主键策略 |
@OrderBy | 内置 SQL 默认指定排序,优先级低于 wrapper 条件查询 |
5. CURD
用法:Mapper接口继承MyBatis-Plus的BaseMapper类,并传入实体的范型。
package person.rootwhois.blog.mapper;
import person.rootwhois.blog.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 陈广生
* @since 2022-01-05
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
// 根据id查询对应的记录
User user = userMapper.selectById(1L);
// 根据id查询对应的记录
User user = userMapper.selectById(1L);
// 查询全部用户
List<User> users = userMapper.selectList(null);
// 根据id插入传入的参数,并返回受影响的行数
int result = userMapper.insert(user);
// 根据id更新对应记录,并返回受影响的行数
int i = userMapper.updateById(user);
// 批量查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
// 使用map指定条件查询
List<User> users = userMapper.selectByMap(map);
// 删除操作,把查询操作的select换成delete即可
6. 条件构造器Wrapper
6.1. QueryWrapper
6.2. UpdateWrapper
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.between("age",20,30)
.notLike("name","e")
.likeRight("email","t")
.eq("name","n")
.orderByAsc("id");
wrapper.inSql("id","select id from user where id<3"); // where id in (sql)
List<User> users = userMapper.selectList(wrapper);
7. 主键生成策略
这里对应@TableId的一个属性IdType
/**
* 数据库ID自增
* <p>该类型请确保数据库设置了 ID自增 否则无效</p>
*/
AUTO(0),
/**
* 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
*/
NONE(1),
/**
* 用户输入ID
* <p>该类型可以通过自己注册自动填充插件进行填充</p>
*/
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);
8. 自动填充
package person.rootwhois.blog.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Author: 陈广生
* @Date: 2022/01/05/12:05 PM
* @Description:
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("create_time", new Date(), metaObject);
this.setFieldValByName("update_time", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("update_time", new Date(), metaObject);
}
}
@ApiModelProperty("创建时间")
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
9. 乐观锁配置
@ApiModelProperty("版本号")
@TableField("version")
@Version
private Integer version;
package person.rootwhois.blog.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @Author: 陈广生
* @Date: 2022/01/05/2:06 PM
* @Description:
*/
@Configuration
@EnableTransactionManagement
@MapperScan("person.rootwhois.blog.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
10. 分页配置
package person.rootwhois.blog.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @Author: 陈广生
* @Date: 2022/01/05/2:06 PM
* @Description:
*/
@Configuration
@EnableTransactionManagement
@MapperScan("person.rootwhois.blog.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL))
return interceptor;
}
}
Page<Article> articlePage = new Page<>(1,5);
articleMapper.selectPage(articlePage, null);
articlePage.getRecords().forEach(System.out::println);
System.out.println(articlePage.getTotal());
11. 逻辑删除
@ApiModelProperty("是否已经删除")
@TableField("deleted")
@TableLogic
private Integer deleted;
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)
12. TypeHandler
使用TypeHandler可以实现实体和JSON相互转化。
@Data
@Accessors(chain = true)
// 使用autoResultMap开启结果自动映射
@TableName(value = "info",autoResultMap = true)
public class Info implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 这个字段数据库存的是json字符串
*/
// 这里有两个可选,JacksonTypeHandler和FastjsonTypeHandler。
@TableField(typeHandler = JacksonTypeHandler.class)
private User value;
}
JacksonTypeHandler和FastjsonTypeHandler的区别:
@TableField(typeHandler = JacksonTypeHandler.class)
private User value;
@TableField(typeHandler = FastjsonTypeHandler.class)
private User value;
JacksonTypeHandler可以支持MVC JSON解析和MySQL JSON解析。可以兼容MP的功能和满足支持MySQL JSON解析。
FastjsonTypeHandler不支持MySQL JSON的解析。
坑:使用的时候,不能自动识别泛型。但JSON的key必须使用双括号括起来。在实际使用中,Map<Integer, List<Integer>>
的类型会被解析成Map<String, List<Integer>>
,从而导致出现类型转换异常。
12.1. 自定义TypeHandler
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@MappedJdbcTypes(JdbcType.VARBINARY)
@MappedTypes({List.class})
public abstract class ListTypeHandler<T> extends BaseTypeHandler<List<T>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
String content = CollUtil.isEmpty(parameter) ? null : JSON.toJSONString(parameter);
ps.setString(i, content);
}
@Override
public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return this.getListByJsonArrayString(rs.getString(columnName));
}
@Override
public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.getListByJsonArrayString(rs.getString(columnIndex));
}
@Override
public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return this.getListByJsonArrayString(cs.getString(columnIndex));
}
private List<T> getListByJsonArrayString(String content) {
return StrUtil.isBlank(content) ? new ArrayList<>() : JSON.parseObject(content, this.specificType());
}
/**
* 具体类型,由子类提供
*
* @return 具体类型
*/
protected abstract TypeReference<List<T>> specificType();
}
使用:
import com.alibaba.fastjson.TypeReference;
import java.util.List;
public class UserListTypeHandler extends ListTypeHandler<User> {
@Override
protected TypeReference<List<User>> specificType() {
return new TypeReference<List<User>>(){
};
}
}