JPA:如何处理版本化实体?

时间:2017-10-10 07:56:51

标签: java oracle jpa eclipselink or-mapper

我对实体进行了版本控制,作为其主键的一部分。版本控制是通过上次修改的时间戳完成的:

@Entity
@Table(name = "USERS")
@IdClass(CompositeKey.class)
public class User {
  @Column(nullable = false)
  private String name;

  @Id
  @Column(name = "ID", nullable = false)
  private UUID id;

  @Id
  @Column(name = "LAST_MODIFIED", nullable = false)
  private LocalDateTime lastModified;

  // Constructors, Getters, Setters, ...

}

/**
 * This class is needed for using the composite key.
 */
public class CompositeKey {
  private UUID id;
  private LocalDateTime lastModified;
}

UUID会自动转换为数据库的String并返回模型。 LocalDateTime也是如此。它会自动转换为Timestamp并返回。

我的应用程序的一个关键要求是:数据可能永远不会更新或被删除,因此任何更新都会产生一个年轻lastModified的新条目。此要求已满足上面的代码和工作正常,直到这一点。

现在出现问题部分:我希望另一个对象引用用户。由于版本控制,这将包括lastModified字段,因为它是主键的一部分。这会产生一个问题,因为引用可能会很快过时。

一种方法可能取决于id的{​​{1}}。但是,如果我试试这个,JPA告诉我,我想访问一个不是User的字段:

Entity

解决我的困境的正确方法是什么?

修改

我得到了JimmyB的建议,我尝试过但也失败了。我在这里添加了失败的代码:

@Entity
@Table(name = "USER_DETAILS")
public class UserDetail {

  @Id
  @Column(nullable = false)
  private UUID id;

  @OneToOne(optional = false)
  @JoinColumn(name = "USER_ID", referencedColumnName = "ID")
  private UUID userId;

  @Column(nullable = false)
  private boolean married;

  // Constructors, Getter, Setter, ...

}

3 个答案:

答案 0 :(得分:0)

这里有一个合乎逻辑的1:1关系,由于版本控制,它成为技术上的1:n关系。

您基本上有三种选择:

  1. 清除JPA方式:声明反向' @ManyToOne从用户到"其他对象&#34}的关系并确保在创建新的User记录时始终处理它。
  2. '哈克肥胖型'方式:在"其他对象"中声明@OneToMany关系并使用@JoinColumn强制它使用一组特定的列进行连接。这样做的问题是JPA总是期望连接列上的唯一引用,以便读取 UserDetail加上引用的User记录应该有效,而 UserDetail 级联到User以避免不受欢迎/未记录的影响。
  3. 只需将用户UUID存储在"其他对象"并在需要时自行解决参考。
  4. 你问题中添加的代码是错误的:

    @JoinColumn(name = "USER_ID", referencedColumnName = "ID")
    private UUID userId;
    

    更正确,虽然没有你想要的结果,但

    @JoinColumn(name = "USER_ID", referencedColumnName = "ID")
    private User user;
    

    这不会起作用,因为正如我上面所说,每UserDetail个用户记录可能不止一个,因此您需要@OneToMany这里的Collection<User>关系,由User表示。

    另一个&#39;清洁&#39;解决方案是引入一个具有1:1基数w.r.t的人工实体。到您可以引用的逻辑@Entity public class UserId { @Id private UUID id; @OneToMany(mappedBy="userId") private List<User> users; @OneToOne(mappedBy="userId") private UserDetail detail; } @Entity public class User { @Id private Long _id; @ManyToOne private UserId userId; } @Entity public class UserDetail { @OneToOne private UserId userId; } ,例如

    {{1}}

    通过这种方式,您可以轻松地从用户导航到详细信息并返回。

答案 1 :(得分:0)

我找到了一个解决方案,但这并不令人满意,但有效。我创建了一个UUID字段userId,它没有绑定到Entity,并确保它只在构造函数中设置。

@Entity
@Table(name = "USER_DETAILS")
public class UserDetail {

  @Id
  @Column(nullable = false)
  private UUID id;

  @Column(nullable = false)
  // no setter for this field
  private UUID userId;

  @Column(nullable = false)
  private boolean married;

  public UserDetail(User user, boolean isMarried) {
    this.id = UUID.randomUUID();
    this.userId = user.getId();
    this.married = isMarried;
  }

  // Constructors, Getters, Setters, ...

}

我不喜欢这样一个事实,即我不能依赖数据库来同步userId,但只要我坚持不设置策略,就应该可以很好地工作。

答案 2 :(得分:0)

您似乎需要的内容似乎位于历史记录表的行上,以跟踪更改。请参阅https://wiki.eclipse.org/EclipseLink/Examples/JPA/History,了解EclipseLink如何在使用普通/传统JPA映射和使用时为您处理此问题。