Mybatis-Plus关联查询、⾃动建表、数据填充、动态条件
简介
本框架( )结合公司⽇常业务场景,对做了进⼀步的拓展封装,即保留MP原功能,⼜添加更多有⽤便捷的功能。具体拓展体现在数据⾃动填充(类似JPA中的审计)、关联查询(类似sql中的join)、⾃动建表(仅⽀持mysql)、冗余数据⾃动更新、动态条件等功能做了补充完善。其中⾃动建表,是在框架上的基础上改进适配本框架的,只保留了其表创建功能,因此改动较⼤不与原框架兼容。
项⽬地址
快速开始
引⼊jar包
starter内⾃带了MybatisPlus3.4.3.3版本及spring-boot2.3.12的依赖管理,如果要更改springboot的版本,可以排除掉,但是如果要变更MybatisPlus的版本,请注意了,框架中重写了TableInfoHelper,不同版本的MP该类有所变动,同时框架内也采⽤了MP的部分⼯具类,例如LambdaUtils、ReflectionKit等在不同的版本也有所变动,需要⼩⼼,哈哈哈哈,可以联系我帮你改~~
<dependency>
<groupId>com.tangzc</groupId>
<artifactId>mybatis-plus-ext-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
⾃动建表
根据实体上的注解及字段注解⾃动创建、更新数据库表。
官⽅的设计思路是默认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对应⼀个数据库表(正常⽤不到请忽略_)
@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;
// 省略其他属性
......
}
// 启⽤⾃动⽣成数据库表功能,此处简化了A.CTable的复杂配置,均采⽤默认配置
@EnableAutoTable
tone control是什么意思@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
# actable的配置信息保留了如下⼏项,均做了默认配置,正常⽆需配置
actable.table.auto=update
pe=mysql
actable.index.prefix=⾃⼰定义的索引前缀#该配置项不设置默认使⽤actable_idx_web平台有哪些
actable.unique.prefix=⾃⼰定义的唯⼀约束前缀#该配置项不设置默认使⽤actable_uni_
数据填充
可以在数据插⼊或更新的时候,⾃动赋值数据操作⼈、操作时间、默认值等属性。
以⽂章发布为例,讲解⼀下数据填充的基本⽤法。通过如下例⼦可发现,在创建Artice的时候,我们⽆需再去关⼼过多的与业务⽆关的字段值,只需要关
⼼title、content两个核⼼数据即可,其他的数据均会被框架处理。
@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;
}
/**
* 全局获取⽤户ID
* 此处实现IOptionByAutoFillHandler接⼝和AutoFillHandler接⼝均可,建议实现IOptionByAutoFillHandler接⼝,
* 因为框架内的BaseEntity默认需要IOptionByAutoFillHandler的实现。后⾯会讲到BaseEntity的使⽤。
*/
@Component
public class UserIdAutoFillHandler implements IOptionByAutoFillHandler<String> {
/**
* @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中
Header("user-id");
}
}
/**
* 全局获取⽤户名
*/
@Component
public class UsernameAutoFillHandler implements AutoFillHandler<String> {
/**
* @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中
Header("user-name");
}
}
关联查询
数据关联查询的解决⽅案,替代sql中的join⽅式,通过注解关联多表之间的关系,查询某实体的时候,⾃动带出其关联性的数据实体。
本⽰例以⽐较复杂的通过中间表关联数据的案例来讲解下,⽤户和⾓⾊之间多对多,通过中间表进⾏数据级联,@BindEntity*系列是关联Entity的数据,@BindField*系列是关联Entity下的某个字段。当@Bind*系列注解⽤在对象上即表达⼀对⼀,当注解在List上时便表达⼀对多的意思,当外部对象本⾝就是查询集合的情况下便是多对多的场景了。
@Data
@Table(comment = "⾓⾊信息")
public class Role {
@ColumnComment("主键")
private String id;
@ColumnComment("⾓⾊名")
private String name;
}
@Data
@Table(comment = "⽤户信息")
public class User {
@ColumnComment("主键")
private String id;
@ColumnComment("⽤户名")
private String username;
@ColumnComment("密码")
private String password;
// 关键配置,声明了User想关联对应的Rule集合,中间表是UserRule
@BindEntityByMid(conditions = @MidCondition(
field ofmidEntity = UserRole.class, selfMidField = "userId", joinMidField = "roleId"
))
private List<Role> roles;
}
@Data
@Table(comment = "⽤户-⾓⾊关联关系")
public class UserRole {
@ColumnComment("主键")
private String id;
@ColumnComment("⽤户id")
private String userId;
@ColumnComment("⾓⾊id")
private String roleId;
}
/
**
* ⽤户服务
*/
@Slf4j
@Service
public class UserService {
// UserRepository继承了BaseRepository<UserMapper, User>,后⾯会讲BaseRepository
@Resource
private UserRepository userRepository;
/**
* 根据⽤户的名字模糊查询所有⽤户的详细信息
*/
@Transactional(readOnly = true)
public List<UserDetailWithRoleDto> searchUserByNameWithRule(String name) {
// MP的lambda查询⽅式
List<User> userList = userRepository.lambdaQuery()
.eq(name != null, User::getUsername, name)
.list();
// 关键步骤,指定关联⾓⾊数据。如果你打开sql打印,会看到3条sql语句,第⼀条根据id去User表查询user信息,第⼆条根据userId去UserRule中间表查询所有的ruleId,第三条sql根据ruleId集合去R Binder.bindOn(userList, User::getRoles);
// Binder.bind(userList); 此种⽤法默认关联user下所有声明需要绑定的元素
c++ scanf用法return Dto5(userList);
}
/**
* 根据⽤户的名字模糊查询所有⽤户的详细信息,等价于上⼀个查询⽅式
*/
@Transactional(readOnly = true)
public List<UserDetailWithRoleDto> searchUserByNameWithRule2(String name) {
// 本框架拓展的lambda查询器lambdaQueryPlus,增加了bindOne、bindList、bindPage
// 显然这是⼀种更加简便的查询⽅式,但是如果存在多级深度的关联关系,此种⽅法就不适⽤了,还需要借助Binder
List<User> userList = userRepository.lambdaQueryPlus()
.eq(name != null, User::getUsername, name)
.bindList(User::getRoles);
return Dto5(userList);
}
}
提⽰: 假如存在此种场景:User、Role、Menu三个实体,他们之间的关系是:User 多对多 Role、Role 多对多Menu,当我查询出User的集合后,如何获取Role和Menu的数据
呢?
// 数据库查询出了⽤户列表【1】
List<User> userList = userRepository.list();
// 为所有⽤户关联⾓⾊信息【2】
Binder.bindOn(userList, User::getRoles);
// 为所有⾓⾊信息关联菜单信息【3】
/
/ Deeper为⼀个深度遍历⼯具,可以深⼊到对象的多层属性内部,从⽽获取全局上该层级的所有对象同⼀属性
pycharm学生免费使用多久Binder.bindOn(Deeper.with(userList).inList(User::getRoles), User::getMenus);
注意 :【2】和【3】存在顺序依赖,必须先执⾏【2】才能执⾏【3】
数据冗余
当其他表的数据需要作为当前表的查询条件的时候,多数情况下会使⽤sql的join语法,另⼀种⽅案是做数据冗余,讲其他表的字段作为当前表的字段,但是牵扯⼀个
数据修改后同步的问题,本框架可以解决。
假设⽤户评论的场景,评论上需要冗余⽤户名和头像,如果⽤户的名字和头像有改动,则需要同步新的改动,代码如下:
@Data
@Table(comment = "⽤户信息")
public class User {
@ColumnComment("主键")
private String id;
@ColumnComment("⽤户名")
private String username;
@ColumnComment("头像")
private String icon;
// 省略其他属性
......
}
@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;
mysql语句的执行顺序}
动态条件
适⽤场景:数据筛选,⽐如根据不同权限获取不同数据,⽤户只能看到⾃⼰的数据,管理员能看到所有⼈的数据。
此种场景,我们通常需要在每⼀个查询、更新、删除的sql操作上都追加上某个条件,很容易忘记,但是可以抽象成注解直接配置到Entity上,就省去了每个数据操作关⼼这个特殊条件了。
@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;
// 省略其他字段
......
}
@Component
public class CurrentUserDynamicConditionHandler implements IDynamicConditionHandler {
@Resource
private HttpServletRequest request;
@Override
public List<Object> values() {
/
/ 只有当enable()返回true的时候本动态条件才⽣效
// 返回空集合或者null的时候,sql上体现的是 [column] is null,只返回⼀个值的时候sql上体现的是 [column]=***,返回集合的时候,sql上体现的是 [column] in (***)
String userId = Header("USER_ID");
return Collections.singletonList(userId);
}
@Override
public boolean enable() {
// 简单例⼦:header中取⽤户权限,如果是⾮管理员则执⾏该过滤条件,如果是管理员默认查全部,返回false,本动态条件失效
String userRule = Header("USER_ROLE");
return !"ADMIN".equals(userRule);
}
}
BaseEntity使⽤
通常的表设计中,都会要求添加⼀些审计数据,⽐如创建⼈、创建时间、最后修改⼈、最后修改时间,但是这些属性⼜不应该属于业务的,更多的是为了数据管理使⽤的。如果业务需要使⽤的话,建议起⼀个有意义的业务名称与上述的创建时间区分开,⽐如⽤户的注册时间(registrationTime)。为了简化数据审计字段的⼯作量,框架内部集成了BaseEntity
@Getter
@Setter
public class BaseEntity<ID_TYPE extends Serializable, TIME_TYPE> {
// 这⾥就是数据填充样例那⾥提到的IOptionByAutoFillHandler接⼝
// 此处单独指定⼀个标记性的接⼝是为了区别⽤户其他数据的⾃动填充,例如⽤户名、⽤户电话等都
会实现AutoFillHandler接⼝,框架上根据该接⼝⽆法拿到唯⼀的实现,因此同样IOptionByAutoFillHa @InsertOptionUser(IOptionByAutoFillHandler.class)
@ColumnComment("创建⼈")
protected ID_TYPE createBy;
@InsertUpdateOptionUser(IOptionByAutoFillHandler.class)
@ColumnComment("最后更新⼈")
protected ID_TYPE updateBy;
@InsertOptionDate
@ColumnComment("创建时间")
protected TIME_TYPE createTime;
@InsertUpdateOptionDate
@ColumnComment("最后更新时间")
protected TIME_TYPE updateTime;
}
还存在某些情况下数据表要求设计成逻辑删除(逻辑删除存在很多弊端,不建议⽆脑所有表都设计为逻辑删除),所以框架同时提供了⼀个BaseLogicEntity,该实现
⽅式利⽤的是MP本⾝⾃带的逻辑删除策略。
@Getter
@Setter
public class BaseLogicEntity<ID_TYPE extends Serializable, TIME_TYPE> extends BaseEntity<ID_TYPE, TIME_TYPE> {
// 使⽤了MP⽀持的逻辑删除注解
@TableLogic
@DefaultValue("0")
@ColumnComment("逻辑删除标志")
protected Integer deleted;
}
BaseRepository使⽤
建议开发中以此为数据基本操作类,⽽不是以*Mapper为基础操作类,如果需要使⽤*Mapper中的⽅法,可以直接通过getMapper()取得Entity对应的*Mapper类,此类
与*Mapper类相⽐做了很多的增强功能,尤其是其lambda语法,⾮常⾼效便捷。
// 集成了MP的ServiceImpl,实现了IBaseRepository接⼝(内部拓展了lambda查询操作)
public abstract class BaseRepository<M extends BaseMapper<E>, E> extends ServiceImpl<M, E> implements IBaseRepository<E> {
@Override
public boolean updateById(E entity) {
boolean result = super.updateById(entity);
if(result) {
// 数据⾃动更新@DataSource注解的配合逻辑
.ate(entity));
}
return result;
}
@Override
public boolean updateBatchById(Collection<E> entityList, int batchSize) {
boolean result = super.updateBatchById(entityList, batchSize);
if(result) {
// 数据⾃动更新@DataSource注解的配合逻辑
for (E entity : entityList) {
}
}
return result;
}
@Override
protected Class<M> currentMapperClass() {
return (Class<M>) Class(), BaseRepository.class, 0);
}
@Override
protected Class<E> currentModelClass() {
return (Class<E>) Class(), BaseRepository.class, 1);
}
}
注解详细介绍
⾃动建表注解
只有⼩部分注解,进⾏了轻微改动,基本所有注解均是通⽤的,详细教程可以直接参考A.CTable官⽅。
@Table
新增⼀个primary属性,isNull属性为了⼀致性改为了isNotNull属性默认false
@TableCharset
@TableComment
@TableEngine
@TablePrimary
新增注解,同步@Table中的primary属性,在多个Entity映射⼀张表的情况下,确定主Entity是哪个,数据表⽣成的时候根据主表来⽣成。
@IgnoreTable
@EnableTimeSuffix
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论