JPA one-to-one insert how to do it properly with eclipselink

不问归期 提交于 2019-12-12 03:09:41

问题


I am trying to do simple one-to-one mapping with JPA and EclipseLink.

I have the following DB structure in PostgreSQL

CREATE TABLE public.employee (
  id SERIAL,
  firstname VARCHAR(20),
  lastname VARCHAR(20),
  CONSTRAINT employee_pkey PRIMARY KEY(id)
);
CREATE TABLE public.employee_info (
  id INTEGER NOT NULL,
  info TEXT DEFAULT ''::text NOT NULL,
  CONSTRAINT employee_info_pkey PRIMARY KEY(id),
  CONSTRAINT employee_info_fk FOREIGN KEY (id)
);

and I am trying to reflect this as the following code:

Employee:

@Entity
@Table(name = "employee")
public class Employee implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Integer id;
  @Basic(optional = false)
  @Column(name = "firstname")
  private String firstname;
  @Basic(optional = false)
  @Column(name = "lastname")
  private String lastname;
  @OneToOne(cascade = CascadeType.ALL, mappedBy = "employee")
  private EmployeeInfo employeeInfo;
...
}

and employee_info

@Entity
@Table(name = "employee_info")
public class EmployeeInfo implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)  
  @Column(name = "id")
  private Integer id;
  @Column(name = "info")
  private String info;
  @OneToOne
  @PrimaryKeyJoinColumn
  private Employee employee;
...
}

Now I am trying to insert the data:

    em.getTransaction().begin();
    Employee emp = new Employee();
    emp.setFirstname("FirstName");
    emp.setLastname("LastName");
    EmployeeInfo ei = new EmployeeInfo();
    ei.setInfo("some info");
    emp.setEmployeeInfo(ei);
    em.persist(emp);
    em.getTransaction().commit();
    em.close();
    emf.close();

and get the error, since JPA is generating the INSERT query without specifying the id for the second table

[EL Info]: 2015-02-28 14:15:31.741--ServerSession(1440332016)--EclipseLink, version: Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd
[EL Info]: connection: 2015-02-28 14:15:31.902--ServerSession(1440332016)--file:/D:/PROJEKTY/JAVA/JPATest3/build/classes/_JPATest3PU login successful
[EL Warning]: 2015-02-28 14:15:31.963--UnitOfWork(2115597658)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
  Detail: Failing row contains (null, some info).
Error Code: 0
Call: INSERT INTO employee_info (info) VALUES (?)
    bind => [1 parameter bound]
Query: InsertObjectQuery(jpatest3.EmployeeInfo[ id=null ])
Exception in thread "main" javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
  Detail: Failing row contains (null, some info).
Error Code: 0
Call: INSERT INTO employee_info (info) VALUES (?)
    bind => [1 parameter bound]
Query: InsertObjectQuery(jpatest3.EmployeeInfo[ id=null ])
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:157)
    at jpatest3.JPATest3.main(JPATest3.java:32)
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
  Detail: Failing row contains (null, some info).
Error Code: 0
Call: INSERT INTO employee_info (info) VALUES (?)
    bind => [1 parameter bound]
Query: InsertObjectQuery(jpatest3.EmployeeInfo[ id=null ])
    at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:340)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.processExceptionForCommError(DatabaseAccessor.java:1611)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:898)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:962)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:631)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:558)
    at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2002)
    at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:298)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:242)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:228)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.insertObject(DatasourceCallQueryMechanism.java:377)
    at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:165)
    at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:180)
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite(DatabaseQueryMechanism.java:489)
    at org.eclipse.persistence.queries.InsertObjectQuery.executeCommit(InsertObjectQuery.java:80)
    at org.eclipse.persistence.queries.InsertObjectQuery.executeCommitWithChangeSet(InsertObjectQuery.java:90)
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
    at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:899)
    at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:798)
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1804)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1786)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1737)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitNewObjectsForClassWithChangeSet(CommitManager.java:226)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsForClassWithChangeSet(CommitManager.java:193)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:138)
    at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4207)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1531)
    at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:277)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1169)
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:132)
    ... 1 more
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
  Detail: Failing row contains (null, some info).
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2157)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1886)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:555)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:417)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:363)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:890)
    ... 33 more
Java Result: 1

Can somebody please tell me what I am doing wrong and how to fix the code or data structure to have it working fine with JPA / Eclipselink?

Thank you

EDIT 1: The code below works, but is this the way it should be done?

Employee emp = new Employee();
emp.setFirstname("FirstName");
emp.setLastname("LastName");

em.persist(emp);
em.flush();

EmployeeInfo ei = new EmployeeInfo(emp.getId(), "some info");
em.persist(ei);

@Entity
@Table(name = "employee")

public class Employee implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "id")
  private Integer id;
  @Basic(optional = false)
  @Column(name = "firstname")
  private String firstname;
  @Basic(optional = false)
  @Column(name = "lastname")
  private String lastname;
  @OneToOne(cascade = CascadeType.ALL, mappedBy = "employee")
  private EmployeeInfo employeeInfo;

@Entity
@Table(name = "employee_info")

public class EmployeeInfo implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @Basic(optional = false)
  @Column(name = "id")
  private Integer id;
  @Basic(optional = false)
  @Column(name = "info")
  private String info;
  @JoinColumn(name = "id", referencedColumnName = "id", insertable = false, updatable = false)
  @OneToOne(optional = false)
  private Employee employee;

EDIT 2: After some time I found out how to achieve what I wanted. I asked wrong questions. It was not about one-to-one mapping. It was as simple as @SecondaryTable annotation. With @SecondaryTable(s) annotation one entity can be spread around two or more tables and this is what I wanted to achieve

@Entity
@Table(name = "employee")
@SecondaryTable(name = "employee_info")

public class Employee implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  private String name;
  @Column(table = "employee_info")
  private String longName;

....

Employee e = new Employee();

e.setName("name");
e.setLongName("long name");

em.getTransaction().begin();
em.persist(e);

em.getTransaction().commit();
emf.close();

回答1:


You mapped the relation as managed by the EmployeeInfo, it means that you also have to set the employee on the newly created EmployeeInfo object as JPA will not do it for you. As it is now, JPA tries to save the employeeinfo without the parent object (and you get your null key, but that is not all).

The employinfo primary key is the primary key of the employee so it cannot be generated as you declared @GeneratedValue(strategy = GenerationType.IDENTITY), but it has to adopt the primary key of its parent empolyee object.

You need to use @Id or @PrimaryKeyJoinColumn on you @OneToOne mapping in EmployeeInfo to inform JPA that the key should be derived from the parent, there is explanation of it here: http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Primary_Keys_through_OneToOne_and_ManyToOne_Relationships

Finally, it will be better if you change your ID generation on employee from the Identity to Table. That way the JPA "knows" the key before the inset and can setup the keys on both objects, otherwise it has to insert first and retrieve the primary key set.

So for examplee:

EmployeeInfo {
  @Id
  @Column(name = "id")
...
  @OneToOne
  @Id
  @JoinColumn(name="id", referencedColumnName="id")
  private Employee employee;
}

Employee emp = new Employee();
emp.setFirstname("FirstName");
emp.setLastname("LastName");

EmployeeInfo ei = new EmployeeInfo("some info");
ei.setEmploy(emp);
emp.setEmployeeInfo(ei);

em.persist(emp);


来源:https://stackoverflow.com/questions/28782324/jpa-one-to-one-insert-how-to-do-it-properly-with-eclipselink

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!