复合键

时间:2015-04-27 18:22:29

标签: hibernate postgresql jpa

让我们说我想建模一个问题跟踪器,我的模型由两个实体组成:Repository和Issue。存储库可能包含几个问题。所以你得到大约以下的JPA类:

@Entity
@Table(name="repository")
public class Repository {

    @Id
    @GeneratedValue 
    private Integer id;

    private String name;

    // Getters and setters
}

对于问题类:

@Entity
@Table(name="issue")
@IdClass(Issue.IssuePk)
public class Issue {

   static class IssuePk implements Serializable {

        private Repository repository;
        private Integer issueNumber;
        // Getters, setters, equals and hashcode

    }

    @Id
    @ManyToOne
    private Repository repository;

    @Id
    private Integer issueNumber;

    // Getters and setters
}

现在我想要以类似自动增量的方式生成问题编号,然后在存储库本地生成。我看到了各种选择,但我不确定什么是最好的。

在实例化问题时手动计算ID

不使用任何自动生成,只需在持久化之前设置值。因此:查询存储库的问题,计算最大发行号(如果有),增量,设置和持久。这感觉有点容易出错,因为如果你要从代码的其他部分实例化问题,你需要牢记这一点。

Issue issue = new Issue();
issue.setRepository(repository);
// For example, assuming this is right for the context now:
issue.setIssueNumber(repository.getIssues().size() + 1);

(显然,可以对此进行重构,以便对此问题编号生成进行重复数据删除,但这不会阻止null问题persisted上的问题EntityManger

JPA生命周期事件

使用JPA生命周期事件,挂钩@PrePersist并在那里做同样的事情。这样做的好处是可以自动调用它,而不是在代码库中复制。

// On the issue entity
@PrePersist
void setIssueNumberOnPersist() {
     if(getIssueNumber() == null) {
          setIssueNumber(getRepository().getIssues().size() + 1);
      }
}

然而,它似乎与JPA限制之一相冲突:

  

为避免与触发实体生命周期事件(仍在进行中)的原始数据库操作发生冲突,回调方法不应调用EntityManager或Query方法,也不应访问任何其他实体对象。

使用数据库触发器

通过问题表上的触发器设置issueId值,并让JPA在插入后更新它的值。这种方法的一个缺点是,如果切换数据库,必须修补此触发器。

我现在不是真的在编写触发器,但我认为它大致是:

before insert on issue
    select max(issue_id) as val from issue where repository = issue.repository
    issue_id = val + 1
end

或使用缓存值:

before insert on issue
    select next_issue_id as val from repository where id = issue.repository_id
    issue_id = val
    update repository set next_issue_id = val + 1 where id = issue.repository_id
end

使用Hibernate IdentifierGenerator

似乎IdentifierGenerator也可以做这项工作。但是,它需要通过查询与数据库进行交互,我认为这会破坏与不同数据库和架构更改的兼容性。

 public class IssueIdGenerator implements IdentifierGenerator {

     public Serializable generate(SessionImplementor session, Object object)
             throws HibernateException {

         Connection connection = session.connection();
         try {

             PreparedStatement ps = connection
                     .prepareStatement("... query ...");
             // Calculate the next issueId
             return issueId;
         } catch (SQLException e) {
             log.error(e);
             throw new HibernateException(
                     "Unable to generate Stock Code Sequence");
         }
         return null;
     }
 }

我缺少的选项

如果我错过了一个选项,我很高兴听到!

什么是最佳选择?

1 个答案:

答案 0 :(得分:1)

另一种选择是缓存附加到存储库的问题数量。

因此,您创建一个字段issueCount,它将在构造函数中初始化,方法是从数据库中提取数量,然后在创建问题时相应地递增。

请记住,这必须同步,因为您不希望最终遇到具有相同ID的2个问题。

根据各种stackoverflow问题和论坛帖子,这似乎是一个已知问题,Hibernate建议开发人员在他们的项目中创建逻辑,而不是依赖于Hibernate的@GeneratedValue