【问题标题】:JPA: How to handle versioned entities?JPA:如何处理版本化实体?
【发布时间】:2018-03-21 13:07:40
【问题描述】:

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

@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 字段,因为它是主键的一部分。这会产生一个问题,因为引用可能很快就过时了。

方法可能取决于Userid。但是如果我尝试这个,JPA 会告诉我,我喜欢访问一个不是 Entity 的字段:

@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, ...

}

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

编辑

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

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

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

  @OneToMany
  @JoinColumn(name = "USER_ID", referencedColumnName = "ID")
  private List<User> users;

  @Column(nullable = false)
  private boolean married;

  public User getUser() {
    return users.stream().reduce((a, b) -> {
      if (a.getLastModified().isAfter(b.getLastModified())) {
        return a;
      }
      return b;
    }).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
  }

  // Constructors, Getter, Setter, ...

}

【问题讨论】:

  • “但是如果我尝试这个,JPA 会告诉我,我喜欢访问一个不是实体的字段。” - 您在这里如何/尝试了什么?
  • 依赖Userid。我用代码示例更新了我的问题。
  • 为什么f...你在主键中包含版本????
  • 正如我在要求中所说:数据可能永远不会改变,因此只允许插入。
  • @Gab 因为否则他需要添加另一个人工密钥。

标签: java oracle jpa eclipselink or-mapper


【解决方案1】:

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

【讨论】:

  • Hibernate 调用该功能"Envers"
  • 感谢您的解决方案,不幸的是,这违反了永不更新政策(通过设置end_date,当条目过期时)。否则,这真的可以解决我的问题。
  • 结束日期使查询和判断项目何时被删除(软删除)变得更容易,如果无法修改行,我不确定您将如何处理。可以对 HistoryPolicy 进行子类化,以使功能更接近您想要的。
【解决方案2】:

这里是逻辑上的 1:1 关系,由于版本控制,它变成了技术上的 1:n 关系。

你基本上有三个选择:

  1. 清洁 JPA 方式:声明从用户到“其他对象”的“反向”@ManyToOne 关系,并确保在创建新的User 记录时始终处理它。
  2. 'Hack-ish' 方式:在“其他对象”中声明@OneToMany 关系并强制它使用一组特定的列进行连接,使用@JoinColumn。这样做的问题是 JPA 总是期望对连接列的唯一引用,以便 读取 UserDetail 加上引用的 User 记录应该工作,而 写入 UserDetail应该级联到User 以避免不必要的/未记录的影响。
  3. 只需将用户的UUID 存储在“其他对象”中,并在需要时自行解析引用。

你的问题中添加的代码是错误的:

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

更正确,虽然不是你想要的结果,会是

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

但这不起作用,因为正如我上面所说,每个UserDetail 可能有多个用户记录,所以你需要一个@OneToMany 关系,用Collection&lt;User&gt; 表示。


另一个“干净”的解决方案是引入一个具有 1:1 基数 w.r.t 的人工实体。到您可以参考的逻辑User,例如

@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;

}

这样,您可以轻松地从用户导航到详细信息并返回。

【讨论】:

  • 感谢您的回答。我尝试了您的第二个解决方案但失败了(请参阅更新的答案)。您能否回顾一下并指出您能找到的任何缺陷?
【解决方案3】:

我找到了一个解决方案,虽然不是很令人满意,但很有效。我创建了一个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,但只要我坚持 no setter 策略,它应该可以很好地工作。

【讨论】:

    猜你喜欢
    • 2017-03-22
    • 2019-12-31
    • 2020-02-12
    • 1970-01-01
    • 1970-01-01
    • 2016-09-17
    • 2013-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多