最近我在项目中用到了MybatisPlus,自己用它搭了下框架,发现确实挺好用的,写了这篇总结,希望与大家一起进步学习。(本文中如果有错误的地方,欢迎大家指正,记得轻喷,不要打脸就行) 自搭框架采用springBoot+mybatisPlus+mysql
一、环境搭建
- 在pom文件中加入mybatisPlus和lombok依赖(自搭框架,省事,搭着玩玩)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
- application.yml加入mysql数据库连配置
###服务器基本设置
server:
###端口号
port: 8081
###项目名
servlet:
context-path: /
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/yzwtest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
- 分别创建controller、service、mapper文件夹存放控制层、业务层、数据层代码。
- (1)首先是数据层代码 User类。其中comm_user是我所创建的User表
@TableName(value="comm_user")
@Data
public class User extends BaseEntity {
@TableField(value = "name")
private String name;
@TableField(value = "password")
private String password;
}
- (2)service层,分为UserService和UserServiceImpl.其中IService接口和ServiceImpl是mybatisPlus提供的基类,继承该接口,可以使用mybatisPlus所封装的CRUD方法.具体用法可查看mybatisPlus官网
UserService
public interface UserService extends IService<User> {
}
UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
- (3)controller层。创建UserController,进行CRUD测试。mybatisPlus封装的基本方法基本足够我们平时的开发使用,还有lambda表达式的用法(具体可前往官网查看。)。如果不满足我们的需求,我们可以用自定义XMl或者sql注入器(后续也会介绍如何使用)。
二、逻辑删除字段配置
- 在application.yml文件中加入逻辑删除字段配置。其中isValid为所有表字段的逻辑删除字段
mybatis-plus:
global-config:
db-config:
logic-delete-field: isValid #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
logic-delete-value: 0 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 1 # 逻辑未删除值(默认为 0)
- 在实体类字段上加入@TableLogic注解
@TableLogic
private String isValid;
三、利用自动填充功能,回填相关字段
相信大家在设计表的时候,都会有创建时间(人)、更新时间(人)、逻辑删除的字段。但是如果每次做新增和更新的操作自动回填这些字段,可以减少代码量
- (1)创建BaseEntity,User类继承BaseEntity(备注:所有表的公共字段提取出来,可以减少代码量)。在实体类字段上添加 @TableField并填写fill注解
| 值 | 描述 |
|---|---|
| DEFAULT | 默认不处理 |
| INSERT | 插入时填充字段 |
| UPDATE | 更新时填充字段 |
| INSERT_UPDATE | 插入和更新时填充字段 |
@Getter
@Setter
public abstract class BaseEntity implements Serializable {
@TableId(value="id",type = IdType.AUTO)
private Long id;
@TableField(value = "user_created",fill = FieldFill.INSERT)
private Long userCreated;
@TableField(value = "org_created",fill = FieldFill.INSERT)
private Long orgCreated;
@TableField(value = "time_created",fill = FieldFill.INSERT)
private Timestamp timeCreated;
@TableField(value = "user_updated",fill = FieldFill.UPDATE)
private Long userUpdated;
@TableField(value = "org_updated",fill = FieldFill.UPDATE)
private Long orgUpdated;
@TableField(value = "time_updated",fill = FieldFill.UPDATE)
private Timestamp timeUpdated;
@TableField(value = "is_valid",fill = FieldFill.INSERT_UPDATE,select = false)
@TableLogic
private String isValid;
}
- (2)自定义AutoFillObjectHandler自动填充处理器类,并实现MetaObjectHandler
@Component
public class AutoFillObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//判断自动填充的字段,减少性能消耗
if (metaObject.hasGetter("userCreated") && metaObject.hasGetter("orgCreated")
&& metaObject.hasGetter("timeCreated") && metaObject.hasGetter("isValid")) {
setFieldValByName("userCreated", Long.parseLong("1"), metaObject);
setFieldValByName("orgCreated", Long.parseLong("1"), metaObject);
setFieldValByName("timeCreated", new Timestamp(System.currentTimeMillis()), metaObject);
setFieldValByName("isValid", "1", metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
setFieldValByName("userUpdated", 2L, metaObject);
setFieldValByName("test", 2L, metaObject);
setFieldValByName("orgUpdated", 2L, metaObject);
setFieldValByName("timeUpdated", new Timestamp(System.currentTimeMillis()), metaObject);
}
}
四、字段类型处理器(以下二选一,也可以都配置)
- JacksonTypeHandler类型处理。
在pom文件中加入JacksonTypeHandler依赖包
<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
在实体类字段上添加typeHandler注解
@TableField(typeHandler = JacksonTypeHandler.class)
private Book book;
- FastjsonTypeHandler类型处理 在pom文件中加入JacksonTypeHandler依赖包
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
在实体类字段上添加typeHandler注解
@TableField(typeHandler = FastjsonTypeHandler.class)
private Book book;
五、sql注入器
MybatisPlus封装了CRUD的许多方法,若不符合自己的需求,可以通过sql注入器,添加自己所需的自定义方法。接下来上代码,自定义方法为findOne
- (1)创建method文件夹,自定义方法FindOne,继承AbstractMethod
public class FindOne extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
/* 执行 SQL ,动态 SQL 参考类 SqlMethod */
String sql = "select * from " + tableInfo.getTableName()
+ " where " + tableInfo.getKeyColumn() + "=#{" + tableInfo.getKeyProperty() + "}";
/* mapper 接口方法名一致 */
String method = "findOne";
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return addSelectMappedStatementForTable(mapperClass, method, sqlSource, tableInfo);
}
}
- (2)创建自定义sql注入器,继承DefaultSqlInjector
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
//增加自定义方法
methodList.add(new FindOne());
return methodList;
}
}
- (3)创建MyBaseMapper,继承BaseMapper;UserMapper继承MyBaseMapper
UserMapper
@Mapper
public interface UserMapper extends MyBaseMapper<User> {
}
MyBaseMapper
public interface MyBaseMapper<T> extends BaseMapper<T> {
/**
*自定义findOne方法
*/
T findOne(Serializable id);
/**
*自定义deleteAll方法
*/
int deleteAll();
}
- (4)创建MP配置文件MybatisPlusConfig,放在config目录下。
@Configuration
public class MybatisPlusConfig {
/**
* 自定义sql注入器
*/
@Bean
public MySqlInjector mySqlInjector (){
return new MySqlInjector();
}
}
做完以上配置,就完成自定义方法的注入。但是讲到sql注入器,不得不讲下mapper层选装件。mapper层选装件需要sql注入器配合使用。
mapper层选装件的使用
| 选装件 | 效果备注 |
|---|---|
| alwaysUpdateSomeColumnById(T entity) | 根据ID更新固定的某些字段 |
| insertBatchSomeColumn(List entityList) | 批量新增数据,自选字段新增 |
| deleteByIdWithFill(T entity) | 根据ID删除逻辑删除字段,并带字段回填功能 |
- 在MyBaseMapper上添加选装件的方法
public interface MyBaseMapper<T> extends BaseMapper<T> {
/**
*自定义findOne方法
*/
T findOne(Serializable id);
//选装件方法
int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY)T entity);
int insertBatchSomeColumn(List<T> entityList);
int deleteByIdWithFill(T entity);
}
- 在自定义注入器MySqlInjector上添加选装件方法
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
//增加自定义方法
methodList.add(new DeleteAll());
methodList.add(new FindOne());
//predicate判断不需要更新的字段
methodList.add(new AlwaysUpdateSomeColumnById(predicate ->!predicate.getColumn().equals("sex")));
//predicate判断不需要新增的字段
methodList.add(new InsertBatchSomeColumn(predicate ->!predicate.getColumn().equals("sex")));
methodList.add(new LogicDeleteByIdWithFill());
return methodList;
}
}
- deleteByIdWithFill选装件使用时,所需要数据回填的字段添加@TableField(fill = FieldFill.UPDATE)注解。因为是逻辑删除,执行的是update操作
@TableField(value = "name",fill = FieldFill.UPDATE)
private String name;
@TableField(value = "password",fill = FieldFill.UPDATE)
private String password;
六、MP通用枚举
- 自定义枚举类SexEnum,实现MP提供的IEnum
public enum SexEnum implements IEnum<String> {
MAN("1","男"),
WOMAN("0","女"),
NONE("2","未知");
private String value;
private String desc;
SexEnum(String value , String desc) {
this.value = value;
this.desc = desc;
}
@Override
public String getValue() {
return this.value;
}
}
- 在application.yml文件中加入枚举路径配置
mybatis-plus:
type-enums-package: com.yuzw.cusproject.mybatisplus.enums
- 在MybatisPlusConfig配置Jackson序列化
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer(){
return builder -> builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
}
- 在User类用枚举定义
@TableField(value = "sex",fill = FieldFill.UPDATE)
private SexEnum sexEnum;
随后进行CRUD操作,可以进行正常的操作。但是在控制层(即controller)有个问题,在接收JSON的User类时,枚举是按照ordinal(下标)接收的。举个例子,传的是0(女),但是后台接收到的是1(男)。因为1(男)在SexEnum是第一个定义的。如果还不理解,上图:
七、自定义枚举
- 自定义BaseEnum枚举类接口
public interface BaseEnum<T> {
/**
* 真正与数据库进行映射的值
*
* @return value
*/
@JsonValue
T getValue();
/**
* 显示的名称
*
* @return displayName
*/
String getDisplayName();
/**
* 按枚举的value获取枚举实例
*
* @param enumType 枚举类型
* @param value 值
* @return T
*/
static <E extends BaseEnum> E fromValue(Class<E> enumType, Object value) {
if (value == null) {
return null;
}
for (E e : enumType.getEnumConstants()) {
if ((value instanceof String && value.equals(e.getValue())) ||
(value instanceof Integer && value == e.getValue()) ||
(value instanceof BigDecimal && Integer.valueOf(value.toString()) == e.getValue())) {
return e;
}
}
return null;
}
/**
* 按枚举的value获取枚举显示名称
*
* @param enumType 枚举类型
* @param value 值
* @return String
*/
static <E extends BaseEnum> String getDisplayNameByValue(Class<E> enumType, Object value) {
E e = fromValue(enumType, value);
if (e == null) {
return null;
} else {
return e.getDisplayName();
}
}
/**
* 获取显示名称
*
* @param e 枚举
* @return String
*/
static <E extends BaseEnum> String getDisplayName(E e) {
if (e == null) {
return null;
} else {
return e.getDisplayName();
}
}
/**
* 批量-按枚举的value获取枚举显示名称
*
* @param enumType 枚举类型
* @param value 值:多值逗号分隔
* @return String
*/
static <E extends BaseEnum> String getDisplayNameByValues(Class<E> enumType, String value) {
String[] values = value.split(",");
StringBuilder result = new StringBuilder();
for (int i = 0; i < values.length; i++) {
for (E e : enumType.getEnumConstants()) {
if (values[i].equals(e.getValue().toString())) {
if (result.toString().equals("")) {
result.append(e.getDisplayName());
} else {
result.append(",").append(e.getDisplayName());
}
}
}
}
return result.toString();
}
}
- 自定义枚举类型转换器UniversalEnumTypeHandler
public class UniversalEnumTypeHandler<E extends BaseEnum> extends BaseTypeHandler<E> {
/**
* 枚举类型
*/
private Class<E> type;
/**
* 枚举数组
*/
private E[] enums;
/**
* Constructor
*
* @param type 枚举类型
*/
public UniversalEnumTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
this.enums = this.type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
//BaseTypeHandler 进行非空校验
if (jdbcType == null) {
ps.setObject(i, parameter.getValue());
} else {
ps.setObject(i, parameter.getValue(), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object code = rs.getObject(columnName);
if (rs.wasNull()) {
return null;
}
return getEnumByValue(code);
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object code = rs.getObject(columnIndex);
if (rs.wasNull()) {
return null;
}
return getEnumByValue(code);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Object code = cs.getObject(columnIndex);
if (cs.wasNull()) {
return null;
}
return getEnumByValue(code);
}
/**
* 值获取枚举对象
*
* @param value 值
* @return 枚举
* @author Frode Fan
*/
private E getEnumByValue(Object value) {
if (value instanceof Integer) {
for (E e : enums) {
if (e.getValue() == value) {
return e;
}
}
throw new IllegalArgumentException("Unknown enumeration type , please check the enumeration code : " +
value);
}
if (value instanceof BigDecimal) {
for (E e : enums) {
if (e.getValue() == Integer.valueOf(value.toString())) {
return e;
}
}
throw new IllegalArgumentException("Unknown enumeration type , please check the enumeration code : " +
value);
}
if (value instanceof String) {
for (E e : enums) {
if (value.equals(e.getValue())) {
return e;
}
}
throw new IllegalArgumentException("Unknown enumeration type , please check the enumeration code : " +
value);
}
throw new IllegalArgumentException("Unknown enumeration type:" + enums.getClass() + ", value:" + value);
}
}
3.在MybatisPlusConfig中配置默认的枚举类型处理器
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return new MybatisPlusCustomizers();
}
/**
* 自定义配置
*/
class MybatisPlusCustomizers implements ConfigurationCustomizer {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.setDefaultEnumTypeHandler(UniversalEnumTypeHandler.class);
}
}
- 自定义枚举SexEnum实现BaseEnum
public enum SexEnum implements BaseEnum<String> {
MAN("1","男"),
WOMAN("0","女"),
NONE("2","未知");
private String value;
private String desc;
SexEnum(String value , String desc) {
this.value = value;
this.desc = desc;
}
@Override
public String getValue() {
return this.value;
}
@Override
public String getDisplayName() {
return desc;
}
}
最后上下目录结构图,方便大家参考
感言
这是我第一次写博客,写的不正确的地方欢迎大家指正