0

I have an entity hierarchy, which is represented and editable in a web interface and persisted in the data layer of my web application. To make persisting easier I annotated some entities with CascadaType.PERSIST and CascadeType.MERGE - but this results in exceptions on persist and merge!

Here the entities:

  • Adresse has Land
  • Anfrage has adresse.land
  • Angebot has adresse.land and anfrage.adresse.land

The entity classes are generated from some XML source, so it would be hard to define, which entities should use cascading and which not!

@Entity
public class Adresse {
    @Id
    @Column(name = "id", nullable = false)
    @TableGenerator(name = "ids", table = "schema.ids", pkColumnName = "id_name", pkColumnValue = "ID", valueColumnName = "id", initialValue = 10000, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "ids")
    public Long getId() { return this.id; }

    @ManyToOne(fetch= FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="land_id")
    public Land getLand() { return land; }
}

@Entity
public class Anfrage {
    @Id
    @Column(name = "id", nullable = false)
    @TableGenerator(name = "ids", table = "schema.ids", pkColumnName = "id_name", pkColumnValue = "ID", valueColumnName = "id", initialValue = 10000, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "ids")
    public Long getId() { return this.id; }

    @ManyToOne(fetch= FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="adresse_id")
    public Adresse getAdresse() { return adresse; }
}

@Entity
public class Angebot {
    @Id
    @Column(name = "id", nullable = false)
    @TableGenerator(name = "ids", table = "schema.ids", pkColumnName = "id_name", pkColumnValue = "ID", valueColumnName = "id", initialValue = 10000, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "ids")
    public Long getId() { return this.id; }

    @ManyToOne(fetch= FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="adresse_id")
    public Adresse getAdresse() { return adresse; }

    @ManyToOne(fetch= FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="anfrage_id")
    public Anfrage getAnfrage() { return anfrage; }
}

Here the service for Anfrage - similar available for Angebot and Adresse:

@DomainService
public class AnfrageService {
    @Inject protected Repository repository;

    public Anfrage getById(final Long id) {
        return repository.getEntity(this.entityClass, id);
    }

    public void save(Anfrage entity) {
        repository.save(entity);
    }

    public Anfrage update(final Anfrage entity) {
        return repository.update(entity);
    }
}

Here the repository - representing the data access layer

@ApplicationScoped
@Transactional(value = Transactional.TxType.SUPPORTS)
public class Repository {
    @Inject protected EntityManager entityManager;

    public <T> T getEntity(final Class<T> clazz, final Object id) {
        return entityManager.find(clazz, id);
    }

    @Transactional(value = Transactional.TxType.REQUIRED)
    public <T> T update(T entity) {
        return entityManager.merge(entity);
    }

    @Transactional(value = Transactional.TxType.REQUIRED)
    public <T> void save(T entity) {
        entityManager.persist(entity);
    }
}

And here my Arquillian Test - same errors / exceptions appear in the application

@Test
public void saveNewAngebotWithExistingAnfrageWithExistingAdresse() {

    final Long adresseID = 60L;
    final Long anfrageID = 8811L;

    Adresse adresse = adresseService.getById(adresseID); // -> returns adresse with land.id=1000
    Anfrage anfrage = anfrageService.getById(anfrageID); // -> anfrage also has adresse with land.id=1000

    Angebot angebot = AngebotFactory.create();
    angebot.setBeschreibung("Testangebot");

    angebot.setAdresse(adresse);
    angebot.setAnfrage(anfrage);

    angebotService.save(angebot); 

    // -> javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: 
    // detached entity passed to persist: my.domain.model.impl.Anfrage

    Angebot persistedAngebot = angebotService.update(angebot); 

    // -> java.lang.IllegalStateException: Multiple representations of the same entity 
    // [my.domain.model.impl.Land#1000] are being merged. 
    // Detached: [my.domain.model.impl.Land@273e9761[id=1000,version=6,created=17.05.2016 16:25:28,modified=17.05.2016 16:25:28,i18nKey=land.oesterreich,isoCode=AT,intVorwahl=43]]; 
    // Detached: [my.domain.model.impl.Land@7c939d12[id=1000,version=6,created=17.05.2016 16:25:28,modified=17.05.2016 16:25:28,i18nKey=land.oesterreich,isoCode=AT,intVorwahl=43]]
}

Using save / persist it results in an exception saying that anfrage.angebot could not be saved, because it is detached!

Using update / merge it results in an exception saying the anfrage.adresse.land and angebot.land are the same entity and therefore could not be merged!

Do I have to handle persist/merge by hand for my entity hierarchy?

I thought cascading PERSIST and MERGE makes things easiser, but it seems more to lead into troubles?!

I am using Wildfly 9 but got the same errors with Wildfly 10!

Any hints welcome - Thank you!

raho
  • 129
  • 4
  • 18
  • Can you not remove `Adresse` from `Angebot`? Or is the `Adresse` for `Angebot` and `Anfrage` different? – ujulu Jun 27 '16 at 11:06
  • Unfortunately I can not remove `Adresse` from `Angebot` and I cannot control whether the `Adresse` fields are equal and different. – raho Jun 27 '16 at 13:09
  • And you cannot remove `cascade = {CascadeType.PERSIST, CascadeType.MERGE}` attribute of the `Adresse` association from within the `Angebot` entity? Or, better, what can you change in the entities? – ujulu Jun 27 '16 at 13:33
  • I can remove the `cascade` annotations, but this means that I must handle the persist and merge operations manually. My assumption was, that cascading makes my code cleaner and I must not care about associated entities. – raho Jun 27 '16 at 13:42
  • The reason why I wrote you to remove `cascade` is that you are not creating `Adresse` but rather fetching it. That means the instance of `Adresse` is a detached entity. In that case, persisting `Angebot` should not throw an exception but synchronize the changes to the DB as `Angebot` is the owner of the relationship. That is the theory, I haven't tried it. – ujulu Jun 27 '16 at 19:08
  • I solved the problem by completely removing the cascade annotations. – raho Jul 01 '16 at 12:53

0 Answers0