简介

本框架(Gitee地址 )结合公司日常业务场景,对Mybatis-Plus 做了进一步的拓展封装,即保留MP原功能,又添加更多有用便捷的功能。具体拓展体现在数据自动填充(类似JPA中的审计)关联查询(类似sql中的join)自动建表(仅支持mysql)冗余数据自动更新动态条件等功能做了补充完善。其中自动建表,是在A.CTable 框架上的基础上改进适配本框架的,只保留了其表创建功能,因此改动较大不与原框架兼容。

项目地址

https://gitee.com/tangzc/mybatis-plus-ext

快速开始

引入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
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
# actable的配置信息保留了如下几项,均做了默认配置,正常无需配置
actable.table.auto=update
actable.model.pack=[Spring启动类所在包]
actable.database.type=mysql
actable.index.prefix=自己定义的索引前缀#该配置项不设置默认使用actable_idx_
actable.unique.prefix=自己定义的唯一约束前缀#该配置项不设置默认使用actable_uni_

数据填充

可以在数据插入或更新的时候,自动赋值数据操作人、操作时间、默认值等属性。

以文章发布为例,讲解一下数据填充的基本用法。通过如下例子可发现,在创建Artice的时候,我们无需再去关心过多的与业务无关的字段值,只需要关心titlecontent两个核心数据即可,其他的数据均会被框架处理。

@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中
        return request.getHeader("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中
        return request.getHeader("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(
            midEntity = 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集合去Rule表查询全部的权限
        Binder.bindOn(userList, User::getRoles);
        // Binder.bind(userList); 此种用法默认关联user下所有声明需要绑定的元素
        
        return UserMapping.MAPPER.toDto5(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 UserMapping.MAPPER.toDto5(userList);
    }
}

提示: 假如存在此种场景:UserRoleMenu三个实体,他们之间的关系是:User 多对多 RoleRole 多对多Menu,当我查询出User的集合后,如何获取Role和Menu的数据呢?

// 数据库查询出了用户列表 【1】
List<User> userList = userRepository.list();
// 为所有用户关联角色信息 【2】
Binder.bindOn(userList, User::getRoles);
// 为所有角色信息关联菜单信息 【3】
// Deeper为一个深度遍历工具,可以深入到对象的多层属性内部,从而获取全局上该层级的所有对象同一属性
Binder.bindOn(Deeper.with(userList).inList(User::getRoles), User::getMenus);
注意
版权声明:本文为tangzc原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/tangzc/p/15241621.html