Hibernate : Sorting ManyToMany mapping - java

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.

Related

How to Inner join with #ManyToMany created Table in entity ( Spring Boot)

I use the H2 database and spring boot for this task, I have two entities.
The First entity is:
public class e1{
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "t12",
joinColumns = { #JoinColumn(name = "e1_ID")},
inverseJoinColumns = { #JoinColumn(name = "e2_ID")})
private Set<Game> listSet1 = new HashSet<>();
}
The second entity is:
public class e2{
#ManyToMany(mappedBy = "listSet1")
private Set<Player> listSet2 = new HashSet<>();
}
After running the application we have a table called "t12".
Now, I have a question, I would like to know, How to join with this table I mean ( #JoinTable t12) without an entity from the e2 class.
I mean, when we are running an application Spring use this annotation #JoinTable to create a table for two entities. Now after created this table I want to join with them. but the table ( I mean #JoinTable name "X") has no entity. I want to use Inner join with this table and one of my entities.
Thank you all.
What exactly do you need to do?
Spring Data JPA lets you write native SQL queries using the #Query annotation with nativeQuery = true.
e.g. In your Repository interface, you can try something like:
#Query(
value = "SELECT * FROM e1 INNER JOIN t12 ON t12.e1_ID = e1.id WHERE t12.e2_ID = :e2ID",
nativeQuery=true)
Collection<e1> findAllByE2ID(Long e2ID);
Some useful resources:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#_native_queries
https://www.baeldung.com/spring-data-jpa-query#2-native
https://attacomsian.com/blog/spring-data-jpa-many-to-many-mapping
Please take a look at some naming conventions in java and Spring Boot:
https://www.oracle.com/java/technologies/javase/codeconventions-namingconventions.html
https://www.baeldung.com/java-class-file-naming
https://www.baeldung.com/spring-data-jpa-custom-naming

In JPA, how would I find all of a type that has a property value and a ManyTomany related entity with a property value?

I want to get all the listeners that have an action with the name "Create".
I have the following entities (simplified for this question):
#Entity
public class Listener
{
...elided...
#ManyToMany(targetEntity = Action.class, fetch = FetchType.EAGER)
#JoinTable(
name = "ListenerActions",
joinColumns = #JoinColumn( name = "listenerId", referencedColumnName = "id" ),
inverseJoinColumns = #JoinColumn( name = "actionId", referencedColumnName = "id" )
)
List<Action> actions;
}
#Entity
public class Action
{
...elided...
private String name;
}
How do I filter on the list of actions? I only need one of the actions in a Listener's "actions" to have the name "Create".
A path expression in a JPA 2 query can navigate to a collection-valued field, including across a one-to-many or many-to-many relationship, but that must be the last step in the path; JPA does not permit you to write a path expression that navigates further from there. Therefore, to perform the kind of filtering you want to do, you need to perform a join or create a subquery. The JPA 2.0 specifications provide this example of the former:
SELECT DISTINCT o
FROM Order o JOIN o.lineItems l
WHERE l.product.productType = 'office_supplies'
, which has the form you want.
Adapting that to your schema is left as an exercise.

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;

#OrderColumn generates a request to update the primary key

I use a list. The list is comprised of a compound primary key which is also used for sorting the list.
The problem is that if I delete an element in the list (key compound),
annotation #OrderColumn generates a request to update a primary key, and the cost rises an exception of type:
[26-05-2011 10:34:18:835] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000
[26-05-2011 10:34:18:835] ERROR org.hibernate.util.JDBCExceptionReporter -Duplicate entry '10-10' for key 'PRIMARY'
[26-05-2011 10:34:18:835] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
Here is the definition of the mapping :
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "chapter_item", joinColumns = { #JoinColumn(name = "chapter_id", nullable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "item_id", nullable = false, updatable = false) })
#OrderColumn(name="iorder")
public List<Item> getItems() {
return items;
}
Here is the update query where I have a problem:
Hibernate:
update
chapter_item
set
item_id=?
where
chapter_id=?
and iorder=?
I wonder if this is a known bug, and if anyone has a solution?
Regarding #OrderColumn, following documentation is found at http://docs.oracle.com/javaee/6/api/javax/persistence/OrderColumn.html
Specifies a column that is used to maintain the persistent order of a
list. The persistence provider is responsible for maintaining the
order upon retrieval and in the database. The persistence provider is
responsible for updating the ordering upon flushing to the database to
reflect any insertion, deletion, or reordering affecting the list.
So we see that the persistence provider i.e. hibernate is responsible for updating the column named iorder. It is also said that:
The OrderColumn annotation is specified on a OneToMany or ManyToMany
relationship or on an element collection. The OrderColumn annotation
is specified on the side of the relationship that references the
collection that is to be ordered. The order column is not visible as
part of the state of the entity or embeddable class.
Please take note of the sentence that says:
The order column is not visible as part of the state of the entity or
embeddable class.
So, may I suggest you to consider not selecting the column iorder for #OrderColumn since it is a part of your composite key and hibernate is sure to update this value when you delete or insert an element in list (List<Item>).
Hope, this helps.
Maybe one option could be change the order annotation and use:
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "chapter_item", joinColumns = { #JoinColumn(name = "chapter_id", nullable = false, updatable = false) }, inverseJoinColumns = {#JoinColumn(name = "item_id", nullable = false, updatable = false) })
#org.hibernate.annotations.Sort(type = SortType.COMPARATOR, comparator = ItemComparator)
public List<Item> getItems() {
return items;
}
https://dzone.com/articles/sorting-collections-hibernate
Check performance solution because maybe is too slow for big amount of data, and if you can share if was a possible solution would be great to know

Hibernate - #ElementCollection - Strange delete/insert behavior

#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.

Categories

Resources