Reputation: 29300
I have a parent -> child relationship, with a @ManyToOne
/ @OneToMany
relationship.
I'm processing updates to the parent, in code that goes roughly like this:
When running through, I find the following sequence occurs
DataIntegrityViolationException
is thrown, as the child from the 2nd update is INSERTed again.I assume that this must be related to the fact the parent is cached, rather than returned from the database. I'm not sure what the correct process here should be.
Relevant information:
@Transactional
. (A lesson hard-learnt)Whats the correct way to handle this - specifically, to avoid having to load the Parent from the db every time, while still having child entities tracked correctly?
Code example shown below.
@Entity // Parent
class Fixture {
@OneToMany(cascade=CascadeType.ALL, mappedBy="fixture", fetch=FetchType.EAGER) @Getter @Setter
@MapKey(name="instrumentPriceId")
private Map<String,Instrument> instruments = Maps.newHashMap();
private Instrument addInstrument(Instrument instrument)
{
instruments.put(instrument.getInstrumentPriceId(), instrument);
instrument.setFixture(this);
log.info("Created instrument {}",instrument.getInstrumentPriceId());
return instrument;
}
/**
* Returns an instrument with the matching instrumentId.
* If the instrument does not exist, it is created, appended to the internal collection,
* and then returned.
*
* This method is guaranteed to always return an instrument.
* This method is thread-safe.
*
* @param instrumentId
* @return
*/
public Instrument getInstrument(String instrumentId)
{
if (!instruments.containsKey(instrumentId))
{
addInstrument(new Instrument(instrumentId));
}
return instruments.get(instrumentId);
}
}
@Entity // Child
public class Instrument {
@Column(unique=true)
@Getter @Setter
private String instrumentPriceId;
@ManyToOne(optional=false)
@Getter @Setter @JsonIgnore
private Fixture fixture;
public Instrument(String instrumentPriceId)
{
this.instrumentPriceId = instrumentPriceId;
}
}
And, the update processor code:
class Processor {
@Autowired
@Qualifier("FixtureCache")
private Ehcache fixtureCache;
@Autowired
private FixtureRepository fixtureRepository;
void update(String fixtureId, String instrumentId) {
Fixture fixture = getFixture(fixtureId);
// Get the instrument, creating it & appending
// to the collection, if it doesn't exist
fixture.getInstrument(instrumentId);
// do some updates...ommitted
fixtureRepository.save(fixture);
fixtureCache.put(new Element(fixtureId, fixture));
}
/**
* Returns a fixture.
* Returns from the cache first, if present
* If not present in the cache, the db is checked.
* Finally, if the fixture does not exist, a new one is
* created and returned
*/
Fixture getFixture(String fixtureId) {
Fixture fixture;
Element element = fixtureCache.get(fixtureId);
if (element != null)
{
fixture = element.getValue();
} else {
fixture = fixtureRepostiory.findOne(fixtureId);
if (fixture == null)
{
fixture = new Fixture(fixtureId);
}
}
return fixture;
}
}
Upvotes: 1
Views: 720
Reputation: 29300
The answer to this was frustratingly simple.
In the update
method, I was ignoring the result of the save()
operation.
Often, this is fine, if you're not planning on using object again. (which is common, as you save right at the end of your unit of work).
However, as I was continuing to use my 'parent' again, I needed to observe the returned value:
So this:
fixtureRepository.save(fixture);
fixtureCache.put(new Element(fixtureId, fixture));
becomes this:
fixture = fixtureRepository.save(fixture);
fixtureCache.put(new Element(fixtureId, fixture));
Upvotes: 2