开发一个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

开发一个MyBatis通用Mapper的轮子

例如如下代码:

@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 {
}

本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;

2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;

3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;

4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;

5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/294971.html

(0)
上一篇 2022年12月27日
下一篇 2022年12月27日

相关推荐

发表回复

登录后才能评论