JPA:如何处理版本控制的实体?
JPA: How to handle versioned entities?
我将实体的版本控制作为其主键的一部分。版本控制是通过最后一次修改的时间戳完成的:
@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
字段,因为它是主键的一部分。这会产生一个问题,因为引用可能很快就会过时。
方法可能取决于 User
的 id
。但是如果我尝试这个,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, ...
}
这里是逻辑 1:1 关系,由于版本控制,它变成了技术 1:n 关系。
你基本上有三个选择:
- 清洁 JPA 方式:声明从用户到 "other object" 的 'inverse'
@ManyToOne
关系,并确保在创建新的 User
记录时始终处理它。
- 'Hack-ish' 方式:在 "other object" 中声明一个
@OneToMany
关系,并强制它使用一组特定的列进行使用 @JoinColumn
的连接。这个问题是 JPA 总是期望对连接列的唯一引用,以便 reading UserDetail
加上引用的 User
记录应该工作,而 写 UserDetail
应该不级联到User
以避免unwanted/undocumented影响。
- 只需将用户的
UUID
存储在 "other object" 中,并在需要时自行解析引用。
你问题中添加的代码有误:
@JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
更正确的是
@JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
但这行不通,因为正如我上面所说,每个 UserDetail
可能有多个用户记录,因此您需要一个 @OneToMany
关系,由Collection<User>
.
另一个 'clean' 解决方案是引入一个具有 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;
}
这样,您可以轻松地从用户导航到详细信息并返回。
我找到了一个解决方案,虽然不是很令人满意,但确实有效。我创建了一个 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
,但只要我坚持不 setter 政策,它应该工作得很好。
您似乎需要的内容似乎在历史记录中 table,以跟踪更改。请参阅 https://wiki.eclipse.org/EclipseLink/Examples/JPA/History,了解 EclipseLink 如何在使用 normal/traditional JPA 映射和用法时为您处理此问题。
我将实体的版本控制作为其主键的一部分。版本控制是通过最后一次修改的时间戳完成的:
@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
字段,因为它是主键的一部分。这会产生一个问题,因为引用可能很快就会过时。
方法可能取决于 User
的 id
。但是如果我尝试这个,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, ...
}
这里是逻辑 1:1 关系,由于版本控制,它变成了技术 1:n 关系。
你基本上有三个选择:
- 清洁 JPA 方式:声明从用户到 "other object" 的 'inverse'
@ManyToOne
关系,并确保在创建新的User
记录时始终处理它。 - 'Hack-ish' 方式:在 "other object" 中声明一个
@OneToMany
关系,并强制它使用一组特定的列进行使用@JoinColumn
的连接。这个问题是 JPA 总是期望对连接列的唯一引用,以便 readingUserDetail
加上引用的User
记录应该工作,而 写UserDetail
应该不级联到User
以避免unwanted/undocumented影响。 - 只需将用户的
UUID
存储在 "other object" 中,并在需要时自行解析引用。
你问题中添加的代码有误:
@JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
更正确的是
@JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
但这行不通,因为正如我上面所说,每个 UserDetail
可能有多个用户记录,因此您需要一个 @OneToMany
关系,由Collection<User>
.
另一个 'clean' 解决方案是引入一个具有 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;
}
这样,您可以轻松地从用户导航到详细信息并返回。
我找到了一个解决方案,虽然不是很令人满意,但确实有效。我创建了一个 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
,但只要我坚持不 setter 政策,它应该工作得很好。
您似乎需要的内容似乎在历史记录中 table,以跟踪更改。请参阅 https://wiki.eclipse.org/EclipseLink/Examples/JPA/History,了解 EclipseLink 如何在使用 normal/traditional JPA 映射和用法时为您处理此问题。