在开发里面时长会用到ID自增。目前大多数的ID自增都是依赖数据库实现的,不同数据库实现ID自增都有或多或少的差异。这样就导致程序在迁移不同数据库的时候需要针对ID自增这里做特殊处理。为了少做处理,这里直接用程序来实现自增ID。不依赖数据库特性。

具体思路是通过注解@GeneratedValue和@GenericGenerator 来自定义一个主键生成策略 SeqPKGenerator 实现 IdentifierGenerator,在主键生成方法generate()上自己定义主键生成规则。

 里面讲的很详细,不过介绍的都是依赖数据库的ID自增方式。下面说一下自定义主键生成策略。

首先定义一个class 作为主键生成器SeqPKGenerator,需要实现IdentifierGenerator并实现 generate()方法。在generate()方法里面去写自定义的主键生成策略。这里我们写的生成规则是通过参数 Object获取到这个类的Table注解,获取到对应实体的表名称。在通过查找注解


@javax.persistence.Id或@org.springframework.data.annotation.Id


来查找自增列所对应的字段名称。 然后通过mybatis查询自增列的最大值然后获得的最大值加一就是下一个ID。具体实现方法如下

@Component
public class SeqPKGenerator implements IdentifierGenerator {
    
    protected static final String COL_ALIAS = "maxidval";

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object)
        throws HibernateException {
        //获取table注解,通过table获取表名
        Table table = object.getClass().getAnnotation(Table.class);
        if (ObjectUtils.isEmpty(table) || ObjectUtils.isEmpty(table.name()))
            return PKGenerator.getPK();   //如果获取不到Table则返回随机ID
        // 获取表名
        String tablename = table.name();
        DynamicTableService dynamicTableService = SpringContextUtils.getBean(DynamicTableService.class);
        // 获取实体中的自增列,这里认为@javax.persistence.Id或@org.springframework.data.annotation.Id修饰的属性就是我们要的
        Field pkCol = getPKCol(object.getClass());
        if (ObjectUtils.isEmpty(pkCol) || ObjectUtils.isEmpty(pkCol.getName()))
            return PKGenerator.getPK();
        String pk = pkCol.getName();
        // 获取自增列最大值, 用mybatis查询最大ID,dynamicTableService.selectByConditions方法可以自定义。参数是传入表名和自增列名
        List<Map<String, Object>> resultList = dynamicTableService.selectByConditions(new DynamicSelectDTO(tablename,
            CollectionUtil.fastList("max(" + pk + ") " + COL_ALIAS), CollectionUtil.fastList()));
        // 下面各种找不到值的情况统一返回1
        if (ObjectUtils.isEmpty(resultList))
            return 1L;
        Map<String, Object> map = resultList.get(0);
        if (ObjectUtils.isEmpty(map))
            return 1L;
        Object maxid = map.get(COL_ALIAS);
        if (ObjectUtil.isEmpty(maxid))
            return 1L;
        // 找到了+1返回
        Long idLong = ObjectUtil.toLong(maxid);
        return ++idLong;
    }
    
    /**
     * @description 获取实体中的自增列,这里认为@javax.persistence.Id或@org.springframework.data.annotation.Id修饰的属性就是我们要的
     * @param clz
     * @return
     */
    protected Field getPKCol(Class<?> clz) {
        //获取实体的所有元素
        Field[] allFields = ReflectUtil.getAllFields(clz);
        for (Field field : allFields) {
            //如果字段有javax.persistence.Id或者org.springframework.data.annotation.Id注解则返回此字段
            if (!ObjectUtils.isEmpty(field.getAnnotation(javax.persistence.Id.class)) || 
                !ObjectUtils.isEmpty(field.getAnnotation(org.springframework.data.annotation.Id.class)))
                return field;
        }
        return null;
    }
}

SeqPKGenerator 定义好之后就在需要自增主键的字段上面加上注解@GeneratedValue和@GenericGenerator 就可以了。

@GeneratedValue(generator = "mySeqPk")
@GenericGenerator(name = "mySeqPk", strategy = "com.unis.doc.core.pk.SeqPKGenerator")

注意的是 @GeneratedValue 里面的generator  要和 @GenericGenerator 里面的name 相同,@GenericGenerator里面的strategy  要和自己定义的主键生成器SeqPKGenerator一致,需要包括包名。

这里我们定义了一个自增实体的基类BaseSeqDO,需要用到自增ID的继承这个类就可以了。

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseSeqDO implements Serializable {

    /**
     * 主键
     */
    @Id
    @GeneratedValue(generator = "mySeqPk")
    @GenericGenerator(name = "mySeqPk", strategy = "com.unis.doc.core.pk.SeqPKGenerator")
    @Column(name = "id", nullable = false, length = 20)
    @ApiModelProperty(value = "主键")
    private Long id;

    /**
     * 创建人
     */
    // @CreatedBy
    @Column(name = "createdby", nullable = true, length = 100)
    @ApiModelProperty(value = "创建人")
    private Long createdby;

    /**
     * 创建时间
     */
//    @CreatedDate
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Column(name = "createdtime")
    @ApiModelProperty(value = "创建时间")
    private Date createdtime;

    /**
     * 更新人
     */
    // @LastModifiedBy
    @Column(name = "lastmodifiedby", nullable = true, length = 20)
    @ApiModelProperty(value = "更新人")
    private Long lastmodifiedby;

    /**
     * 更新时间
     */
    @LastModifiedDate
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Column(name = "lastmodifiedtime")
    @ApiModelProperty(value = "更新时间")
    private Date lastmodifiedtime;

    /**
     * 逻辑删除标志位
     */
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Column(name = "deletetime")
    @ApiModelProperty(value = "逻辑删除标志位")
    private Date deletetime;

    public BaseSeqDO(Long id) {
        super();
        this.id = id;
    }

    public BaseSeqDO(Long id, Long createdby, Date createdtime, Long lastmodifiedby, Date lastmodifiedtime) {
        super();
        this.id = id;
        this.createdby = createdby;
        this.createdtime = createdtime;
        this.lastmodifiedby = lastmodifiedby;
        this.lastmodifiedtime = lastmodifiedtime;
    }

    public BaseSeqDO(Long id, Date deletetime, Long createdby, Date createdtime, Long lastmodifiedby,
        Date lastmodifiedtime) {
        super();
        this.id = id;
        this.createdby = createdby;
        this.createdtime = createdtime;
        this.lastmodifiedby = lastmodifiedby;
        this.lastmodifiedtime = lastmodifiedtime;
    }

    public BaseSeqDO() {
        super();
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName()+" : "+JSON.toJSONString(this);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getCreatedby() {
        return createdby;
    }

    public void setCreatedby(Long createdby) {
        this.createdby = createdby;
    }

    public Date getCreatedtime() {
        return createdtime;
    }

    public void setCreatedtime(Date createdtime) {
        this.createdtime = createdtime;
    }

    public Long getLastmodifiedby() {
        return lastmodifiedby;
    }

    public void setLastmodifiedby(Long lastmodifiedby) {
        this.lastmodifiedby = lastmodifiedby;
    }

    public Date getLastmodifiedtime() {
        return lastmodifiedtime;
    }

    public void setLastmodifiedtime(Date lastmodifiedtime) {
        this.lastmodifiedtime = lastmodifiedtime;
    }

    public Date getDeletetime() {
        return deletetime;
    }

    public void setDeletetime(Date deletetime) {
        this.deletetime = deletetime;
    }

}

总结:通过实现IdentifierGenerator的方法自定义注解,可以摆脱数据库的约束,不过效率上会有一些欠缺。如果并发量比较高,就需要考虑主键生成规则的并发效率。