开发一个MyBatis通用Mapper的轮子
作者:互联网
一、前言
程序猿为什么如此执着于造轮子?MyBatis-Plus如此强大的工具流行这么多年了,我为啥还在重复造这样的轮子?
1、公司的技术规范不允许使用MyBatis-Plus,咱也不知道什么原因;
3、以前使用SpringDataJpa惯了,今年第一次用MyBatis,必须把它打造成我想要的样子;
6、MyBatis-Plus好像不支持联合主键;
7、还有一些其它的需求,比如对字典字段自动翻译:字典可能来自枚举、字典表、Redis......
10、通用数据权限控制;
11、如果不造此轮子,就没有这篇文章。
以上12点原因,便是造这个轮子的理由。实际上,轮子不重要,重要的是掌握轮子的原理,取其精华,去其糟粕。也欢迎大家拍砖,请轻拍,数学能力被谁拍坏了谁来陪。
二、需求
通用Mapper起码应该包含以下功能:
1、增
2、删
3、改
4、批量增
5、批量删
6、只更新指定字段
7、分页查询查当前页
8、分页查询查总数
9、字典字段翻译
10、数据权限控制
大概长下面这个样子:
public interface BaseMapper<T,K> { int insert(T t); int batchInsert(List<T> entity); int deleteById(K id); int deleteBatchIds(Collection<K> ids); int updateById(T entity); int updateSelectiveById(T entity); T selectById(K id); List<T> selectBatchIds(Collection<K> ids); List<T> selectAll(); List<T> selectPage(PageRequest<T> pageRequest); Long selectCount(T entity); }
三、实现原理
1、基于MyBatis3提供的SqlProvider构建动态Sql
例如如下代码:
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName") List<User> getUsersByName(String name); class UserSqlBuilder { public static String buildGetUsersByName(final String name) { return new SQL(){{ SELECT("*"); FROM("users"); if (name != null) { WHERE("name like #{value} || '%'"); } ORDER_BY("id"); }}.toString(); } }
2、基于自定义注解,为实体和数据库表建立对应关系
例如如下代码:
@Table("user") public class User { @Id(auto = true) @Column(value = "id") private Long id; @Column(value = "name", filterOperator = FilterOperator.LIKE) @OrderBy(orderPriority = 0) private String name; @OrderBy(order = Order.DESC, orderPriority = 1) private Integer age; private String email; @Transient private String test; }
基于以上两个原理,当方法被调用时,我们便可构建出相应的动态Sql,从而实现该通用Mapper。
四、代码实现
1、自定义注解
1)@Table
了解Jpa的朋友一定很熟悉,这个就是为实体指定表名,实体不加这个注解就认为实体名与表名一致:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Table { //表名,不指定则使用实体类名 String value() default ""; }
2)@Column
指定完表名,该指定列名了,同样的如果字段不指定则认为字段名与表列名一致:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Column { //对应数据库列名 String value() default ""; //查询时的过滤类型 FilterOperator filterOperator() default FilterOperator.EQ; //是否查询,select是否带上该字段 boolean selectable() default true; //是否插入,insert是否带上该字段 boolean insertable() default true; //是否更新,update是否带上该字段 boolean updatable() default true; }
3)@Id
这个注解就是为了表明该字段是否是数据库主键。当然,这个注解可以与@Column合并,但为了更清晰,我还是决定单独使用这个注解。并且,也方便后期扩展。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Id { //主键是否自动生成 boolean auto() default false; }
4)@OrderBy
这个注解来标明查询时的排序字段,同时考虑如果排序字段有多个,可定义优先级:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface OrderBy { //排序 Order order() default Order.ASC; //多个排序字段先后顺序 int orderPriority() default 0; }
5)@Transient
考虑实体中有些字段在数据库中不存在的情况。使用这个注解来标注这样的字段:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Transient { }