2

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

@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为数据库的 a 并返回模型的。对于LocalDateTime. 它会自动转换为 aTimestamp并返回。

我的应用程序的一个关键要求是:数据可能永远不会更新或被删除,因此任何更新都会导致新条目的lastModified. 上述代码满足了此要求,并且在此之前可以正常工作。

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

一种方法可能取决于id. User但是如果我尝试这个,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, ...

}
4

3 回答 3

0

您在这里拥有的是逻辑上的 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<User>.


另一个“干净”的解决方案是将一个具有 1:1 基数的人工实体引入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;

}

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

于 2017-10-10T08:17:41.570 回答
0

我找到了一个解决方案,虽然不是很令人满意,但很有效。我创建了一个UUIDfield userId,它没有绑定到 anEntity并确保它仅在构造函数中设置。

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

于 2017-10-10T09:21:09.787 回答
0

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

于 2017-10-10T13:40:23.003 回答