0

I need some help resolving a Hibernate TransientPropertyValueException

We have two entities:

@Entity
@Table(name = "TABLE_A")
public class TableA {
    @Id
    @Column(name = "EXT_ID", nullable = false, unique = true, updatable = false)
    private String extId;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "EXT_ID", updatable = false, insertable = false)
    private TableB tableB;

   (...)
}

@Entity
@Table(name = "TABLE_B")
public class TableB  {
    @Id
    @Column(name = "EXT_ID", nullable = false)
    private String extId;

    @OneToOne
    @JoinColumn(name = "EXT_ID")
    private TableA tableA;
}

Now we delete a TableA using a CrudRepository:

@Override
@Transactional
public boolean cancel(String extId) {
    Optional<TableA> maybeTableA = repository.findTableA(extId, DELETE_STATUS_SET);

    return maybeTableA.map(tableA -> {
        repository.delete(tableA);
        return true;
    }).orElse(false);
}

So far everything works fine. But now whenever I want to query for the TableB which had a reference to the TableA I am getting a TransientPropertyValueException It is obvious, that the reference from TableB to TableA has not been deleted before commiting the transaction.

Is there a nice way to resolve this problem? I have tried sessionFactory.getCurrentSession().flush() directly after the delete but it also does not work

The only alternative that worked was manually deleting the reference within the transaction:

tableB.setTableA(null);
tableBRepository.save(tableB);

But this is a little bit hacky imo

Abaddon666
  • 1,533
  • 15
  • 31

3 Answers3

1

When it comes to JPA relationships, you are responsible to update both sides of the relationship.

Here, the cancel method should update both tableA and tableB. Otherwise, what you get is an EXT_ ID value in tableB that has no correspondence in tableA. Then, upon retrieving the tableB entity, Hibernate expects a corresponding tableA entity, which doesn't exist anymore and throws an error.

TableB's extId needs to be nullable, if the tableB entity should remain when its corresponding tableA entity is deleted.

One more thing: Like in TableA, it will be necessary for TableB to declare either extId or tableA as updatable = false, insertable = false. Hibernate doesn't like two writable properties on the same database field.

@Override
@Transactional
public boolean cancel(String extId) {
    Optional<TableA> maybeTableA = repository.findTableA(extId, DELETE_STATUS_SET);

    return maybeTableA.map(tableA -> {

        // update tableB
        TableB tableB = tableA.getTableB();
        tableB.setExtId(null);
        tableB.setTableA(null);
        tableBRepository.save(tableB);

        // and update tableA
        repository.delete(tableA);
        return true;
    }).orElse(false);
}
Markus Pscheidt
  • 6,853
  • 5
  • 55
  • 76
  • Thank you for this very nice answer! How come that hibernate correctly resolves this before closing the transaction? If one rest call deletes the entity and the next one queries it, it works. Is it a problem not to delete the ext_id on B when deleting A in this scenario? We need the value preserved even on deletion of A – Abaddon666 May 26 '18 at 12:19
  • Hibernate won't persist anything before the transaction is finalized, and even then it won't necessarily flush the changes to the database immediately. If you must preserve the value of ext_id, consider writing it into another field in tableB that is not used for retrieving tableA. – Markus Pscheidt May 27 '18 at 08:06
  • Thanks a lot. I will consider your suggestions! – Abaddon666 May 27 '18 at 09:56
0

Have you tried the @MapsId annotation?

https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/

Arlo Guthrie
  • 1,152
  • 3
  • 12
  • 28
  • Thanks for the tip, can you also point out how exactly this can solve my deletion problem? As far as i understood it, this is only another way to model the mapping in my entities – Abaddon666 May 25 '18 at 15:36
  • Take a look at the one-to-one relationship at this site between Contact and ContactDetail: http://techferry.com/articles/hibernate-jpa-annotations.html#Transient – Arlo Guthrie Jun 28 '18 at 16:46
0

Have You looked at cascading? I myself would cho with your hacky approach since I find it easier to read and follow in the code.

JPA @ManyToOne with CascadeType.ALL

David Karlsson
  • 9,396
  • 9
  • 58
  • 103
  • `It seems in your case to be a bad idea, as removing an Address would lead to removing the related User` This exactly what i do not want. The TableB entry has to stay in the database. I checkted cascading but did not find the right one. Is there something for my use case? – Abaddon666 May 25 '18 at 15:33