AlejoDev
AlejoDev

Reputation: 3202

Map Unidirectional One To One Relationship in Hibernate 5

My question is very simple, why hibernate does not bring the primary key of the Account entity to the User entity automatically when I persist a Account entity as for example if it does @mapsId?

If we have this mapping in mind:

@Entity
@Table(name="account")
@Getter
@Setter
public class Account implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="id")
    @GeneratedValue(strategy= GenerationType.AUTO, generator="native")
    @GenericGenerator(name = "native", strategy = "native")
    private int accountId;

    @Column(name="email")
    private String email;

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn(name="id", referencedColumnName="pk_account$user")
    private User user;

}

@Entity
@Table(name="user")
@Getter
@Setter
public class User implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="pk_account$user")
    private int userId;

    @Column(name="identification_number")
    private String identificationNumber;

}

and then I do this:

public static void main( String[] args )
    {
        SessionFactory sessionFactory;
        Configuration configuration= new Configuration();
        configuration.configure();
        sessionFactory=configuration.buildSessionFactory();
        Session session= sessionFactory.openSession();
        session.beginTransaction();
        Account account= new Account();
        account.setEmail("[email protected]");

        User user= new User();
        user.setIdentificationNumber("11462727272");
        account.setUser(user);

        session.persist(account);
        session.getTransaction().commit();

    }

I have this error:

Hibernate: insert into account (email) values (?)
Hibernate: insert into user (identification_number, pk_account$user) values (?, ?)
    Jun 04, 2018 5:41:16 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
    WARN: SQL Error: 1452, SQLState: 23000
    Jun 04, 2018 5:41:16 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
    ERROR: Cannot add or update a child row: a foreign key constraint fails (`prueba`.`user`, CONSTRAINT `fk_user_account` FOREIGN KEY (`pk_account$user`) REFERENCES `account` (`id`))
    Jun 04, 2018 5:41:16 PM org.hibernate.internal.ExceptionMapperStandardImpl mapManagedFlushFailure
    ERROR: HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
    Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:149)
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164)
        at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1460)
        at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:511)
        at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3278)
        at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2474)
        at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473)
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178)
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39)
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271)
        at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98)
        at com.espiritware.pruebaOneToOne.App.main(App.java:32)
    Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
        at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3136)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3651)
        at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:90)
        at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
        at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478)
        at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356)
        at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
        at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454)
        ... 9 more
    Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`prueba`.`user`, CONSTRAINT `fk_user_account` FOREIGN KEY (`pk_account$user`) REFERENCES `account` (`id`))
        at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:115)
        at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95)
        at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
        at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:960)
        at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1116)
        at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1066)
        at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1396)
        at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1051)
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
        ... 17 more

I expected the following:

  1. get ID from generator, set it on the Account
  2. persist Account
  3. set Account's ID on User
  4. persist User

Why the above does not happen? Is there any way this works as I expect?

However, if I manually assign the id to the user this works without problems:

public static void main( String[] args )
{
    SessionFactory sessionFactory;
    Configuration configuration= new Configuration();
    configuration.configure();
    sessionFactory=configuration.buildSessionFactory();
    Session session= sessionFactory.openSession();
    session.beginTransaction();
    Account account= new Account();
    account.setEmail("[email protected]");

    session.persist(account);

    User user= new User();
    user.setUserId(account.getAccountId());
    user.setIdentificationNumber("11462727272");
    account.setUser(user);

    session.persist(account);
    session.getTransaction().commit();

}

I would really appreciate if someone could give you an explanation. I would like to do this without making manual assignments, maintaining the unidirectional relationship

Upvotes: 1

Views: 634

Answers (1)

Brian Vosburgh
Brian Vosburgh

Reputation: 3276

The JPA spec indicates you should no longer use @PrimaryKeyJoinColumn for @OneToOne mappings. As the User primary key is determined by Account, you might try a bi-directional one-to-one and use "derived identity":

@Entity
@Table(name="account")
@Getter
@Setter
public class Account implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="id")
    @GeneratedValue(strategy= GenerationType.AUTO, generator="native")
    @GenericGenerator(name = "native", strategy = "native")
    private int accountId;

    @Column(name="email")
    private String email;

    @OneToOne(cascade = CascadeType.ALL, mappedBy="account")
    private User user;
}

@Entity
@Table(name="user")
@Getter
@Setter
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="pk_account$user")
    private int userId;

    @OneToOne
    @MapsId
    private Account account;

    @Column(name="identification_number")
    private String identificationNumber;
}

Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.

Upvotes: 1

Related Questions