cascade PERSIST during synchronization exception - java
I have a problem when trying to persist new entities. I'm using Eclipselink 2.4.2 as entity manager. My BaseDao class in the store method flushes and refreshes entity after persisting it as new (persist->flush->refresh). All is happening in a single transaction.
My entities look like this (the part I'm concerned about):
TrustEntity {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "trust", cascade = {CascadeType.ALL})
#PrivateOwned
private List<TrustIncentiveRateEntity> trustIncentiveRates;
}
TrustIncentiveRateEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TRUST_ID", nullable = false)
private TrustEntity trust;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "trustIncentiveRate", cascade = {CascadeType.ALL})
#PrivateOwned
private List<TrustIncentiveRateValueEntity> trustIncentiveRateValues;
}
TrustIncentiveRateValueEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TRUST_INCENTIVE_RATE_ID", nullable = false)
private TrustIncentiveRateEntity trustIncentiveRate;
}
I'm creating a new Trust entity, instantiate a TrustIncentiveRateEntity list, create one new element in it, instantiate a TrustIncentiveRateEntity and create one new element in it.
During debugging I could see that all the references, in both ways, are correct.
Now, when I try to persist this here is what happens:
Log from server:
FINE: SELECT SEQ_TRUST.NEXTVAL FROM DUAL
FINE: SELECT SEQ_TRUST_INCENTIVE_RATE.NEXTVAL FROM DUAL
FINE: SELECT SEQ_TRUST_INCENTIVE_RATE_VALUE.NEXTVAL FROM DUAL
FINE: INSERT INTO TRUST (TRUST_ID, ACTION_DATE, ACTION_USER_ID, IS_ACTIVE, CREATION_DATE, CREATION_USER_ID, IS_INCENTIVE_ACTIVE, IS_NO_CREDIT_LIMIT, PROCESS_CURRENT_STATUS, REMARKS, STATUS_INCENTIVE, TRUST_CODE, TRUST_NAME, TRUST_TYPE, UPDATE_DATE, UPDATE_USER_ID, VERSION_OPT_LOCK, STRUCTURAL_ORG_UNIT_SALES_ID, STRUCTURAL_ORG_UNIT_ID, USER_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
bind => [38007, 2013-04-26 09:46:31.582, 1003186, true, 2013-04-26 07:46:34.659, 1003186, true, false, OPEN, null, OPEN, 100058, 741963852, T, null, null, 1, 387, 387, 1003186]
FINE: INSERT INTO TRUST_INCENTIVE_RATE (TRUST_INCENTIVE_RATE_ID, CREATION_DATE, CREATION_USER_ID, EQUIPMENT_SIZE, EXTENDED_EQ_GROUP_ID, RATE_BASIS, UPDATE_DATE, UPDATE_USER_ID, VERSION_OPT_LOCK, TRADE_ID, TRUST_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
bind => [15001, 2013-04-26 07:46:39.862, 1003186, 20, 2, B, null, null, 1, 144001, 38007]
FINE: INSERT INTO TRUST_INCENTIVE_RATE_VALUE (TRUST_INCENTIVE_RATE_VALUE_ID, CREATION_DATE, CREATION_USER_ID, EFFECTIVE_DATE, EXPIRY_DATE, INCENTIVE, STATUS, UPDATE_DATE, UPDATE_USER_ID, VERSION_OPT_LOCK, CURRENCY_CODE, TRUST_INCENTIVE_RATE_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
bind => [14007, 2013-04-26 07:46:39.955, 1003186, 2013-04-26 00:00:00.0, 9999-12-31 00:00:00.0, 12, OPEN, null, null, 1, USD, 15001]
FINE: SELECT TRUST_ID, ACTION_DATE, ACTION_USER_ID, IS_ACTIVE, CREATION_DATE, CREATION_USER_ID, IS_INCENTIVE_ACTIVE, IS_NO_CREDIT_LIMIT, PROCESS_CURRENT_STATUS, REMARKS, STATUS_INCENTIVE, TRUST_CODE, TRUST_NAME, TRUST_TYPE, UPDATE_DATE, UPDATE_USER_ID, VERSION_OPT_LOCK, STRUCTURAL_ORG_UNIT_SALES_ID, STRUCTURAL_ORG_UNIT_ID, USER_ID FROM TRUST WHERE (TRUST_ID = ?)
bind => [38007]
FINE: SELECT TRUST_INCENTIVE_RATE_ID, CREATION_DATE, CREATION_USER_ID, EQUIPMENT_SIZE, EXTENDED_EQ_GROUP_ID, RATE_BASIS, UPDATE_DATE, UPDATE_USER_ID, VERSION_OPT_LOCK, TRADE_ID, TRUST_ID FROM TRUST_INCENTIVE_RATE WHERE (TRUST_ID = ?)
bind => [38007]
FINE: SELECT TRUST_INCENTIVE_RATE_VALUE_ID, CREATION_DATE, CREATION_USER_ID, EFFECTIVE_DATE, EXPIRY_DATE, INCENTIVE, STATUS, UPDATE_DATE, UPDATE_USER_ID, VERSION_OPT_LOCK, CURRENCY_CODE, TRUST_INCENTIVE_RATE_ID FROM TRUST_INCENTIVE_RATE_VALUE WHERE (TRUST_INCENTIVE_RATE_ID = ?)
bind => [15001]
So far so good, but when the transaction is commited by EntityManager I get the following exception:
WARNING: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: my.package.entity.TrustIncentiveRateEntity#1fa1df7.
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.discoverUnregisteredNewObjects(RepeatableWriteUnitOfWork.java:303)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.calculateChanges(UnitOfWorkImpl.java:706)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1498)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.issueSQLbeforeCompletion(UnitOfWorkImpl.java:3151)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:345)
at org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:158)
at org.eclipse.persistence.transaction.JTASynchronizationListener.beforeCompletion(JTASynchronizationListener.java:68)
at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:435)
at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:855)
at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:5136)
at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4901)
at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:2045)
at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1994)
at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:222)
at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88)
at $Proxy241.save(Unknown Source)
Which for me seems strange, like the EM tries to actually store TrustIncentiveRateValueEntity before TrustIncentiveRateEntity and can't see TrustIncentiveRateEntity.
After looking at similar threads I've added CascadeType.PERSIST to the #ManyToOne annotation over trustIncentiveRate field in TrustIncentiveRateValueEntity class. After that the situation looks like this: EM inserts the entities like before AND THEN it gets nextval from SEQ_TRUST_INCENTIVE_RATE and tries to insert TrustIncentiveRateValueEntity again (with the new id, but the rest of the field values remain the same). It results in constrain violation, as I have a unique constrain on cross-section of some of this table columns. Exception, transaction rolled back, I am still sad.
My store method in the BaseDao class:
#TransactionAttribute(TransactionAttributeType.MANDATORY)
public T_ENTITY store(T_ENTITY entity) {
if (!entity.isNewlyCreated()) {
T_ENTITY mergedEntity = em.merge(entity);
flush();
return mergedEntity;
} else {
try {
em.persist(entity);
flush();
refresh();
} catch (RuntimeException exc) {
entity.resetPersistentFlag();
throw exc;
}
return entity;
}
}
But calling the em.persist(entity) directly, without flush/refresh causes the same problem.
The logic of the service call:
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public TrustEntity save(TrustEntity dto) {
TrustEntity trust = trustDao.store(trust);
workflowConversation.triggerWorkflow(); // doesn't do anything to any of the entities when they are freshly created
return trust;
}
Anybody could help me in identifying what could be wrong with this?
You cannot have a OneToMany that uses both a mappedby, making it bidirectional, and a joincolumn marking it as unidirectional. They conflict and are causing you issues.
When you mark a relationship as mappedby, you specify that all information and control of the relationship is on the other side - that includes the joincolumn info. try:
TrustEntity {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "trust", cascade = {CascadeType.ALL})
#PrivateOwned
private List<TrustIncentiveRateEntity> trustIncentiveRates;
}
TrustIncentiveRateEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TRUST_ID", nullable = false)
private TrustEntity trust;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "trustIncentiveRate", cascade = {CascadeType.ALL})
#PrivateOwned
private List<TrustIncentiveRateValueEntity> trustIncentiveRateValues;
}
Related
Bi-directional one to one mapping only working one way
For some reason with this setup, when saving the transaction with a nested ticket. It will create both and then connect the two. However when I use the transaction repository to find the transaction it will have an attached ticket however when I use the ticket repository and find the ticket, it doesn't have the attached transaction. I generated this relationship with jhipster jdl, not sure what is going wrong here. Transaction.java ... #JsonIgnoreProperties(value = { "person", "event", "transaction", "nameTags" }, allowSetters = true) #OneToOne(cascade = CascadeType.ALL) #JoinColumn(unique = true) private Ticket tickets; public Ticket getTickets() { return this.tickets; } public Transaction tickets(Ticket ticket) { this.setTickets(ticket); return this; } public void setTickets(Ticket ticket) { this.tickets = ticket; } ... Ticket.java ... #JsonIgnoreProperties(value = { "tickets", "membershipLevel", "person", "event" }, allowSetters = true) #OneToOne(mappedBy = "tickets") private Transaction transaction; public Transaction getTransaction() { return this.transaction; } public Ticket transaction(Transaction transaction) { this.setTransaction(transaction); return this; } public void setTransaction(Transaction transaction) { if (this.transaction != null) { this.transaction.setTickets(null); } if (transaction != null) { transaction.setTickets(this); } this.transaction = transaction; } ... Edit: Here is the generated SQL output, it is showing here just a normal select wihtout joins from both but transaction has the ticket_id in the transaction table while ticket is doing the same but has no reference and would need to do a join but is not. Hibernate: insert into ticket (cost_per_ticket, count, event_id, person_id, picked_up, id) values (?, ?, ?, ?, ?, ?) Hibernate: insert into transaction (cost_sub_items_purchased, date, donation, event_id, event_donation, generic_sub_items_purchased, membership_level_id, notes, number_of_memberships, person_id, tickets_id, total_amount, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) Hibernate: select transactio0_.id as id1_23_, transactio0_.cost_sub_items_purchased as cost_sub2_23_, transactio0_.date as date3_23_, transactio0_.donation as donation4_23_, transactio0_.event_id as event_i10_23_, transactio0_.event_donation as event_do5_23_, transactio0_.generic_sub_items_purchased as generic_6_23_, transactio0_.membership_level_id as members11_23_, transactio0_.notes as notes7_23_, transactio0_.number_of_memberships as number_o8_23_, transactio0_.person_id as person_12_23_, transactio0_.tickets_id as tickets13_23_, transactio0_.total_amount as total_am9_23_ from transaction transactio0_ Hibernate: select ticket0_.id as id1_22_, ticket0_.cost_per_ticket as cost_per2_22_, ticket0_.count as count3_22_, ticket0_.event_id as event_id5_22_, ticket0_.person_id as person_i6_22_, ticket0_.picked_up as picked_u4_22_ from ticket ticket0_
Why to use Set in OneToMany Mapping in hibernate
I have two tables with a one-to-many relationship. I want to fetch those records and insert into another database which having same table by changing the primary key. My application entity class #Entity #Table(name = "EM_APPLICATION") public class ApplicationTable { #Id private int APPLICATION_ID; #Id private String CUSTOMER_ID; private String LAST_NAME; private String FIRST_NAME; #OneToMany( fetch = FetchType.EAGER,cascade = CascadeType.ALL) #JoinColumns({ #JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID"), #JoinColumn(name = "APPLICATION_ID", referencedColumnName = "APPLICATION_ID") }) private Set<AddressTable> address; //Getters and setters } Address entity class.. #Entity #Table(name="EM_APPL_ADDRESS") public class AddressTable{ #Id private int APPLICATION_ID; #Id private String CUSTOMER_ID; #Id private String ADDRESS_TYPE; //Getters and setters } I have to execute a method for fetching records from DB using hibernate: public void execute(String applId, String customerId) { Session session = HibernateQAUtil.openSession(); Transaction tx = session.beginTransaction(); String hql = "FROM ApplicationTable WHERE CUSTOMER_ID =:CUSTOMER_ID AND APPLICATION_ID =:APPLICATION_ID"; Query query = session.createQuery(hql); query.setParameter("CUSTOMER_ID", customerId); query.setParameter("APPLICATION_ID", Integer.parseInt(applId)); List<ApplicationTable> list = query.list(); tx.commit(); session.close(); ApplicationTable applVO = list.get(0); insertApplication(applVO ); } After fetching the records, I am changing APPLICATION_ID, CUSTOMER_ID and some other columns in address table and after inserting in another database. private void insertApplication(ApplicationTable emApplVO) { applVO.setAPPLICATION_ID(123456); applVO.setCUSTOMER_ID("88888888"); Set<AddressTable> addressSet = emApplVO.getAddress(); for (AddressTable address : addressSet) { address.setAPPLICATION_ID(123456); address.setCUSTOMER_ID("88888888"); address.setZIP(500032); } Session session1 = HibernateUtil.openSession(); Transaction beginTransaction = session1.beginTransaction(); session1.save(emApplVO); beginTransaction.commit(); session1.close(); } Hibernate queries in console log are... (below mentioned queries are too large so copied to some extent only..) Hibernate: select em_applica0_.CUSTOMER_ID as CUSTOMER1_0_,em_applica0_.APPLICATION_ID as APPLICAT2_0_,em_applica0_.ARCHIVE_IND as ARCHIVE8_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=? Hibernate: select address0_.CUSTOMER_ID as CUSTOMER1_0_1_, address0_.APPLICATION_ID as APPLICAT2_0_1_, address0_.ADDRESS_TYPE as ADDRESS3_1_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=? Hibernate: insert into EM_APPLICATION (CUSTOMER_ID, APPLICATION_ID, APPLICATION_NBR, APPLICATION_STATUS, APPLICATION_TYPE) values (?, ?, ?, ?) Hibernate: insert into EM_APPL_ADDRESS (CUSTOMER_ID, APPLICATION_ID, ADDRESS_TYPE) values (?, ?, ?) Question 1: in the insert method, I have assigned address to addresSet and made some changes in addresSet, after making those changes, I am not assigned the addressSet to applVO (i.e. not written applVO.setAddress(addresSet )) but it inserted a record with updated values into the Address table. What is happening here? When I am changing code inside insertApplication(ApplicationTable emApplVO) method to private void insertApplication(ApplicationTable emApplVO) { applVO.setAPPLICATION_ID(123456); applVO.setCUSTOMER_ID("88888888"); Set<AddressTable> addressSet = emApplVO.getAddress(); Set<AddressTable> newAddressSet = new HashSet<AddressTable>(); for (AddressTable address : newAddressSet) { address.setAPPLICATION_ID(emApplVO.getAPPLICATION_ID()); address.setCUSTOMER_ID(emApplVO.getCUSTOMER_ID()); address.setZIP(500032); newAddressSet.add(address); } emApplVO.setAddress(null); emApplVO.setAddress(newAddressSet); Session session1 = HibernateUtil.openSession(); Transaction beginTransaction = session1.beginTransaction(); session1.save(emApplVO); beginTransaction.commit(); session1.close(); } Hibernate queries in console log are... It also executing update ... Hibernate: select em_applica0_.CUSTOMER_ID as CUSTOMER1_0_,em_applica0_.APPLICATION_ID as APPLICAT2_0_,em_applica0_.ARCHIVE_IND as ARCHIVE8_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=? Hibernate: select address0_.CUSTOMER_ID as CUSTOMER1_0_1_, address0_.APPLICATION_ID as APPLICAT2_0_1_, address0_.ADDRESS_TYPE as ADDRESS3_1_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=? Hibernate: insert into EM_APPLICATION (CUSTOMER_ID, APPLICATION_ID, APPLICATION_NBR, APPLICATION_STATUS, APPLICATION_TYPE) values (?, ?, ?, ?) Hibernate: insert into EM_APPL_ADDRESS (CUSTOMER_ID, APPLICATION_ID, ADDRESS_TYPE) values (?, ?, ?) update EM_APPL_ADDRESS set CUSTOMER_ID=?, APPLICATION_ID=? where CUSTOMER_ID=? and APPLICATION_ID=? and ADDRESS_TYPE=? Question 2: why is the update query executed? Question 3: while using List<AddressTable> instead of Set<AddressTable>, I got some errors. What is the difference?
Hibernate not saving into join table - ManyToOne and JoinTable relation
I'm using SpringBoot 2.0.2 with hibernate 5.2.17 and MariaDB 10.1 Customer: #Entity public class Customer extends Company { #ManyToOne(fetch = FetchType.LAZY, cascade = ALL) #JoinTable(name = "company_services", joinColumns = #JoinColumn(name = "companyId", insertable = true, updatable = true), inverseJoinColumns = #JoinColumn(name = "serviceId", insertable = true, updatable = true)) private Service service; Service: #Entity public class Service { #Id #GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; With Hibernate log enabled I can see this: Hibernate: /* insert com.example.company.Customer */ insert into ` companies` ( ... ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) Hibernate: select last_insert_id() Hibernate: /* insert com.example.company.Customer */ insert into ` company_service` ( `serviceId`, `companyId` ) values (?, ?) 20:43:53.524 TRACE [cid: none] [session: none] org.hibernate.type.descriptor.sql.BasicBinder -- binding parameter [1] as [BIGINT] - [3] 20:43:53.524 TRACE [cid: none] [session: none] org.hibernate.type.descriptor.sql.BasicBinder -- binding parameter [2] as [BIGINT] - [3] But when I confirm the result in the db the companies table is OK but the company_services record does not get saved. Is that a known bug? I'm missing something?
Hibernate does not use join table for many to one relationship. It uses a join column for many to one relationship.
Unnecessary updates in #OneToMany
I have model object as follows Employee.java #Entity #Table(name = "EMPLOYEE") public class Employee { #Id #SequenceGenerator(name = "emp_seq", sequenceName = "seq_employee") #GeneratedValue(generator = "emp_seq") #Column(name = "EMPLOYEE_ID") private Integer employeeId; #Column(name = "EMPLOYEE_NAME") private String employeeName; } Department.java #Entity #Table(name = "DEPARTMENT") public class Department { #Id #Column(name = "DEPARTMENT_ID") #GeneratedValue(strategy = GenerationType.AUTO) private Integer departmentId; #Column(name = "DEPARTMENT_NAME") private String departmentName; #Column(name = "LOCATION") private String location; #OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) #JoinColumn(name = "DEPARTMENT_ID") private List<Employee> employees = new ArrayList<>(); } while saving this it is generating two extra update statements. Test class Employee e1 = new Employee(); e1.setEmployeeName("Employee-1"); Employee e2 = new Employee(); e2.setEmployeeName("Employee-2"); Department d = new Department(); d.setDepartmentName("Test"); d.setLocation("Test"); d.getEmployees().add(e1); d.getEmployees().add(e2); em.getTransaction().begin(); em.persist(d); em.getTransaction().commit(); on committing the following statements are generated... Hibernate: insert into DEPARTMENT (DEPARTMENT_NAME, LOCATION, DEPARTMENT_ID) values (?, ?, ?) Hibernate: insert into EMPLOYEE (EMPLOYEE_NAME, EMPLOYEE_ID) values (?, ?) Hibernate: insert into EMPLOYEE (EMPLOYEE_NAME, EMPLOYEE_ID) values (?, ?) **Hibernate: update EMPLOYEE set DEPARTMENT_ID=? where EMPLOYEE_ID=? **Hibernate: update EMPLOYEE set DEPARTMENT_ID=? where EMPLOYEE_ID=? my question here is why 2 extra update(marked by *) statements are needed?
That's the order on which Hibernate does the operations normally. Take a look at this https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/event/internal/AbstractFlushingEventListener.html#performExecutions%28org.hibernate.event.spi.EventSource%29 According to this documentation: Execute all SQL (and second-level cache updates) in a special order so that foreign-key constraints cannot be violated:
When you add Employees to a Department, employees must have a Department ID so that's the reason why Hibernate do an extra update. If you want to avoid it you can create first the department, and then the employees adding manually Department id
Due to the #OneToMany #JoinColumn(name = "DEPARTMENT_ID") that annotates the attribute Department.employees the table EMPLOYEE has a foreign key to the table DEPARTMENT. When you persiste the new department with the two employees a new row is inserted into the table DEPARTMENT and two rows are inserted into the table EMPLOYEE but the column DEPARTMENT_ID is null. Then two updates are executed to set this column and relate the EMPLOYEE rows with the DEPARTMENT row. The question is why this is not done in one step, i.e. instead of executing the following: Hibernate: insert into DEPARTMENT (DEPARTMENT_NAME, LOCATION, DEPARTMENT_ID) values (?, ?, ?) Hibernate: insert into EMPLOYEE (EMPLOYEE_NAME, EMPLOYEE_ID) values (?, ?) Hibernate: insert into EMPLOYEE (EMPLOYEE_NAME, EMPLOYEE_ID) values (?, ?) **Hibernate: update EMPLOYEE set DEPARTMENT_ID=? where EMPLOYEE_ID=? **Hibernate: update EMPLOYEE set DEPARTMENT_ID=? where EMPLOYEE_ID=? the following should be executed: Hibernate: insert into DEPARTMENT (DEPARTMENT_NAME, LOCATION, DEPARTMENT_ID) values (?, ?, ?) Hibernate: insert into EMPLOYEE (EMPLOYEE_NAME, EMPLOYEE_ID, DEPARTMENT_ID) values (?, ?, ?) Hibernate: insert into EMPLOYEE (EMPLOYEE_NAME, EMPLOYEE_ID, DEPARTMENT_ID) values (?, ?, ?)
Different behaviour using unidirectional or bidirectional relation
I want to persist a mail entity which has some resources (inline or attachment). First I related them as a bidirectional relation: #Entity public class Mail extends BaseEntity { #OneToMany(mappedBy = "mail", cascade = CascadeType.ALL, orphanRemoval = true) private List<MailResource> resource; private String receiver; private String subject; private String body; #Temporal(TemporalType.TIMESTAMP) private Date queued; #Temporal(TemporalType.TIMESTAMP) private Date sent; public Mail(String receiver, String subject, String body) { this.receiver = receiver; this.subject = subject; this.body = body; this.queued = new Date(); this.resource = new ArrayList<>(); } public void addResource(String name, MailResourceType type, byte[] content) { resource.add(new MailResource(this, name, type, content)); } } #Entity public class MailResource extends BaseEntity { #ManyToOne(optional = false) private Mail mail; private String name; private MailResourceType type; private byte[] content; } And when I saved them: Mail mail = new Mail("asdasd#asd.com", "Hi!", "..."); mail.addResource("image", MailResourceType.INLINE, someBytes); mail.addResource("documentation.pdf", MailResourceType.ATTACHMENT, someOtherBytes); mailRepository.save(mail); Three inserts were executed: INSERT INTO MAIL (ID, BODY, QUEUED, RECEIVER, SENT, SUBJECT) VALUES (?, ?, ?, ?, ?, ?) INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE, MAIL_ID) VALUES (?, ?, ?, ?, ?) INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE, MAIL_ID) VALUES (?, ?, ?, ?, ?) Then I thought it would be better using only a OneToMany relation. No need to save which Mail is in every MailResource: #Entity public class Mail extends BaseEntity { #OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) #JoinColumn(name = "mail_id") private List<MailResource> resource; ... public void addResource(String name, MailResourceType type, byte[] content) { resource.add(new MailResource(name, type, content)); } } #Entity public class MailResource extends BaseEntity { private String name; private MailResourceType type; private byte[] content; } Generated tables are exactly the same (MailResource has a FK to Mail). The problem is the executed SQL: INSERT INTO MAIL (ID, BODY, QUEUED, RECEIVER, SENT, SUBJECT) VALUES (?, ?, ?, ?, ?, ?) INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE) VALUES (?, ?, ?, ?) INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE) VALUES (?, ?, ?, ?) UPDATE MAILRESOURCE SET mail_id = ? WHERE (ID = ?) UPDATE MAILRESOURCE SET mail_id = ? WHERE (ID = ?) Why this two updates? I'm using EclipseLink, will this behaviour be the same using another JPA provider as Hibernate? Which solution is better? UPDATE: - If I don't use #JoinColumn EclipseLink creates three tables: MAIL, MAILRESOURCE and MAIL_MAILRESOURCE. I think this is perfectly logic. But with #JoinColumn it has information enough for creating only two tables and, in my opinion, do only inserts, with no updates.
When you use a #JoinColumn in a OneToMany you are defining a "unidirectional" one to many, which is a new type of mapping added in JPA 2.0, this was not supported in JPA 1.0. This is normally not the best way to define a OneToMany, a normal OneToMany is defined using a mappedBy and having a ManyToOne in the target object. Otherwise the target object has no knowledge of this foreign key, and thus the separate update for it. You can also use a JoinTable instead of the JoinColumn (this is the default for OneToMany), and then there is no foreign key in the target to worry about. There is also a fourth option. You could mark the MailResource as an Embeddable instead of Entity and use an ElementCollection. See, http://en.wikibooks.org/wiki/Java_Persistence/OneToMany
Mapped by defines owning side of the relation ship so for JPA it gives better way to handle associations. Join Column only defines the relationship column. Since JPA is completely reflection based framework I could think of the optimization done for Mapped by since it is easy find owning side this way.