Hibernate - #ElementCollection - Strange delete/insert behavior - java

#Entity
public class Person {
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
private List<Location> locations;
[...]
}
#Embeddable
public class Location {
[...]
}
Given the following class structure, when I try to add a new location to the list of Person's Locations, it always results in the following SQL queries:
DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson
And
A lotsa' inserts into the PERSON_LOCATIONS table
Hibernate (3.5.x / JPA 2) deletes all associated records for the given Person and re-inserts all previous records, plus the new one.
I had the idea that the equals/hashcode method on Location would solve the problem, but it didn't change anything.
Any hints are appreciated!

The problem is somehow explained in the page about ElementCollection of the JPA wikibook:
Primary keys in CollectionTable
The JPA 2.0 specification does not
provide a way to define the Id in the
Embeddable. However, to delete or
update a element of the
ElementCollection mapping, some unique
key is normally required. Otherwise,
on every update the JPA provider would
need to delete everything from the
CollectionTable for the Entity, and
then insert the values back. So, the
JPA provider will most likely assume
that the combination of all of the
fields in the Embeddable are unique,
in combination with the foreign key
(JoinColunm(s)). This however could be
inefficient, or just not feasible if
the Embeddable is big, or complex.
And this is exactly (the part in bold) what happens here (Hibernate doesn't generate a primary key for the collection table and has no way to detect what element of the collection changed and will delete the old content from the table to insert the new content).
However, if you define an #OrderColumn (to specify a column used to maintain the persistent order of a list - which would make sense since you're using a List), Hibernate will create a primary key (made of the order column and the join column) and will be able to update the collection table without deleting the whole content.
Something like this (if you want to use the default column name):
#Entity
public class Person {
...
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
#OrderColumn
private List<Location> locations;
...
}
References
JPA 2.0 Specification
Section 11.1.12 "ElementCollection Annotation"
Section 11.1.39 "OrderColumn Annotation"
JPA Wikibook
Java Persistence/ElementCollection

In addition to Pascal's answer, you have to also set at least one column as NOT NULL:
#Embeddable
public class Location {
#Column(name = "path", nullable = false)
private String path;
#Column(name = "parent", nullable = false)
private String parent;
public Location() {
}
public Location(String path, String parent) {
this.path = path;
this.parent= parent;
}
public String getPath() {
return path;
}
public String getParent() {
return parent;
}
}
This requirement is documented in AbstractPersistentCollection:
Workaround for situations like HHH-7072. If the collection element is a component that consists entirely
of nullable properties, we currently have to forcefully recreate the entire collection. See the use
of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info. In order to delete
row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
the current "WHERE COL = ?" (fails for null for most DBs). Note that
the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the
AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing
recreation isn't ideal, but not really any other option in ORM 4.

We discovered that entities we were defining as our ElementCollection types did not have an equals or hashcode method defined and had nullable fields. We provided those (via #lombok for what it's worth) on the entity type and it allowed hibernate (v 5.2.14) to identify that the collection was or was not dirty.
Additionally, this error manifested for us because we were within a service method that was marked with the annotation #Transaction(readonly = true). Since hibernate would attempt to clear the related element collection and insert it all over again, the transaction would fail when being flushed and things were breaking with this very difficult to trace message:
HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]
Here is an example of our entity model that had the error
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
public class Entity2 {
private UUID someUUID;
}
Changing it to this
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
#EqualsAndHashCode
public class Entity2 {
#Column(nullable = false)
private UUID someUUID;
}
Fixed our issue. Good luck.

I had the same issue but wanted to map a list of enums: List<EnumType>.
I got it working like this:
#ElementCollection
#CollectionTable(
name = "enum_table",
joinColumns = #JoinColumn(name = "some_id")
)
#OrderColumn
#Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();
public void setEnumList(List<EnumType> newEnumList) {
this.enumTypeList.clear();
this.enumTypeList.addAll(newEnumList);
}
The issue with me was that the List object was always replaced using the default setter and therefore hibernate treated it as a completely "new" object although the enums did not change.

Related

Hibernate update before insert in one to many

I am getting the constraint violation exception because of the order of operations performed by Hibernate. I have the following entities defined.
#Entity
public class A {
#Id
private Integer id;
#OneToMany(mappedBy = "a", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<B> bList;
public void setBList(List<B> bList) {
if(CollectionUtils.isNotEmpty(this.bList)) {
this.bList.clear();
}
if(CollectionUtils.isNotEmpty(bList)) {
this.bList.addAll(bList);
}
}
}
#Entity
#Table(uniqueConstraints={#UniqueConstraint(columnNames = {"name", "a_id", "isDeleted"})})
public class B {
#Id
private Integer id;
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name="a_id")
private A a;
private boolean isDeleted;
}
When I set the new list of Bs (containing one item updated as deleted and a new item having the same values in the columns corresponding to constraint) in entity A and save entity A, I get constraint violation.
Hibernate is performing insert of the new item before updating the old item as deleted leading to constraint violation when in fact the data is correct in the application.
Am I doing something wrong here or Is there any configuration or fix for this?
Answer changed on 2021/05/07 due to comment from the OP pointing out it was missing the point
There are 2 things you should change for things to work
You should not rely on Hibernate to guess the right order of operations for you. It relies on heuristics that might not fit your intent. In your case, you should call EntityManager.flush after your soft-delete of the old B and before persisting the new one.
Your unique constrain will cause problems anyway, when you'll soft-delete your second B, that is identical regarding unique columns. More hereafter
In general, ensuring this kind of constrains in DB is a bad idea. If you try and update/insert an entity that violates them, then you'll get an obscure PersistenceException and it will be hard to warn your users about the exact cause. So you will have to programmatically check those constrains before insertion/update anyways. Hence, you'd better remove them and ensure unicity through your program, unless they're vital to data integrity. Same goes for not-nullable columns and other constrains that are pure business logic.
Now last advice from experience: for soft-delete column, use a TimeStamp rather than a boolean. Same effort updating and reading your records, but it gives you some valuable information about when a record was deleted.

JPA mapping issue when mapping two entities with same `JoinColumn`

I'm having some weird situation, where I have for example an entity called Article, which has a relation to Supplier, but also to Supplier Contact Person. For example:
Supplier is linked to Article by Supplier_Id, while ContactpersonSupplier is linked to Article by both Supplier_Id (to SupplierId) and Supplier_Contactperson_Id (to Id).
So, right now we mapped all relations on Article:
#JoinColumn(name = "Supplier_Id")
private Supplier supplier;
#JoinColumns({
#JoinColumn(name = "Supplier_Id"),
#JoinColumn(name = "Supplier_Contactperson_Id")
private SupplierContactperson supplierContactperson;
This does not work because we're mapping Supplier_Id twice, once for supplier and once for supplierContactperson. If you do this, you get the following exception:
org.hibernate.MappingException: Repeated column in mapping for entity: Article column: Supplier_Id (should be mapped with insert="false" update="false")
In a normal situation you would link them up like this: Article -> ContactpersonSupplier -> Supplier, and then there would be no problems.
However, ContactpersonSupplier is not required, but Supplier is required. This means that if we leave the contactperson away, we can't provide a supplier.
We cannot use insertable = false, updatable = false for the very same reason, if we put these values on supplier, we cannot add a supplier if the contactperson is not provided.
We cannot add them on supplierContactperson either, because JPA/Hibernate requires you to put it on all #JoinColumn's inside a #JoinColumns, and if we do that, we can't save a contactperson.
One idea we have is to simply map the IDs, in stead of using related entities, but we're wondering if there's an alternative approach that might work. So the question is, how should we solve this mapping issue?
One thing to mention though, the data structure cannot be changed.
this worked for me:
#JoinColumn(name = "Supplier_Id",insertable=false,updatable=false)
private Supplier supplier;
#JoinColumns({
#JoinColumn(name = "Supplier_Id",insertable=false,updatable=false),
#JoinColumn(name = "Supplier_Contactperson_Id",insertable=false,updatable=false)
private SupplierContactperson supplierContactperson;
#Column(name="Supplier_Id")
private String supplier_id;
#Column (name = "Supplier_Contactperson_Id")
private String supplier_contact_Person_id;
and then in the setters
for setSupplierContactPerson(contactPerson)
supplierContactPerson = contactPerson;
if (contactPerson!=null){
supplier_id = contactPerson.getSupplierID();
supplier_contact_Person_id = contactPerson.getSupplierContactPersonID();
}
for setSupplier(supplier):
supplier = supplier;
if (supplier != null){
supplier_id = supplier.getId();
}
To map only the id of ContactPersonSupplier has a problem: you could put a contact person from a supplier A and the supplier B and the database would not complain.
Since supplier is required, I'd try:
1. Put insert=false, update=false in the JoinColumn("supplier_id") of the contact person field, to avoid the complains from JPA.
2. modify (if still hadn't) setSupplierContactPerson() with
if (contactPerson != null){
setSupplier(contactPerson.getSupplier());
} else {
setSupplier(null);
}
Another option is to modify getSupplier() with
if (contactPerson != null){
return contactPerson.getSupplier();
}
return supplier;

Hibernate : Sorting ManyToMany mapping

Consider the following mapping with JPA annotations
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "infotype_validations",
joinColumns = { #JoinColumn(name = "info_type_id") },
inverseJoinColumns = { #JoinColumn(name = "validation_id") }
)
#OrderBy(value="validation_id desc")
public Set<Validation> getValidation() {
return validation;
}
My intention is to have a jointable in the database and each time the getValidation() is called in my services the records get returned ordered by validation_id. Now to test my functionality I make use of DbUnit. Each time I start a testclass my database gets created and hibernate creates my tables afterwhich DbUnit fills them with data. When I comment #OrderBy my tests pass but when I uncomment it, I get table infotype_validations can't be found. I've looked at the available documentation online and it seems it is perfectly possible to have #OrderBy in this kind of mapping. So what am I missing ?
You need to use the field name not the column name.
//Assuming the field is validationId
#OrderBy(value="validationId desc")
public Set<Validation> getValidation() {
return validation;
}
Also make sure that the infotype_validations table exists within your database and the spelling matches.

How can read-only collections be mapped in JPA / Hibernate that don't cause DB updates

is it possible to create relations in hibernate / jpa that are fetched when the containing entity is fetched but will never ever result in any db updates, when the containing entity is saved? I'll try to make the requirement clear by an example.
I have a simple entity B
#Entity
public class B {
private int bId;
#Id
public int getBId() {
return bId;
}
public void setBId(int aId) {
bId = aId;
}
}
And another entity A, which contains a uni-directional many-to-many mapping to this class.
#Entity
public class A {
private int aId;
private List<B> bs;
#Id
public int getAId() {
return aId;
}
public void setAId(int aId) {
this.aId = aId;
}
#ManyToMany
#JoinTable(name = "A_B",
joinColumns = {#JoinColumn(name = "AID")},
inverseJoinColumns = {#JoinColumn(name = "BID")}
)
public List<B> getBs() {
return bs;
}
public void setBs(List<B> aBs) {
bs = aBs;
}
}
When entity A is fetched from db and merged afterwards as follows
A a = em.find(A.class, 1);
a.getBs().size();
em.merge(a);
, the merge results in the following SQL statements
Hibernate:
delete
from
A_B
where
AID=?
Hibernate:
insert
into
A_B
(AID, BID)
values
(?, ?)
Hibernate:
insert
into
A_B
(AID, BID)
values
(?, ?)
I have to avoid these deletes + updates. For my application I can ensure that the mapping table will never ever be updated using hibernate. Anyway, it is required to update the containing entity.
So my question is: Is it possible to map such "read-only" collections and to avoid db changes?
Best regards
Thomas
Update:
These are the tables and the data I'm using:
CREATE TABLE A (
AID INTEGER NOT NULL
)
DATA CAPTURE NONE ;
CREATE TABLE B (
BID INTEGER NOT NULL
)
DATA CAPTURE NONE ;
CREATE TABLE A_B (
AID INTEGER NOT NULL,
BID INTEGER NOT NULL
)
DATA CAPTURE NONE ;
INSERT INTO A (AID) VALUES (1);
INSERT INTO B (BID) VALUES (1);
INSERT INTO B (BID) VALUES (2);
INSERT INTO A_B (AID, BID) VALUES (1, 1);
INSERT INTO A_B (AID, BID) VALUES (1, 2);
In addition the collection also needs to be initialized before the merge is performed:
a.getBs().size();
Note: I've added the line from above to the original post, too.
As written in a comment, I couldn't initially reproduce the behavior. Without altering the collection of B, Hibernate was just updating A, leaving the join table untouched. However, by altering the collection of Bs (e.g. adding a B), I could get the DELETE then INSERT behavior. I don't know if this illustrates your scenario but here is my explanation...
When using a Collection or List without an #IndexColumn (or a #CollectionId), you get Bag semantic with all their drawbacks: when you remove an element or alter the collection, Hibernate first delete all elements and then insert (it has no way to maintain the order).
So, to avoid this behavior, either use:
Set semantic (i.e. use a Set if you don't need a List, which is true for 95% of the cases).
Bag semantic with primary key (i.e. use a List with a #CollectionId) - I didn't test this.
true List semantic (i.e. use a List with a #org.hibernate.annotations.IndexColumn or the JPA 2.0 equivalent #OrderColumn if you are using JPA 2.0)
The Option 1 is the obvious choice if you don't need a List. If you do, I only tested Option 3 (feels more natural) that you would implement like this (requires an extra column and a unique constraint on (B_ID, BS_ORDER) in the join table):
#Entity
public class A {
private int aId;
private List<B> bs;
#Id
public int getAId() { return aId; }
public void setAId(int aId) { this.aId = aId; }
#ManyToMany
#JoinTable(name = "A_B",
joinColumns = {#JoinColumn(name = "AID")},
inverseJoinColumns = {#JoinColumn(name = "BID")}
)
#org.hibernate.annotations.IndexColumn(name = "BS_ORDER")
public List<B> getBs() { return bs; }
public void setBs(List<B> aBs) { bs = aBs; }
}
And Hibernate will update the BS_ORDER column as required upon update/removal of Bs.
References
Hibernate Annotations 3.4 Reference Guide
2.2.5.3. Collections
2.4.6.2. Extra collection types
Hibernate Annotations 3.5 Reference Guide
2.4.6. Collection related annotations
I've modified my test code and switched to Set as a replacement for the List. Due to this change the delete and update statements get no longer generated, as long as the collection remains unmodified. Unfortunately I have conditions in my project, where the collection is modified. Therefore I was asking for a read-only semantic where hibernate loads the mapped data from db but does not save any changes, which might have been done to the collection.
Yes, I know that this is what you were asking for but:
I thought that your concern was the DELETE then INSERT and the "read only part" of your question was looking like an ugly workaround of the real problem.
The "extra" requirement i.e. not saving the state of a persistent collection that has been modified (which is unusual, when you have a persistent collection, you usually want to save it if you modify its state) wasn't clear, at least not for me.
Anyway... Regarding the second point, Hibernate's #Immutable annotation won't fit here (it disallows all modifications, throwing an exception if any). But maybe you could work on a transient copy of the collection instead of modifying the persistent one?

How to use Hibernate #Any-related annotations?

Could someone explain to me how Any-related annotations (#Any, #AnyMetaDef, #AnyMetaDefs and #ManyToAny) work in practice. I have a hard time finding any useful documentation (JavaDoc alone isn't very helpful) about these.
I have thus far gathered that they somehow enable referencing to abstract and extended classes. If this is the case, why is there not an #OneToAny annotation? And is this 'any' referring to a single 'any', or multiple 'any'?
A short, practical and illustrating example would be very much appreciated (doesn't have to compile).
Edit: as much as I would like to accept replies as answers and give credit where due, I found both Smink's and Sakana's answers informative. Because I can't accept several replies as the answer, I will unfortunately mark neither as the answer.
Hope this article brings some light to the subject:
Sometimes we need to map an
association property to different
types of entities that don't have a
common ancestor entity - so a plain
polymorphic association doesn't do the
work.
For example let's assume three different applications which manage a media library - the first application manages books borrowing, the second one DVDs, and the third VHSs. The applications have nothing in common. Now we want to develop a new application that manages all three media types and reuses the exiting Book, DVD, and VHS entities. Since Book, DVD, and VHS classes came from different applications they don't have any ancestor entity - the common ancestor is java.lang.Object. Still we would like to have one Borrow entity which can refer to any of the possible media type.
To solve this type of references we can use the any mapping. this mapping always includes more than one column: one column includes the type of the entity the current mapped property refers to and the other includes the identity of the entity, for example if we refer to a book it the first column will include a marker for the Book entity type and the second one will include the id of the specific book.
#Entity
#Table(name = "BORROW")
public class Borrow{
#Id
#GeneratedValue
private Long id;
#Any(metaColumn = #Column(name = "ITEM_TYPE"))
#AnyMetaDef(idType = "long", metaType = "string",
metaValues = {
#MetaValue(targetEntity = Book.class, value = "B"),
#MetaValue(targetEntity = VHS.class, value = "V"),
#MetaValue(targetEntity = DVD.class, value = "D")
})
#JoinColumn(name="ITEM_ID")
private Object item;
.......
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
}
The #Any annotation defines a polymorphic association to classes from multiple tables. This type of mapping
always requires more than one column. The first column holds the type of the associated entity. The remaining
columns hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so
this is most certainly not meant as the usual way of mapping (polymorphic) associations. You should use this
only in very special cases (eg. audit logs, user session data, etc).
The #Any annotation describes the column holding the metadata information. To link the value of the
metadata information and an actual entity type, The #AnyDef and #AnyDefs annotations are used.
#Any( metaColumn = #Column( name = "property_type" ), fetch=FetchType.EAGER )
#AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
#MetaValue( value = "S", targetEntity = StringProperty.class ),
#MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
#JoinColumn( name = "property_id" )
public Property getMainProperty() {
return mainProperty;
}
idType represents the target entities identifier property type and metaType the metadata type (usually String).
Note that #AnyDef can be mutualized and reused. It is recommended to place it as a package metadata in this
case.
//on a package
#AnyMetaDef( name="property"
idType = "integer",
metaType = "string",
metaValues = {
#MetaValue( value = "S", targetEntity = StringProperty.class ),
#MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
package org.hibernate.test.annotations.any;
//in a class
#Any( metaDef="property", metaColumn = #Column( name = "property_type" ), fetch=FetchType.EAGER )
#JoinColumn( name = "property_id" )
public Property getMainProperty() {
return mainProperty;
}
#ManyToAny allows polymorphic associations to classes from multiple tables. This type of mapping always requires
more than one column. The first column holds the type of the associated entity. The remaining columns
hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so this is most
certainly not meant as the usual way of mapping (polymorphic) associations. You should use this only in very
special cases (eg. audit logs, user session data, etc).
#ManyToAny(
metaColumn = #Column( name = "property_type" ) )
#AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
#MetaValue( value = "S", targetEntity = StringProperty.class ),
#MetaValue( value = "I", targetEntity = IntegerProperty.class ) } )
#Cascade( { org.hibernate.annotations.CascadeType.ALL } )
#JoinTable( name = "obj_properties", joinColumns = #JoinColumn( name = "obj_id" ),
inverseJoinColumns = #JoinColumn( name = "property_id" ) )
public List<Property> getGeneralProperties() {
Src: Hibernate Annotations Reference Guide 3.4.0GA
Hope it Helps!
The #Any annotation defines a polymorphic association to classes from multiple tables, right, but polymorphic associations such as these are an SQL anti-pattern! The main reason is that you canĀ“t define a FK constraint if a column can refer to more than one table.
One of the solutions, pointed out by Bill Karwin in his book, is to create intersection tables to each type of "Any", instead of using one column with "type", and using the unique modifier to avoid duplicates. This solution may be a pain to work with JPA.
Another solution, also proposed by Karwin, is to create a super-type for the connected elements. Taking the example of borrowing Book, DVD or VHS, you could create a super type Item, and make Book, DVD and VHS inherit from Item, with strategy of Joined table. Borrow then points to Item. This way you completely avoid the FK problem. I translated the book example to JPA bellow:
#Entity
#Table(name = "BORROW")
public class Borrow{
//... id, ...
#ManyToOne Item item;
//...
}
#Entity
#Table(name = "ITEMS")
#Inheritance(strategy=JOINED)
public class Item{
// id, ....
// you can add a reverse OneToMany here to borrow.
}
#Entity
#Table(name = "BOOKS")
public class Book extends Item {
// book attributes
}
#Entity
#Table(name = "VHS")
public class VHS extends Item {
// VHSattributes
}
#Entity
#Table(name = "DVD")
public class DVD extends Item {
// DVD attributes
}
Have you read the Hibernate Annotations documentation for #Any? Haven't used that one myself yet, but it looks like some extended way of defining references. The link includes an example, though I don't know if it's enough to fully understand the concept...

Categories

Resources