# mybatis-plus-ext
**Repository Path**: badx/mybatis-plus-ext
## Basic Information
- **Project Name**: mybatis-plus-ext
- **Description**: mybatis-plus框架的拓展包,在框架原有基础上做了进一步的轻度封装,更加方便使用,针对数据自动填充(类似JPA中的审计)、关联查询(类似sql中的join)、自动建表(仅支持mysql)、冗余数据自动更新、固定条件等方面通过注解做了增强。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 133
- **Created**: 2021-09-07
- **Last Updated**: 2021-09-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 简介
> 本框架结合公司日常业务场景,对[Mybatis-Plus](https://gitee.com/baomidou/mybatis-plus) 做了进一步的拓展封装,即保留MP原功能,又添加更多有用便捷的功能。具体拓展体现在`数据自动填充(类似JPA中的审计)`、`关联查询(类似sql中的join)`、`自动建表(仅支持mysql)`、`冗余数据自动更新`、`动态条件`等功能做了补充完善。其中`自动建表`,是在[A.CTable](https://gitee.com/sunchenbin/mybatis-enhance) 框架上的基础上改进适配本框架的,只保留了其表创建功能,因此改动较大不与原框架兼容。
## 前言
##### 如果感觉框架对您有所帮助,请给个小星星⭐️,作者二线不知名小公司码农一枚,欢迎来撩共同进步。
## 快速开始
### 引入jar包
> starter内自带了MybatisPlus3.4.3.2版本及spring-boot2.3.12的依赖管理,如果要更改springboot的版本,可以排除掉,但是如果要变更MybatisPlus的版本,请注意了,框架中重写了TableInfoHelper,不同版本的MP该类有所变动,同时框架内也采用了MP的部分工具类,例如LambdaUtils、ReflectionKit等在不同的版本也有所变动,需要小心,哈哈哈哈,可以联系我帮你改~~
```xml
com.tangzc
mybatis-plus-ext-boot-starter
1.2.8
```
### 自动建表
> 根据实体上的注解及字段注解自动创建、更新数据库表。
>
> 官方的设计思路是默认Bean下的所有字段均不是表字段,需要手动通过@Column声明,我在引用过来之后,改为了默认所有字段均为表字段,只有被MP的@TableField(exist=false)修饰的才会被排除,具备@TableField(exist=false)功能的注解有:@Exclude、@Bind**系列,他们集成了@TableField,且内置exist属性为false了。
>
> 另外A.CTable框架内部集成了类似MP的功能,不如MP完善,所以我也剔除掉了,顺带解决了不兼容和bug。同时像DefaultValue注解重名了,也给它改名为ColumnDefault了,另外整理了一遍内部的注解利用spring的AliasFor做了关联,更方便管理。
>
> 其中还有一点,@Table里面加了一个primary属性,表示是否为主表,为了支持多个Entity对应一个数据库表(正常用不到请忽略^_^)
```java
@Data
// @Table标记的可被识别为需要自动创建表的Entity
@Table(comment = "用户")
public class User {
// 自动识别id属性名为主键
// @IsAutoIncrement声明为自增主键,什么都不声明的话,默认为雪花算法的唯一主键(MP的自带功能),推荐默认便于后期的数据分布式存储等处理。
@IsAutoIncrement
// 字段注释
@ColumnComment("主键")
// 字段长度
@ColumnLength(32)
private String id;
// 索引
@Index
// 非空
@IsNotNull
@ColumnComment("名字")
private String name;
// 唯一索引
@Unique
// 非空
@IsNotNull
@ColumnComment("手机号")
private String phone;
// 省略其他属性
......
}
```
```java
// 启用自动生成数据库表功能,此处简化了A.CTable的复杂配置,均采用默认配置
@EnableAutoTable
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
```
```properties
# actable的配置信息保留了如下几项,均做了默认配置,正常无需配置
actable.table.auto=update
actable.model.pack=[Spring启动类所在包]
actable.database.type=mysql
actable.index.prefix=自己定义的索引前缀#该配置项不设置默认使用actable_idx_
actable.unique.prefix=自己定义的唯一约束前缀#该配置项不设置默认使用actable_uni_
```
### 数据填充
> 可以在数据插入或更新的时候,自动赋值数据操作人、操作时间、默认值等属性。
>
> 以文章发布为例,讲解一下数据填充的基本用法。通过如下例子可发现,在创建Artice的时候,我们无需再去关心过多的与业务无关的字段值,只需要关心`title`、`content`两个核心数据即可,其他的数据均会被框架处理。
```java
@Data
@Table(comment = "文章")
public class Article {
// 字符串类型的ID,默认也是雪花算法的一串数字(MP的默认功能)
@ColumnComment("主键")
private String id;
@ColumnComment("标题")
private String title;
@ColumnComment("内容")
private String content;
// 文章默认激活状态
@DefaultValue("ACTIVE")
@ColumnComment("内容")
// ActicleStatusEnum(ACTIVE, INACTIVE)
private ActicleStatusEnum status;
@ColumnComment("发布时间")
// 插入数据时候会自动获取系统当前时间赋值,支持多种数据类型,具体可参考@OptionDate注解详细介绍
@InsertOptionDate
private Date publishedTime;
@ColumnComment("发布人")
// 插入的时候,根据UserIdAutoFillHandler自动填充用户id
@InsertOptionUser(UserIdAutoFillHandler.class)
private String publishedUserId;
@ColumnComment("发布人名字")
// 插入的时候,根据UserIdAutoFillHandler自动填充用户名字
@InsertOptionUser(UsernameAutoFillHandler.class)
private String publishedUsername;
@ColumnComment("最后更新时间")
// 插入和更新数据时候会自动获取系统当前时间赋值,支持多种数据类型,具体可参考@OptionDate注解详细介绍
@InsertUpdateOptionDate
private Date publishedTime;
@ColumnComment("最后更新人")
// 插入和更新的时候,根据UserIdAutoFillHandler自动填充用户id
@InsertUpdateOptionUser(UserIdAutoFillHandler.class)
private String publishedUserId;
@ColumnComment("最后更新人名字")
// 插入和更新的时候,根据UserIdAutoFillHandler自动填充用户名字
@InsertUpdateOptionUser(UsernameAutoFillHandler.class)
private String publishedUsername;
}
```
```java
/**
* 全局获取用户ID
* 此处实现IOptionByAutoFillHandler接口和AutoFillHandler接口均可,建议实现IOptionByAutoFillHandler接口,
* 因为框架内的BaseEntity默认需要IOptionByAutoFillHandler的实现。后面会讲到BaseEntity的使用。
*/
@Component
public class UserIdAutoFillHandler implements IOptionByAutoFillHandler {
/**
* @param object 当前操作的数据对象
* @param clazz 当前操作的数据对象的class
* @param field 当前操作的数据对象上的字段
* @return 当前登录用户id
*/
@Override
public String getVal(Object object, Class> clazz, Field field) {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
// 配合网关或者过滤器,token校验成功后就把用户信息塞到header中
return request.getHeader("user-id");
}
}
```
```java
/**
* 全局获取用户名
*/
@Component
public class UsernameAutoFillHandler implements AutoFillHandler {
/**
* @param object 当前操作的数据对象
* @param clazz 当前操作的数据对象的class
* @param field 当前操作的数据对象上的字段
* @return 当前登录用户id
*/
@Override
public String getVal(Object object, Class> clazz, Field field) {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
// 配合网关或者过滤器,token校验成功后就把用户信息塞到header中
return request.getHeader("user-name");
}
}
```
### 关联查询
> 数据关联查询的解决方案,替代sql中的join方式,通过注解关联多表之间的关系,查询某实体的时候,自动带出其关联性的数据实体。
>
> 本示例以比较复杂的通过中间表关联数据的案例来讲解下,用户和角色之间多对多,通过中间表进行数据级联,@BindEntity\*系列是关联Entity的数据,@BindField\*系列是关联Entity下的某个字段。当@Bind\*系列注解用在对象上即表达一对一,当注解在List上时便表达一对多的意思,当外部对象本身就是查询集合的情况下便是多对多的场景了。
```java
@Data
@Table(comment = "角色信息")
public class Role {
@ColumnComment("主键")
private String id;
@ColumnComment("角色名")
private String name;
}
```
```java
@Data
@Table(comment = "用户信息")
public class User {
@ColumnComment("主键")
private String id;
@ColumnComment("用户名")
private String username;
@ColumnComment("密码")
private String password;
// 关键配置,声明了User想关联对应的Rule集合,中间表是UserRule
@BindEntityByMid(conditions = @MidCondition(
midEntity = UserRole.class, selfMidField = "userId", joinMidField = "roleId"
))
private List roles;
}
```
```java
@Data
@Table(comment = "用户-角色关联关系")
public class UserRole {
@ColumnComment("主键")
private String id;
@ColumnComment("用户id")
private String userId;
@ColumnComment("角色id")
private String roleId;
}
```
```java
/**
* 用户服务
*/
@Slf4j
@Service
public class UserService {
// UserRepository继承了BaseRepository,后面会讲BaseRepository
@Resource
private UserRepository userRepository;
/**
* 根据用户的名字模糊查询所有用户的详细信息
*/
@Transactional(readOnly = true)
public List searchUserByNameWithRule(String name) {
// MP的lambda查询方式
List userList = userRepository.lambdaQuery()
.eq(name != null, User::getUsername, name)
.list();
// 关键步骤,指定关联角色数据。如果你打开sql打印,会看到3条sql语句,第一条根据id去User表查询user信息,第二条根据userId去UserRule中间表查询所有的ruleId,第三条sql根据ruleId集合去Rule表查询全部的权限
Binder.bindOn(userList, User::getRoles);
// Binder.bind(userList); 此种用法默认关联user下所有声明需要绑定的元素
return UserMapping.MAPPER.toDto5(userList);
}
/**
* 根据用户的名字模糊查询所有用户的详细信息,等价于上一个查询方式
*/
@Transactional(readOnly = true)
public List searchUserByNameWithRule2(String name) {
// 本框架拓展的lambda查询器lambdaQueryPlus,增加了bindOne、bindList、bindPage
// 显然这是一种更加简便的查询方式,但是如果存在多级深度的关联关系,此种方法就不适用了,还需要借助Binder
List userList = userRepository.lambdaQueryPlus()
.eq(name != null, User::getUsername, name)
.bindList(User::getRoles);
return UserMapping.MAPPER.toDto5(userList);
}
}
```
==提示==: 假如存在此种场景:`User`、`Role`、`Menu`三个实体,他们之间的关系是:`User` 多对多 `Role`、`Role` 多对多`Menu`,当我查询出User的集合后,如何获取Role和Menu的数据呢?
```java
// 数据库查询出了用户列表 【1】
List userList = userRepository.list();
// 为所有用户关联角色信息 【2】
Binder.bindOn(userList, User::getRoles);
// 为所有角色信息关联菜单信息 【3】
// Deeper为一个深度遍历工具,可以深入到对象的多层属性内部,从而获取全局上该层级的所有对象同一属性
Binder.bindOn(Deeper.with(userList).inList(User::getRoles), User::getMenus);
```
###### 注意📢:【2】和【3】存在顺序依赖,必须先执行【2】才能执行【3】
### 数据冗余
> 当其他表的数据需要作为当前表的查询条件的时候,多数情况下会使用sql的join语法,另一种方案是做数据冗余,讲其他表的字段作为当前表的字段,但是牵扯一个数据修改后同步的问题,本框架可以解决。
>
> 假设用户评论的场景,评论上需要冗余用户名和头像,如果用户的名字和头像有改动,则需要同步新的改动,代码如下:
```java
@Data
@Table(comment = "用户信息")
public class User {
@ColumnComment("主键")
private String id;
@ColumnComment("用户名")
private String username;
@ColumnComment("头像")
private String icon;
// 省略其他属性
......
}
```
```java
@Data
@Table(comment = "评论")
public class Comment {
@ColumnComment("主键")
private String id;
@ColumnComment("评论内容")
private String content;
@ColumnComment("评论人id")
private String userId;
// 基于该注解,框架会自动注册监听EntityUpdateEvent事件,User的updateById和updateBatchById两个方法会自动发布EntityUpdateEvent事件
@DataSource(source = User.class, field = "username", conditions = @Condition(selfField = "userId"))
@ColumnComment("评论人名称")
private String userName;
@DataSource(source = User.class, field = "icon", condition = @Condition(selfField = "userId"))
@ColumnComment("评论人头像")
private String userIcon;
}
```
### 动态条件
> 适用场景:数据筛选,比如根据不同权限获取不同数据,用户只能看到自己的数据,管理员能看到所有人的数据。
>
> 此种场景,我们通常需要在每一个查询、更新、删除的sql操作上都追加上某个条件,很容易忘记,但是可以抽象成注解直接配置到Entity上,就省去了每个数据操作关心这个特殊条件了。
```java
@Data
@Table(comment = "文章")
public class Article {
@ColumnComment("主键")
private String id;
@ColumnComment("标题")
private String title;
@ColumnComment("内容")
private String content;
@ColumnComment("发布人")
@InsertOptionUser(UserIdAutoFillHandler.class)
// 添加了该注解后,针对文章的查询、修改、删除操作,均会被自动带上 published_user_id=或者in的添加
@DynamicCondition(CurrentUserDynamicConditionHandler.class)
private String publishedUserId;
// 省略其他字段
......
}
```
```java
@Component
public class CurrentUserDynamicConditionHandler implements IDynamicConditionHandler {
@Resource
private HttpServletRequest request;
@Override
public List