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

时间:2017-09-13 17:00:11

标签: postgresql jpa eclipselink jpa-2.0

我想在不使用主键的情况下唯一地标识实体。所以我想到了生成一个独特的随机值。此外,值必须易于阅读/手动复制,预计长度为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;
    }
}

由表格

表示在数据库中
CREATE TABLE IF NOT EXISTS schema.mytable{
    -- ...
    value TEXT NOT NULL DEFAULT generate_unique_value_for_mytable(),
    -- ...
    CONSTRAINT "un_value" UNIQUE (value),
    -- ...
}

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

问题

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

  1. 我无法删除 insertable = false ,否则会点击 NOT NULL 约束
  2. 如果我删除 insertable = false 并放入一些虚拟数据,则数据会覆盖 generate_unique_value_for_mytable()
  3. 生成的值
  4. 如果我删除了列注释中的所有内容,我可以保存A实体,但仍为空
  5. 丑陋的解决方案

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

    所以我丑陋的解决方案是装饰负责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. 是否有比我丑陋的解决方案更优雅的解决方案,以提供独特的随机值?

3 个答案:

答案 0 :(得分:1)

  1. 是的。您还没有提到您的数据库,但有可能 Oracle返回通过触发器插入的值,并且具有 Eclipselink在您的模型中获取此值 - 请参阅 https://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_returninsert.htm
  2. 使用将执行的@PrePersist方法设置值     在插入实体之前,如果您依赖于一个或多个数据库查询,则会遇到性能问题,因为插入新的A会很昂贵。您可能只需插入随机值并处理偶然的​​冲突,并选择一些重叠的可能性较小的随机数,例如UUID。

答案 1 :(得分:1)

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

示例:

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

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

答案 2 :(得分:0)

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

最终解决方案

我们终于选择了

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

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

对其他答案的回应

以下是自我参考和下一位读者的其他答案反馈摘要:

  • Oracle触发器:我们正在使用PostgreSQL :(
  • UUID :我们的约束是让我们独特的随机代码具有人类可读性。假设最终用户能够手动重写它。因此,我们不能有一个长字符串,例如UUID。
  • PrePersist :其他业务操作发生在同一事务中的代码生成之后,这意味着在发生冲突时需要重做这些操作。我对管理JPA异常(事务范围等)不太自信,所以我不想玩它。
  • @Generated :这是Hibernate特有的功能。我们正在使用EclipseLink
  • 数据库触发器:如果代码纯粹是在数据库级别生成的,那么我遇到了无法获取值的相同问题:值正确生成为数据库级别,但实体的值为{ {1}}