Uwe Allner
Uwe Allner

Reputation: 3467

Hibernate validates collection members removed from collections

I have encountered a strange phenomenon. In a reduced form my entity classes look like this:

@Entity
@Audited
public class Product {

    @Id
    @Column(length = 32)
    private String id = IdGenerator.createId();

    /** Descriptive texts, samples, comments etc.; ONIX composite    Product/OtherText */
    @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, orphanRemoval=true)
    @JoinColumn(name="product_id")
    @Index(name="idx_prod_text")
    private Set<Text> texts;

    ...
}

@Entity
@Audited
public class Text {

    @Id
    @Column(length = 32)
    private String id = IdGenerator.createId();

    @NotNull
    @Column(nullable=false, length=3)
    private String type;
    ...
}

HashCode and equals work on the id field. I have built a generic pre-validation routine for auto correction and generation of warnings in a user friendly way. Auto correction for Texts with a null type is removal of this text from the collection in the product ( by calling product.getTexts().removeAll(entitiesToRemove)).

Now I got a product (previously read from DB, so attached to Hibernate session), which gets appended a Text with type null. It is auto corrected, and the Text in question is removed from the collection as described above. So far, so good; inspection of the Product in the debugger shows that the Text is not in the texts collection anymore. But when I call productRepository.saveAndFlush(product) (), I get a

javax.validation.ConstraintViolationException: Validation failed for classes [de.vlx.metadatastore.model.product.Text] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='darf nicht null sein', propertyPath=type, rootBeanClass=class de.vlx.metadatastore.model.product.Text, messageTemplate='{javax.validation.constraints.NotNull.message}'}
]

So it seems that this text is still validated by Hibernate? Even if it never was in the data base (which also has of course a NotNull constraint), and is not in the texts collection of the Product anymore?

Even stranger: when I tried to hotfix this by setting a dummy value on the type of the removed entity, I don't get the above exception, but a org.springframework.dao.DataIntegrityViolationException complaining about the fact that type is null.

Which kind of magic is at work, and what can I do to fix that?

Used versions: Hibernate 4.1.7.Final Spring-data-jpa 1.6.0.RELEASE Spring-core 3.2.9.RELEASE

UPDATE: By intensive debuggin I recently found, that (as the Product is delivered in many ways like xml and excel files, and the merging with the current version is a rather complex process) in this case all Texts from the previous version are removed and the updates are added, and afterwards in the central validation routine the erroneus Text is removed. But Hibernate has stored the adding in its action queue, and wants to perform the insert before the remove. And the insert is stored with the intial (null) values, so updating the Text in question did not help.

I am still completely clueless what to do about that. It seems that I have to ensure that no invalid object may enter any collection at any time, as fixing afterwards will not help.

Upvotes: 0

Views: 628

Answers (2)

Uwe Allner
Uwe Allner

Reputation: 3467

After a lot of debugging and testing I finally found the reason for the problem and a workaround.

Hibernate indeed keeps an ActionQueue where all relevant actions on managed entities are kept, and mostly in the order they are done on the entity. In my case there were e.g. two Text objects added to the texts collection in the Product, so Hibernate added an InsertAction to its ActionQueue with the values at the time of insertion (so later fixes on the entity did have no effect but adding an UpdateAction to the ActionQueue). The removal of the faulty object from the collection resulted in a DeleteAction for this object.

These UpdateAction or DeleteAction are never executed because the InsertAction failed with the NotNull-Constraint. So I had to ensure that the inserted objects never have any illegal null values. For this I refactored the validation service so that before inserting the entities with a recognizable dummy value != null, which I use to check against later on. Not so nice, but it works.

I would still be interested in a kind of optimizer for the Hibernate ActionQueue; has something like this been implemented since version 4.1.7?

Special thanks to MartinFrey for some valuable suggestions.

Upvotes: 0

Martin Frey
Martin Frey

Reputation: 10075

Looks like you have two choices:

  • Ensure that the text is not set to null and kept "unmodified" and simply removes the entry.
  • Or set a pseudo text during your auto correction function practically avoiding the NotNull constraint.

Surely the first possibility would be preferable as I'm not sure that setting a pseudo text would not trigger an update statement prior to the delete statement.

UPDATE: I see some possibilities for the issues around managed entities (havent needed this, so untested):

  • refresh the Text entities in the cleanup code with entityManager.refresh. This will issue selects but should avoid the issue.
  • probably you can play with evict from the current session? Don't know how hibernate will react here.
  • ideally avoid modifying the Text values by using an intermediate DTO instead of the entities and merge them in your service method. I think this would be my preferred solution.

Upvotes: 1

Related Questions