I am trying to save an entity that has a many-to-many association to another entity and cascade the persistence to the associated entity and create the association using spring data jpa repository.
I can insert the parent entity_a which contains a set of entity_b using entityARepository.save(entityA)
. Spring jpa is taking care of all the inserts needed in the transaction. All the entity_b's get inserted, entity_a's get inserted and the join table in the middle has the association inserted as well. If I update the same entity_a with a new value in, say timestamp column, the same entityARepository.save(entityA)
handles this and does a corresponding update.
The problem happens when there already exists entity_b (which has an association between some entity_a) and I try to insert a new entity_a with the same entity_b. It is many to many so this is how the data model is supposed to be. But instead of updating the existing entity_b during this entityA save() transaction, it tries to do inserts on entity_b and a constraint violation exception on the primary key is thrown.
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (USER1.SYS_C0013494) violated
Error Code: 1
Call: INSERT INTO ENTITY_B (ID, NAME, VALUE, TIME_STAMP) VALUES (?, ?, ?, ?)
bind => [4 parameters bound]
Query: InsertObjectQuery(EntityB [name=shape, value=circle])
The problem is that spring doesn't have update(). It only has save which should handle update if it receives the same primary key. It's not doing that when a new entity_a is saved and has a collection of entity_b, if any entity_b exists, the whole transaction is failing sure to primary key constraint violation of entity_b.
public class EntityA {
@Id
@SequenceGenerator( name = "EntityASeq", sequenceName = "SQ_ENTITY_A", allocationSize = 1, initialValue = 1 )
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "EntityASeq")
@Column(name = "ID")
private Integer id;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinTable(name = "MY_JOINED_TABLE",
joinColumns = {
@JoinColumn(name = "a_id", referencedColumnName = "ID")},
inverseJoinColumns = {
@JoinColumn(name = "b_id", referencedColumnName = "ID")})
private Set<EntityB> attributes;
// These three columns below have a unique constraint together.
@Column(name = "name")
private String name;
@Column(name = "tenant")
private String tenant;
@Column(name = "type")
private String type;
@Column(name = "timestamp")
private Timestamp timestamp;
}
public class EntityB {
@Id
@SequenceGenerator( name = "EntityBSeq", sequenceName = "SQ_ENTITY_B", allocationSize = 1, initialValue = 1 )
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "EntityBSeq")
@Column(name = "ID")
private Integer id;
@ManyToMany(mappedBy = "attributes")
private Set<EntityA> aSet;
// These two columns below have a unique constraint together.
@Column(name = "name")
private String name;
@Column(name = "value")
private String value;
@Column(name = "timestamp")
private Timestamp timestamp;
}
The id for each is generated by default. I also have a unique constraint on a few columns, which means if an EntityB has the same name/value as an existing one in the database, I want to just update the timestamp. That works if entity_a is already in the table and it has the same entity_b's. A and B's timestamp are updated and no error when I persist with entityARepository.save(entityA)
. (I do some checking on the db with findOne because the id is auto generated an not known. So if a name/value exist, I don't try to insert with a new id, I use the same one in the db and it works (similarly with entity_atenant/name/type.
It also works when I persist an existing entity_a with updated entity_b's. So if a new entity_b is associated with entity_a (that exists as an association with a different entity_a), etc, that works and the persistence is working.
The issue again, is just on INSERT of entityA via repo.save() when some entity_b s already exist for other associations. It should be doing:
INSERT INTO entity_a ...
UPDATE entity_b ...
INSERT INTO MY_JOINED_TABLE ...
But it seems like it's doing
INSERT INTO entity_a ...
INSERT INTO entity_b ... -- fails because primary key constraint fails
INSERT INTO MY_JOINED_TABLE ...
EDIT: I tried removing CascadeType.PERSIST but I get an error saying
During synchronization a new object was found through a relationship that was not marked cascade PERSIST: EntityB [name=color, value=blue].
I wanted to try to manually insert/update but I couldn't do that. It wants me to have the EntityA specified with PERSIST because it has associations to the entityB
I tried inserting in the reverse and now I'm having issues inserting from entityB.save() when there already exists some entityA and I'm adding a new entityA to entityB