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