I have a situation where I need to persist a hierarchy of items:
A Call has a Quote and a Quote has several QuoteFragments. The QuoteFragments are 'owned' by the parent Quote.
The Quote already exists in the database, but the Call and a QuoteFrament are new (there may be other QuoteFragments in the Quote that already exist)
class Call {
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
@JoinColumn(name="contract_quote_id", nullable=true)
protected Quote quote;
@OneToOne(fetch=FetchType.LAZY, cascade={CascadeType.REFRESH})
@JoinColumn(name="fragment_id", nullable=true)
protected QuoteFragment fragment;
}
class Quote {
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="customer_call_id", nullable=false)
protected Call customerCall;
@OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="parent")
protected Set<QuoteFragment> fragments = new HashSet<>();
}
class QuoteFragment {
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="parent_id", nullable=false)
protected Quote parent;
@Column(name="valid_until", nullable=false)
@Temporal(TemporalType.DATE)
protected Date validUntil;
}
The onetoone/manytoone on the quote/call relationship is because when the quote is first persisted it is associated with that first call. Subsequent calls may refer to the same quote but the quote will always refer to the first call.
My problem: I can't find a way using cascades or separate persist/merge actions to stop extra QuoteFragment copies being persisted to the database.
Here is my current code:
Quote quote = getExistingQuote( ... );
QuoteFragment fragment = new QuoteFragment();
fragment.setParent( quote );
quote.addFragment( fragment );
Call call = new Call();
call.setQuote( quote );
call.setQuoteFragment( fragment );
// doing other config stuff
// later on at the persist stage..
Quote existingQuote = call.getQuote();
call.setQuote( null );
// if attaching a fragment, remove before inserting or it will be inserted twice (once by the call and once by the parent)
QuoteFragment newfrag = call.getQuoteFragment();
call.setQuoteFragment( null );
// This bit should insert the Call record but not the quote/fragment
call = entityManager.persist( call );
call.setQuote( existingQuote );
// This should update any quote details, including persisting the fragment
entityManager.merge( call );
if( newfrag != null ) {
// a new fragment was created - need to set this back to the call and update it (the first update will have created it within the parent quote)
call.setQuoteFragment( newfrag );
entityManager.merge( call );
}
I thought by doing this it would only persist the fragment once, but it creates 3 records!
Can anyone help me wire all these up correctly (ideally with a single db write rather than 3) without creating the duplicates?
I'm using JPA 2.1 with Hibernate 4.3.11
Thanks in advance.