JPA:生成非 pk 唯一且随机的字母数字值

JPA: generate non pk unique and random alphanumeric value

我想在不使用主键的情况下唯一标识一个实体。所以我想到了生成一个唯一的随机值。此外,值必须易于阅读/手动复制,预计长度为 6 或 7 个字符。

设计

我的实体A:

public class A{
    // ...
    @Column(name="value", unique=true, nullable=false, insertable=false, updatable=false)
    private String value;
    // ...

    public String getValue(){
        return value;
    }
    protected void setValue(String value){
        this.value = value;
    }
}

在数据库中表示为 table

CREATE TABLE IF NOT EXISTS schema.mytable{
    -- ...
    value TEXT NOT NULL DEFAULT generate_unique_value_for_mytable(),
    -- ...
    CONSTRAINT "un_value" UNIQUE (value),
    -- ...
}

我想让数据库处理这个然后获取值...

问题

根据当前设计,value 已在数据库中正确生成,但是当 JPA 获取 A 实体时,value 字段为空。

  1. 我无法删除 insertable=false 否则,它将违反 NOT NULL 约束
  2. 如果我删除 insertable=false 并放置一些虚拟数据,该数据将覆盖 generate_unique_value_for_mytable 生成的值()
  3. 如果我删除 Column 注释中的所有内容,我可以保存 A 实体,但 value 仍然是空的

丑陋的解决方案

我找不到证据,但看起来让数据库生成值不是个好主意。对于由序列生成的非主键字段,我确实有同样的问题:我无法从数据库中获取值。

所以我丑陋的解决方案是装饰负责A实体的EJB的create()方法:

public class Aejb{

    public void create(A entity){

        // method kind of ensures randomness
        String value = MyUtil.generateRandomValue();
        A isThereAnyoneHere = findByValue(value);

        while(isThereAnyoneHere != null){
            String value = MyUtil.generateRandomValue();
            isThereAnyoneHere = findByValue(value);
        }

        // unicity is ensured
        entity.setValue(value);

        em.persist(entity);
    }

}

问题

  1. 我可以从 JPA 实体中获取数据库生成的非主键值吗?值可以由函数或序列生成。
  2. 是否有比我丑陋的解决方法更优雅的解决方案来提供唯一且随机的值?
  1. Yes.You 没有提到你的数据库,但是有可能 Oracle 将 return 通过触发器插入的值,并且有 Eclipselink 在您的模型中获取此值 - 请参阅 https://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_returninsert.htm
  2. 使用将要执行的@PrePersist 方法设置值 在插入实体之前,但如果您依赖一个或多个数据库查询,您将 运行 陷入性能问题,因为插入一个新的 A 会很昂贵。您可能只是插入随机值并处理偶尔发生的冲突,然后选择一些重叠可能性较小的随机值,例如 UUID。

如果我理解正确,@Generated 注释应该可以解决问题。此批注设置来自数据库 DEFAULT 字段值的值。

示例:

@Generated(GenerationTime.INSERT)
@Column(name="value", unique=true, nullable=false, insertable=false, updatable=false)
private String value;

但是有一个缺点:如果您决定在 Java 中设置字段的值,Hibernate 会使用数据库中 DEFAULT 的结果覆盖它。

自行回答以将问题标记为已关闭


最终解决方案

我们终于找到了

的组合
  • 存储过程:数据库会生成值。该过程还确保该值在 table
  • 中是唯一的
  • 命名查询: 获取过程生成的值。我没有使用 NamedStoredProcedures 因为我们使用的是 PostgreSQL 和 PostgreSQL JDBC 驱动程序不支持名称参数,这引发了一些问题。

使用此配置,EJB 肯定最多有一个数据库调用来获取请求的值。

对其他答案的回应

这里总结一下其他答案反馈,供自己参考和下一位读者:

  • Oracle 触发器:我们正在使用 PostgreSQL :(
  • UUID:我们的约束条件是让我们的唯一随机代码易于人类阅读。假定最终用户能够手动重写它。因此,我们不能有像 UUID 这样的长字符串。
  • PrePersist:其他业务动作发生在同一事务中的代码生成之后,这意味着在发生冲突时需要重做这些动作。我对管理 JPA 异常(事务范围等)不是很有信心,所以我不想玩它。
  • @Generated:这是 Hibernate 特有的功能。我们正在使用 EclipseLink
  • 数据库触发器:如果代码纯粹是在数据库级别生成的,我遇到了同样的问题,没有获取值:值在数据库级别正确生成,但实体将值为 null