#Where with #SecondaryTable does not work with Hibernate - java

There is a one-to-many relationship in my model, where the child entity is stored in two tables.
#Entity
#Table(name = "child1")
#SecondaryTable(name = "child2", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "id1", referencedColumnName = "id1"),
#PrimaryKeyJoinColumn(name = "id2", referencedColumnName = "id2")})
#Where(clause = "col1 is not null and col2 is not null")
#Data
#Immutable
public class Child implements Serializable {...}
Child entity is fetched eagerly together with Parent entity. Problem lies within #Where clause, which should reference columns from two tables: col1 is in table child1 and col2 is in child2. This throws the following error:
ERROR 12333 --- [nio-8183-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper : Column (col2) not found in any table in the query (or SLV is undefined).
java.sql.SQLException: null
...
Using only: #Where(clause = "col1 is not null") gives propper mapping and results in no error.
Using #Where(clause = "child1.col1 is not null and child2.col2 is not null") gives the following error:
Column (child1) not found in any table in the query (or SLV is undefined).
How can I make #Where work with two tables or is there any workaround?
There are some requirements though:
I'm using informix as an underlying database and have read-only access.
I know, that it can be solved by native SQL or even JPQL / criteria API and so on, but doing so would make me rewrite a lot of core. I want to avoid it.

This is due to the HHH-4246 issue.
A workaround would be to replace the #SecondaryTable with a #OneToOne association using #MapsId.
This way, the child2 table becomes the Child2 entity for which you can use #Where or #Filter.

Related

JPA Hibernate OneToOne children enity always fetches parent entity

I am having a problem with a OneToOne relationship.
Trying to load a children entity will always result in an additional query that loads its parent, even if I don't need the parent.
I have already read to articles about it but can't figure it out:
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one/
My structure is quite similiar to their examples above. I've got a parent entity with a OneToOne relationship to a childen. My mapping is already unidirectional. So only the children has the mapping information and the parent has no children mappings at all. I am using the #MapsId annotation and have a shared PK/FK to the parent.
Short information about my entity structure:
#Entity
#Table(name = "parent")
public class ParentEntity
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ParentSequence")
private Long id;
#Entity
#Table(name = "children")
public class ChildrenEntity
#Id
private Long id;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private ParentEntity parent;
My issue is, that the children always fetches the parent eager and creating a second query for that reason.
Examples:
#1 via Entity Manager find
entityManager.find(ChildrenEntity.class, parentId);
#2 via JPA Query
entityManager.createQuery("SELECT c FROM ChildrenEntity c").getResultList()
#3 via Criteria API
var query = cb.createQuery(ChildrenEntity.class);
var root = query.from(ChildrenEntity.class);
query.select(root).where(root.get(ChildrenEntity_.id).in(Arrays.asList(parent.getId())));
entityManager.createQuery(query).getResultList();
All three examples create more then one query. The additional query always load the whole parent entity.
Here a slightly cleaned up server output:
[0m[0m14:40:29,411 INFO Hibernate: select childrenentity0_.parent_id, childrenentity0_.color, from children childrenentity0_
[0m[0m14:40:29,413 INFO Hibernate: select parententity0_.id, parententity0_.additionalname from parent parententity0_ where parententity0_.id=?
Does anyone has an idea how to prevent loading of the parent entity? I do not really need it as the id is more then enough here.
I already found out, that I can use a .fetch statement in the criteria api. That helps reducing is to one query but that will add all parent fields to the select of the first query.
-- EDIT 22/05/09 --
Related Stackoverflow entry: JPA/Hibernate double select on OneToOne Find
Currently I only could find two possible solutions:
#1 Use a join fetch on ChildrenEntity to fetch the ParentEntity
This would work but joins a lot additional columns that are unneeded
#2 Do not select Entities but use a Tuple/Constructor query
-- EDIT 22/05/11 --
Looks like there is also a bug in various hibernate versions where the MapsId annotation does not work as expected.
https://hibernate.atlassian.net/browse/HHH-10771

Hibernate throws error after migration (references on same collection)

I have a problem with using a OneToMany-Mapping with Hibernate that was working in version 4.3.8.Final but is not with version 5.4.2.Final .
When I query multiple entities from the db that have the same entries in a list that is mapped with OneToMany and then try to update those entities, hibernate throws the following exception even though I do not update any entries from the mapped relation: "Found shared reference to a collection: de.Artikel.filialeLager; nested exception is org.hibernate.HibernateException: Found shared reference to a collection: de.Artikel.filialeLager"
Here is my mapping, that was working fine with version 4.3.8.Final of Hibernate but is not anymore with 5.4.2.Final.:
#OneToMany(fetch = LAZY)
#JoinColumn(name = "SOME_ID", referencedColumnName="SOME_ID", insertable = false, updatable = false)
private List<filialeLager> filialeLager;
It seems to have a unidirectional relationship but is adding a reference column for a two-way relationship
The name of the column referenced by this foreign key column. When
used with entity relationship mappings other than the cases described
below, the referenced column is in the table of the target entity.
When used with a unidirectional OneToMany foreign key mapping, the
referenced column is in the table of the source entity. When used
inside a Join- Table annotation, the referenced key column is in the
entity table of the owning entity, or inverse entity if the join is
part of the inverse join definition. When used in a collection table
mapping, the referenced column is in the table of the entity
containing the collection.
You should have something similar to this:
#OneToMany(fetch = LAZY)
#JoinColumn(name = "SOME_ID", insertable = false, updatable = false)
private List<filialeLager> filialeLager;

#Where annotation in hibernate on child table

This is my schema:
event_details:
- id (PK)
- name
- description
event_ticket_types:
- id (PK)
- event_id (References id (event_details)
- ticket_name
EventDetail
//bi-directional many-to-one association to EventTicketType
#OneToMany(mappedBy="eventDetail", cascade = CascadeType.ALL)
private Set<EventTicketType> eventTicketTypes;
in my EventDetail entity, I have added #Where(clause = "deleted_at is NULL")
I've also added the same in EventTicketType
This is how I am getting the data in service.
EventDetail eventDetail = eventDetailRepository.getById(eventId);
Set<EventTicketType> eventTicketTypes = eventDetail.getEventTicketTypes();
And then I am mapping entity and dto.
In the query log - I see that parent table (event_details) executes query with where condition i.e where deleted_at is NULL, but the child table (event_ticket_types) does not have a where condition
Where is it that I am doing wrong?
You're not showing where the #Where annotations are but I'm guessing they're both at class level only. Try also adding the #Where annotation to the #OneToMany method above.
The annotation works at entity level when you select the entity directly, but you need it on the relationship method also if you want it to work for joins.

What is the solution for the N+1 issue in JPA and Hibernate?

I understand that the N+1 problem is where one query is executed to fetch N records and N queries to fetch some relational records.
But how can it be avoided in Hibernate?
The problem
The N+1 query issue happens when you forget to fetch an association and then you need to access it.
For instance, let's assume we have the following JPA query:
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
where pc.review = :review
""", PostComment.class)
.setParameter("review", review)
.getResultList();
Now, if we iterate the PostComment entities and traverse the post association:
for(PostComment comment : comments) {
LOGGER.info("The post title is '{}'", comment.getPost().getTitle());
}
Hibernate will generate the following SQL statements:
SELECT pc.id AS id1_1_, pc.post_id AS post_id3_1_, pc.review AS review2_1_
FROM post_comment pc
WHERE pc.review = 'Excellent!'
INFO - Loaded 3 comments
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 1
INFO - The post title is 'Post nr. 1'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 2
INFO - The post title is 'Post nr. 2'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 3
INFO - The post title is 'Post nr. 3'
That's how the N+1 query issue is generated.
Because the post association is not initialized when fetching the PostComment entities, Hibernate must fetch the Post entity with a secondary query, and for N PostComment entities, N more queries are going to be executed (hence the N+1 query problem).
The fix
The first thing you need to do to tackle this issue is to add [proper SQL logging and monitoring][1]. Without logging, you won't notice the N+1 query issue while developing a certain feature.
Second, to fix it, you can just JOIN FETCH the relationship causing this issue:
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
join fetch pc.post p
where pc.review = :review
""", PostComment.class)
.setParameter("review", review)
.getResultList();
If you need to fetch multiple child associations, it's better to fetch one collection in the initial query and the second one with a secondary SQL query.
How to automatically detect the N+1 query issue
This issue is better to be caught by integration tests.
You can use an automatic JUnit assert to validate the expected count of generated SQL statements. The db-util project already provides this functionality, and it's open-source and the dependency is available on Maven Central.
Suppose we have a class Manufacturer with a many-to-one relationship with Contact.
We solve this problem by making sure that the initial query fetches all the data needed to load the objects we need in their appropriately initialized state. One way of doing this is using an HQL fetch join. We use the HQL
"from Manufacturer manufacturer join fetch manufacturer.contact contact"
with the fetch statement. This results in an inner join:
select MANUFACTURER.id from manufacturer and contact ... from
MANUFACTURER inner join CONTACT on MANUFACTURER.CONTACT_ID=CONTACT.id
Using a Criteria query we can get the same result from
Criteria criteria = session.createCriteria(Manufacturer.class);
criteria.setFetchMode("contact", FetchMode.EAGER);
which creates the SQL :
select MANUFACTURER.id from MANUFACTURER left outer join CONTACT on
MANUFACTURER.CONTACT_ID=CONTACT.id where 1=1
in both cases, our query returns a list of Manufacturer objects with the contact initialized. Only one query needs to be run to return all the contact and manufacturer information required
for further information here is a link to the problem and the solution.
Native solution for 1 + N in Hibernate, is called:
20.1.5. Using batch fetching
Using batch fetching, Hibernate can load several uninitialized proxies if one proxy is accessed. Batch fetching is an optimization of the lazy select fetching strategy. There are two ways we can configure batch fetching: on the 1) class level and the 2) collection level...
Check these Q & A:
#BatchSize but many round trip in #ManyToOne case
Avoiding n+1 eager fetching of child collection element association
With annotations we can do it like this:
A class level:
#Entity
#BatchSize(size=25)
#Table(...
public class MyEntity implements java.io.Serializable {...
A collection level:
#OneToMany(fetch = FetchType.LAZY...)
#BatchSize(size=25)
public Set<MyEntity> getMyColl()
Lazy loading and batch fetching together represent optimization, which:
does not require any explicit fetching in our queries
will be applied on any amount of references which are (lazily) touched after the root entity is loaded (while explicit fetching effects only these named in query)
will solve issue 1 + N with collections (because only one collection could be fetched with root query) without need to farther processing To get DISTINCT root values (check: Criteria.DISTINCT_ROOT_ENTITY vs Projections.distinct)
You can even get it working without having to add the #BatchSize annotation everywhere, just set the property hibernate.default_batch_fetch_size to the desired value to enable batch fetching globally. See the Hibernate docs for details.
While you are at it, you will probably also want to change the BatchFetchStyle, because the default (LEGACY) is most likely not what you want. So a complete configuration for globally enabling batch fetching would look like this:
hibernate.batch_fetch_style=PADDED
hibernate.default_batch_fetch_size=25
Also, I'm suprised that one of the proposed solutions involves join-fetching. Join-fetching is rarely desirable because it causes more data to be transferred with every result row, even if the dependent entity has already been loaded into the L1 or L2 cache. Thus I would recommend to disable it completey by setting
hibernate.max_fetch_depth=0
This is a frequently asked question so I created the article Eliminate Spring Hibernate N+1 Queries to detail the solutions
To help you detect all the N+1 queries in your application and avoid adding more queries, I created the library spring-hibernate-query-utils that auto-detects the Hibernate N+1 queries.
Here is some code to explain how to add it to your application:
Add the library to your dependencies
<dependency>
<groupId>com.yannbriancon</groupId>
<artifactId>spring-hibernate-query-utils</artifactId>
<version>1.0.0</version>
</dependency>
Configure it in your application properties to return exceptions, default is error logs
hibernate.query.interceptor.error-level=EXCEPTION
If you are using Spring Data JPA to implement your repositories, you can specify lazy fetching in the JPA associations:
#Entity
#Table(name = "film", schema = "public")
public class Film implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "language_id", nullable = false)
private Language language;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "film")
private Set<FilmActor> filmActors;
...
}
#Entity
#Table(name = "film_actor", schema = "public")
public class FilmActor implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "film_id", nullable = false, insertable = false, updatable = false)
private Film film;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "actor_id", nullable = false, insertable = false, updatable = false)
private Actor actor;
...
}
#Entity
#Table(name = "actor", schema = "public")
public class Actor implements Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "actor")
private Set<FilmActor> filmActors;
...
}
And add #EntityGraph to your Spring Data JPA-based repository:
#Repository
public interface FilmDao extends JpaRepository<Film, Integer> {
#EntityGraph(
type = EntityGraphType.FETCH,
attributePaths = {
"language",
"filmActors",
"filmActors.actor"
}
)
Page<Film> findAll(Pageable pageable);
...
}
My blog post at https://tech.asimio.net/2020/11/06/Preventing-N-plus-1-select-problem-using-Spring-Data-JPA-EntityGraph.html helps you preventing the N+1 select problem using Spring Data JPA and #EntityGraph.
Here are some snippet codes that would help you to fix the N+1 Problem.
One to Many Relationship with Manager and Client Entity.
Client JPA Repository -
public interface ClientDetailsRepository extends JpaRepository<ClientEntity, Long> {
#Query("FROM clientMaster c join fetch c.manager m where m.managerId= :managerId")
List<ClientEntity> findClientByManagerId(String managerId);
}
Manager Entity -
#Entity(name = "portfolioManager")
#Table(name = "portfolio_manager")
public class ManagerEntity implements Serializable {
// some fields
#OneToMany(fetch = FetchType.LAZY, mappedBy = "manager")
protected List<ClientEntity> clients = new ArrayList<>();
// Getter & Setter
}
Client Entity -
#Entity(name = "clientMaster")
#Table(name = "clientMaster")
public class ClientEntity implements Serializable {
// some fields
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "manager_id", insertable = false, updatable = false)
protected ManagerEntity manager;
// Getter & Setter
}
And finally, Generate output -
Hibernate: select cliententi0_.client_id as client_id1_0_0_, cliententi0_.manager_id as manager_id2_0_0_, managerent1_.manager_id as manager_id1_2_1_, cliententi0_.created_by as created_by7_0_0_, cliententi0_.created_date as created_date3_0_0_, cliententi0_.client_name as client_name4_0_0_, cliententi0_.sector_name as sector_name5_0_0_, cliententi0_.updated_by as updated_by8_0_0_, cliententi0_.updated_date as updated_date6_0_0_, managerent1_.manager_name as manager_name2_2_1_ from client_master cliententi0_, portfolio_manager managerent1_ where cliententi0_.manager_id=managerent1_.manager_id and managerent1_.manager_id=?```

Insert fails for #OneToMany Hibernate mapping with inheritance

I'm using Java and Hibernate 3.6.4.Final.
TABLE_B is a mapping table for a specific type of A entities (B) which have a OneToMany relationship with X entities.
I'm having problems inserting a B entity in the DB.
Reading of B entities from the DB is not a problem and the related X entities are correctly loaded as well.
DB model:
TABLE_A (id, active, info)
TABLE_B (a_id, x_id)
TABLE_X (id, info)
An "A" entity might have zero or more relations with X entities.
An "A" entity with one or more relations to Xs are called "B" entities and have their own behavior in the code.
(The class and table names has been changed for simplicity)
#Entity
#Table(name = "TABLE_A")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class A {
...
}
#Entity
#Table(name="TABLE_B")
#PrimaryKeyJoinColumn(name="A_ID", referencedColumnName = "ID")
public class B extends A implements Serializable {
#OneToMany(cascade={CascadeType.ALL})
#JoinTable(
name="TABLE_B",
joinColumns = #JoinColumn( name="A_ID"),
inverseJoinColumns = #JoinColumn( name="X_ID", insertable = true, nullable = false)
)
private List<X> Xs;
...
}
The log:
SQLStatementLogger - insert into TABLE_A (active, info) values (?, ?)
SQLStatementLogger - insert into TABLE_B (A_ID) values (?)
JDBCExceptionReporter - could not insert: [com.test.B] [insert into TABLE_B (A_ID) values (?)]
java.sql.SQLException: Field 'X_ID' doesn't have a default value
I would understand if the problem is caused by specifying the entity table as "TABLE_B" and using the same for the OneToMany join table but this is what my DB model looks like.
To me it seems like Hibernate first tries to just insert the inheritance and if that would work the next insert would be the mapping between B and X. The problem is that for me the inheritance and mapping table are the same.
How should I map my DB model correctly in Hibernate? Help is much appreciated.
You can't have TABLE_B as both 'entity' table (e.g. table to which B is mapped) and 'join' table (e.g. table that holds joins between B and X).
In the first case, TABLE_B needs to have at most one record for each TABLE_A record (that is, for those As that are also Bs); in the second case TABLE_B needs to have as many records as you have X elements in B's collection which presents an obvious contradiction.
What you can do, therefore, is either of the following:
Map your Xs collection without join table (#ManyToOne on X side; #OneToMany mappedBy="X" on B side). Your 'X_TABLE' will have to have an a_id (or b_id, whatever you call it) FK to owner.
Use another table (TABLE_B_X for B-to-X mapping)

Categories