# MybatisPlus
# 什么是 mybatisplus?
MybatisPlus 是一款优秀的国产持久层框架,基于 Mybatis 实现了基础 SQL 和 CRUD 操作的封装,帮助开发者简化数据库操作。MybatisPlus 基于 Mybatis 实现了功能的扩展和增强,同时兼容现有的 Mybatis 操作。
# 基础准备
案例基于 SpringBoot 来实现
# pom.xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
# 项目配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatisplus?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF8
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 数据表
create table user(
id int primary key auto_increment,
name varchar(11),
age int
);
insert into user values (1, '张三', 20), (2, '李四', 22);
# 实体类
@Data
public class User {
private Integer id;
private String name;
private Integer age;
}
# Mapper 接口
@Component
public interface UserMapper {
User findById(int id);
}
# @MapperScan
Mabatisplus 需要根据 MapperScan 知道到哪里找到 Mapper 接口从而生成动态代理对象注入 IOC 容器
@SpringBootApplication
@MapperScan("com.peter.mybatisplus.mapper")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
# 兼容 Mybatis 操作
定义 resources/mapper/userMapper.xml
<?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.peter.mybatisplus.mapper.UserMapper"> <select id="findById" resultType="com.peter.mybatisplus.model.User"> select * from user where id = #{id} </select> </mapper>测试 userMapper 接口
@SpringBootTest public class UserMapperTest { @Autowired UserMapper userMapper; @Test public void findByIdTest() { User user = userMapper.findById(1); System.out.println(user); } }
# 继承 BaseMapper 类
如果不想自己编写 SQL 语句,可以继承 BaseMapper 中预定义的方法实现 CRUD 操作。
MP 将根据我们所使用的 实体类注解 和 CRUD 方法 动态生成 SQL 语句然后通过 Mybatis 执行。
@Component
public interface UserMapper extends BaseMapper<User> {}
# 实体类注解
实体类注解(字段注解)用于标记实体类成员变量,从而影响实体对象和数据库之间的映射以及 MP 对数据库的操作。
# @TableName
value: 映射数据库表名称
@TableName("user")
public class Account {
@TableId()
private Integer id;
private String name;
private Integer age;
}
# @TableId
value: 映射主键字段
type: 定义主键类型
# type
1. AUTO:ID 自增
2. NONE:默认,同 INPUT
3. INPUT:用户自定义,如果未定义则同 ASSIGN_ID
4. ASSIGN_ID:使用雪花算法生成 ID(注意使用 Long 类型,数据库使用 bigint)
5. ASSIGN_UUID:使用 UUID 进行赋值,主键类型为 String
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
}
# @TableField
value: 映射非主键字段
exist: 是否为数据库表字段
select: 是否查询该字段
fill: 字段自动填充策略
# 如何自动填充 create_time 和 update_time?
实体类添加字段和注解
@Data public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; }创建填充处理器
MybatisPlus 检测到@TableField(fill) 后会注入 MetaObjectHandler 接口对象
@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 saveTest() { User user = new User(); user.setName("王五"); user.setAge(18); System.out.println("userMapper = " + userMapper.insert(user)); }
# @Version
添加乐观锁机制
乐观锁的本质就是这么一条 SQL 语句:update table set field = value, version = version+1 where id = ? and version = ?
乐观锁是为了保证业务多线程运行情况下数据准确性,是业务层面的逻辑,和数据库机制无关。
数据库添加 version 字段并赋值为 1
实体类添加 version 成员变量并添加@Version
@Data public class User { @TableId(type = IdType.AUTO) private Integer id; private String name; private Integer age; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; @Version private Integer version; }将 OptimisticLockerInterceptor 对象添加到 IOC 容器
@Configuration public class MyBatisPlusConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }测试
@Test void updateTest() { User user = userMapper.selectById(3); user.setName("赵四"); User user1 = userMapper.selectById(3); user1.setName("赵武"); userMapper.updateById(user1); // 成功 userMapper.updateById(user); // 失败 }对于 MP 来说会生成这么一条语句,所以我们传入的 user 对象里面如果没有 version 变量(null)则乐观锁不会生效
不过这里存个疑问:MP 居然知道我们更新了什么字段然后只 SET 那个字段,这是怎么做到的?
==> Preparing: UPDATE user SET name=?, update_time=?, version=? WHERE id=? AND version=? ==> Parameters: 赵武(String), 2020-05-26 09:55:07.715(Timestamp), 2(Integer), 3(Integer), 1(Integer)
# @EnumValue
当需要将数据库字段映射为枚举对象时,使用该注解指定要映射的枚举类型成员变量
数据库添加字段 status
定义枚举类型并在需要映射的成员变量上添加@EnumValue
如果映射的成员变量出现了多个重复值,那么取第一个定义的枚举对象(例如:WORK(1, "xxx"), WORK2(1, "xxx1"))
public enum StatusEnum { WORK(1, "工作"), REST(0, "休息"); @EnumValue private Integer code; private String msg; StatusEnum(Integer code, String msg) { this.code = code; this.msg = msg; } }添加实体类成员变量
@Data public class User { @TableId(type = IdType.AUTO) private Integer id; private String name; private Integer age; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; @Version private Integer version; private StatusEnum status; // 添加StatusEnum类型的成员变量 }添加枚举包配置
MP 在注入枚举对象的时候需要定位到具体是哪个类
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl type-enums-package: com.peter.mybatisplus.enums # 添加枚举包配置测试
@Test public void findByIdTest() { User user = userMapper.selectById(1); System.out.println(user); } // User(id=1, name=张三, age=20, createTime=null, updateTime=null, version=null, status=WORK) @Test public void saveTest() { User user = new User(); user.setName("王五"); user.setAge(18); user.setStatus(StatusEnum.REST); // 使用枚举对象 System.out.println("userMapper = " + userMapper.insert(user)); }
我们也可以不使用@EnumValue 而让枚举类继承 IEnum<T>接口获得相同的效果
重写 getValue()方法指定映射的变量
public enum StatusEnum2 implements IEnum {
WORK(1, "工作"),
REST(0, "休息");
private Integer code;
private String msg;
StatusEnum2(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public Serializable getValue() {
return this.code;
}
}
# @TableLogic
映射逻辑删除字段
数据库添加 deleted 字段
实体类添加成员变量
@Data public class User { @TableId(type = IdType.AUTO) private Integer id; private String name; private Integer age; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; @Version private Integer version; private StatusEnum status; @TableLogic private Integer deleted; // 添加逻辑删除映射变量 }配置逻辑删除要赋予的值
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl type-enums-package: com.peter.mybatisplus.enums global-config: db-config: logic-delete-value: 1 # 删除时赋1 logic-not-delete-value: 0 # 不删除赋0测试
// UPDATE user SET deleted=1 WHERE id=? AND deleted=0 @Test void deleteTest() { userMapper.deleteById(3); } // SELECT id,name,age,create_time,update_time,version,status,deleted FROM user WHERE deleted=0 @Test void findAll() { List<User> users = userMapper.selectList(null); System.out.println("users = " + users); }
# CRUD 操作
# 查询操作
// 根据Wrapper条件查询
selectList() // 根据条件<wrapper>查询,返回List<T>
selectOne() // 根据条件<wrapper>查询,返回T
selectCount() // 根据条件<wrapper>查询,返回Integer
selectMaps() // 根据条件<wrapper>查询,返回List<Map<String, Object>>
selectObjs() // 根据条件<wrapper>查询,返回List<Object> Object仅为第一个字段的值
// 根据Map条件查询
selectByMap() // 根据Map<String, Object>查询,返回List<T>
// 根据主键(ID)查询
selectById() // 根据id查询,返回T
selectBatchIds() // 根据idList查询,返回List<T>
// 根据Wrapper分页查询
selectPage() // 根据条件<wrapper>查询,返回<E extends IPage<T>> E
selectMapsPage() // 根据条件<wrapper>查询,返回<E extends IPage<Map<String, Object>>> E
# Wrapper 条件查询
Wrapper 是 MP 中的条件构造器,通过 Wrapper 对象我们构造 SQL 中的各种条件,MP 再将这些条件解析为 SQL 进行查询。
/**
* Wrapper条件查询
* Wrapper用于条件构造
*/
@Test
void selectByWrapper() {
// Wrapper条件构造器
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("name", "张三");
// wrapper.gt("age", 18);
// wrapper.lt("age", 22);
// Map<String, Object> map = new HashMap<>();
// map.put("name", "张三");
// map.put("age", 20);
// wrapper.allEq(map);
// wrapper.likeLeft("name", "三");
// wrapper.orderByAsc("age")
userMapper.selectList(null).forEach(System.out::println);
userMapper.selectList(wrapper).forEach(System.out::println);
userMapper.selectOne(wrapper);
userMapper.selectCount(wrapper);
userMapper.selectMaps(wrapper).forEach(System.out::println);
userMapper.selectObjs(wrapper).forEach(System.out::println);
}
# Map 条件查询
简化 Wrapper.allEq()方法将 Map 直接传入作为条件查询。
/**
* 根据Map条件查询
* 类似Wrapper的allEq查询
*/
@Test
void selectByMap() {
Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 20);
userMapper.selectByMap(map).forEach(System.out::println);
}
# 根据主键(ID)进行查询
传入主键值进行查询。
/**
* 根据主键(ID)值进行查询
*/
@Test
void selectById() {
System.out.println(userMapper.selectById(3));
userMapper.selectBatchIds(Arrays.asList(1,2,3)).forEach(System.out::println);
}
# Wrapper 分页查询
使用 Wrapper 条件的同时传入 Page 分页模型对象,MP 会使用 LIMIT 语句进行部分查询,LIMIT 语句中的值随 Page 中配置而变化,业务上只需不断调整 Page 参数并传入查询语句即可实现分页查询。
添加分页查询插件
@Configuration public class MyBatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }测试
/** * 分页查询 * Page用于提供分页模型 * 分页本质利用数据库的 LIMIT 语句 */ @Test void selectInPage() { // IPage<User> page = new Page<>(2, 1); // IPage<User> ret = userMapper.selectPage(page, null); // System.out.println(ret.getSize()); // System.out.println(ret.getTotal()); // System.out.println(ret.getPages()); // ret.getRecords().forEach(System.out::println); IPage<Map<String, Object>> page = new Page<>(2, 1, false); IPage<Map<String, Object>> ret = userMapper.selectMapsPage(page, null); ret.getRecords().forEach(System.out::println); } }
# 自定义 SQL 查询
如果默认查询无法满足要求(比如多表查询),可以自定义 SQL 语句实现查询。
@Component
public interface UserMapper extends BaseMapper<User> {
@Select("select name from user where id = #{id}")
User findBySql(int id);
}
# 插入操作
insert(T entity)
# 插入一条数据
@Test
public void saveTest() {
User user = new User();
user.setName("王五");
user.setAge(18);
user.setStatus(StatusEnum.REST);
System.out.println("userMapper = " + userMapper.insert(user));
}
# 修改操作
updateById(T entity) // 根据entity中的主键(ID)更新
update(T entity, Wrapper) // 根据Wrapper条件进行更新
# 根据主键(ID)更新
@Test
void updateTest() {
User user = userMapper.selectById(3);
user.setName("赵四");
User user1 = userMapper.selectById(3);
user1.setName("赵武");
userMapper.updateById(user1); // 成功
userMapper.updateById(user); // 失败
}
# 根据 Wrapper 条件进行更新
@Test
void updateByWrapper() {
User user = userMapper.selectById(2);
user.setName("赵四");
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("age", 20);
userMapper.update(user, wrapper);
}
# 删除操作
deleteById()
deleteBatchIds()
delete(Wrapper<T> wrapper)
deleteByMap(Map<String, Object> map)
# 根据主键(ID)删除
@Test
void deleteTest() {
userMapper.deleteById(3);
userMapper.deleteBatchIds(Arrays.asList(1,2));
}
# 根据 Wrapper 条件删除
@Test
void deleteByWrapperTest() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("name", "张三");
userMapper.delete(wrapper);
}
# 根据 Map 条件删除
@Test
void deleteByMapTest() {
Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
userMapper.deleteByMap(map);
}
# 代码生成器
MP 支持根据数据表反向生成 实体类、Mapper、Service、Controller 代码,帮助开发者节省时间。
我们只需要引入生成器并做一些配置即可生成代码。
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.1.tmp</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version> </dependency>velocity 是基于 Java 的模板引擎,MP 默认使用 velocity
配置和执行
public class Executor { public static void main(String[] args) { // 创建generator对象 AutoGenerator generator = new AutoGenerator(); // 数据源配置 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setDbType(DbType.MYSQL); dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatisplus?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF8"); dataSourceConfig.setUsername("root"); dataSourceConfig.setPassword("password"); dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver"); generator.setDataSource(dataSourceConfig); // 全局配置 GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java"); globalConfig.setOpen(false); globalConfig.setAuthor("peter"); globalConfig.setServiceName("%sService"); generator.setGlobalConfig(globalConfig); // 包命名配置 PackageConfig packageConfig = new PackageConfig(); packageConfig.setParent("com.peter.mybatisplus"); packageConfig.setModuleName("generator"); packageConfig.setController("controller"); packageConfig.setService("service"); packageConfig.setServiceImpl("service.impl"); packageConfig.setMapper("mapper"); packageConfig.setEntity("entity"); generator.setPackageInfo(packageConfig); // 代码生成策略配置 StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setEntityLombokModel(true); strategyConfig.setNaming(NamingStrategy.underline_to_camel); strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel); generator.setStrategy(strategyConfig); // 开始执行 generator.execute(); }
# 总结
MybatisPlus 作为 Mybatis 的扩展和增强,提供了一组常用方法帮助开发者简化数据库操作,同时保留了对 Mybatis 的兼容性,当自带的功能无法满足时,开发者仍然可以使用 Mybatis 的功能进行开发。
源码:https://github.com/PeterWangYong/blog-code/tree/master/mybatis-plus