tk.mybatis拓展通用Mapper

2,830 阅读4分钟

背景: 最近做项目遇到这么一个事情,在更新表的时候使用UpdateByPrimaryKeyMapper接口的updateByPrimaryKey方法会把对象中的所有字段都更新了(无论对象中是否有null字段),这样带来一个问题就是每一个实体中都有创建人、创建时间、更新人、更新时间这几个字段,更新的时候数据库中这几个字段是有值的,但是在修改的时候,前端传递的参数中是没有这几个字段的(因为在查询的时候我们并没有把这几个字段查出来),所以更新时,创建人、创建时间、更新人、更新时间都是null值,更新人和更新时间好办我写了一个拦截器会用当前操作人和当前时间去塞进去,但是创建人和创建时间不能这么操作吧。

解决办法:
1.在每次更新操作之前,都去数据库里查询一遍,然后把创建人和创建时间塞进对象。这一部分可以使用AOP统一处理(需要了解的朋友可以留言我在写一篇这种方式的)。
2.想一个办法能够更新部分字段的,有的人说可以用UpdateByPrimaryKeySelectiveMapper中的updateByPrimaryKeySelective方法去做更新,这种方式是可以解决上面的问题,但是它带来了一个新的问题就是加入有些字段就是需要给它更新成null的那怎么办呢?所以这种方式不行。现在需要有一种方式能够在更新时忽略我指定的某些字段的方法,所以就打算拓展通用Mapper。

思路: 写一个自定义注解用来标记哪些字段在更新时需要被忽略的。然后写一个Mapper,在Mapper中生成SQL时去掉那些被忽略注解标记的字段就可以了。

1.首先定义一个注解:

 /**
 * @author changjiu.wang
 * @title: IgnoreUpdate
 * @date 2019/8/14 09:01
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreUpdate {
}

2.在实体基类的创建时间和创建人字段标注为忽略更新注解:

/**
 * @author changjiu.wang
 * @title: BaseEntity
 * @date 2019/7/5 14:13
 */
@MappedSuperclass
@Data
public abstract class BaseEntity implements Serializable {

  @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;

  @Column(name = "created_date")
  @IgnoreUpdate // 忽略更新字段
  protected LocalDateTime createdDate;

  @Column(name = "updated_date")
  protected LocalDateTime updatedDate;

  @Column(name = "created_by")
  @IgnoreUpdate // 忽略更新字段
  protected String createdBy;

  @Column(name = "updated_by")
  protected String updatedBy;

  @Column(name = "deleted")
  protected Boolean deleted = false;

}

3.编写注册的Maper接口:

/**
 * @author changjiu.wang
 * @title: UpdateByPrimaryKeyWithIgnoreFieldMapper
 * 扩展根据主键更新的Mapper 带有忽略字段的更新
 * @date 2019/8/13 22:53
 */
@RegisterMapper
public interface UpdateByPrimaryKeyWithIgnoreFieldMapper<T> {

    @UpdateProvider(
        type = UpdateByPrimaryKeyWithIgnoreFieldProvider.class,
        method = "dynamicSQL"
    )
    int updateByPrimaryKeyWithIgnoreField(T entity);
}

这里需要注意一下在你接口定义的方法上需要被@UpdateProvider注解标识,更新的SQL用@UpdateProvider,查询的SQL用@SelectProvider,新增的SQL用@InsertProvider,删除的SQL用@DeleteProvider,这个注解的type属性是一个实现updateByPrimaryKeyWithIgnoreField方法的类。这个方法的参数是传入需要更新的实体,返回值为影响的行数。

5.编写一个实现这个方法的类:

/**
 * @author changjiu.wang
 * @title: UpdateByPrimaryKeyWithIgnoreFieldProvider
 * @date 2019/8/13 23:01
 */
public class UpdateByPrimaryKeyWithIgnoreFieldProvider extends MapperTemplate {

    public UpdateByPrimaryKeyWithIgnoreFieldProvider(Class<?> mapperClass, MapperHelper mapperHelper){
        super(mapperClass, mapperHelper);
    }

    public String updateByPrimaryKeyWithIgnoreField(MappedStatement ms){
        // 获取实体类
        Class<?> entityClass = super.getEntityClass(ms);
        // 获取表名
        String table = SqlHelper.updateTable(entityClass, tableName(entityClass));
        //拼接SQL
        StringBuilder whereCondition = new StringBuilder(" where ");
        StringBuilder columnValue = new StringBuilder();
        // 获取列字段以及值(除了被标记为忽略的字段)
        List<EntityColumn> entityColumns = getColumnWithoutIgnoreField(entityClass);
        if(!CollectionUtils.isEmpty(entityColumns)){
            for(EntityColumn entityColumn : entityColumns){
                // 获取ID字段和值作为where条件
                if(entityColumn.isId()){
                    String idColumn = entityColumn.getColumn();
                    String idValue = entityColumn.getColumnHolder();
                    whereCondition.append(idColumn).append("=").append(idValue);
                }else{
                    // 拼接SQL
                    String column = entityColumn.getColumn();
                    String value = entityColumn.getColumnHolder();
                    columnValue.append(column).append("=").append(value).append(",");
                }
            }
            columnValue = columnValue.deleteCharAt(columnValue.length()-1);
            columnValue.append(whereCondition);
        }
        if(columnValue.length() > 0){
            columnValue.insert(0,table.concat(" set "));
        }
        // 完整的更新语句
        return columnValue.toString();
    }

    // 过滤被忽略的字段
    private List<EntityColumn> getColumnWithoutIgnoreField(Class<?> entityClass) {
        List<EntityColumn> entityColumns = Lists.newArrayList();
        Set<EntityColumn> columnSet = EntityHelper.getColumns(entityClass);
        for (EntityColumn column : columnSet) {
            if (!column.getEntityField().isAnnotationPresent(IgnoreUpdate.class)) {
                entityColumns.add(column);
            }
        }
        return entityColumns;
    }

}

注意这个类必须继承MapperTemplate,方法名updateByPrimaryKeyWithIgnoreField必须和接口中的方法名一致,参数必须是MappedStatement,返回值是String,也就是最后要执行的SQL。 这个方法主要是根据实体类的列重新拼接一个更新的SQL(除了被忽略的注解标注的字段),具体代码上都有解释。
最后把这个UpdateByPrimaryKeyWithIgnoreFieldMapper拓展到我们的BaseMapper上就可以了:


/**
 * @author changjiu.wang
 * @title: BaseMapper
 * @date 2019/6/11 14:04
 */
public interface BaseMapper<T> extends Mapper<T>, ConditionMapper<T>, IdsMapper<T>, InsertListMapper<T>, UpdateByPrimaryKeyWithIgnoreFieldMapper<T> {
}

原理的话下一篇文章再给出吧