I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
@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;
}
The UUID
is translated automatically into a String
for the database and back for the model. The same goes for the LocalDateTime
. It gets automatically translated to a Timestamp
and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified
. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified
field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id
of the User
. But if I try this, JPA tells me, that I like to access a field, which is not an 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, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
@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, ...
}
I came to a solution, that is not really satisfying, but works. I created a UUID
field userId
, which is not bound to an Entity
and made sure, it is set only in the constructor.
@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, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId
, but as long as I stick to the no setter policy, it should work pretty well.