Reputation: 91
I have two entities, which we'll call A and B. B always has A as a parent with a ManyToOne relation. However, I need A to have a OneToOne relation with the latest record inserted in table B. This is because I need to save multiple versions of B but 99% of the time will only need to use the most recent one.
This looks something like this:
@Data
@Entity
public class A {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Setter(AccessLevel.NONE)
private Long id;
/* Properties
...
*/
@OneToOne(optional = false)
private B latest;
}
@Data
@Entity
public class B {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Setter(AccessLevel.NONE)
private Long id;
/* Properties
...
*/
@Column(nullable = false)
private Date lastModified;
@ManyToOne(optional = false)
private A parent;
}
Now, the issue at hand is that I cannot seem to persist these entities as one always appears to be transient:
latest
references B, yet B is not persisted.parent
references A, yet A is not persisted.Attempting to do so results in:
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : B.parent -> A
I tried wrapping the code responsible for persisiting them in a @Transactional
method but the same happens:
@Transactional
public void saveAB(A parent, B child) {
parent.setLatest(child);
child.setParent(parent);
Arepository.save(parent);
Brepository.save(child);
}
I also thought of disregarding the OneToOne relation from A to B, instead having latest
as a transient @Formula
field which would query B to take the most recent record. However, @Formula
seems to be limited to primitives, not full entities.
What would be the proper way to do this with JPA? Am I approaching this the wrong way?
Upvotes: 0
Views: 344
Reputation: 91
The solution was to apply @JoinFormula as explained here.
@Data
@Entity
public class A {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Setter(AccessLevel.NONE)
private Long id;
/* Properties
...
*/
@ManyToOne
@JoinFormula(value = "(SELECT b.id FROM b " +
"WHERE b.id = id ORDER BY b.lastModified DESC LIMIT 1)")
private B latest;
}
Then on B:
@ManyToOne(optional = false)
private A parent;
Upvotes: 0
Reputation: 81970
Since A
and B
depend on each other they should probably be considered a single aggregate with A
being the aggregate root.
This means you'd have only an ARepository
and also CascadeType.ALL
on the relationships.
Upvotes: 0