Problem description
Hello, I've been using OpenJPA 2.1.2 (JPA 2.0) to retrieve data from a table called LOGISCHRAPPORTwhich has about 28 columns. In some cases OpenJPA decides to issue seperate select statements for one of the columns for no particular reason, leading to a dramatic decrease in query performance.
Everything is fine until...
Initially everything goes fine and all my columns are retrieved in a performant, single SELECT statement by JPA.
As soon as I add a relationship to another entity called RAPTAALMETADATA
#OneToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "RAPPORTNR", referencedColumnName = "RAPPORTNR"),
#JoinColumn(name = "RAPPORTTYPE", referencedColumnName = "RAPPORTTYPE") })
private List<Raptaalmetadata> raptaalmetadata;
---
Queried using Criteria API as follows:
---
Join<LogischRapport, Raptaalmetadata> metadata = reportRoot.join(
"raptaalmetadata");
JPA no longer includes one of my original columns called REPORT_COMMENTS instead it is issuing separate select statements to retrieve the REPORT_COMMENTS column for each instance of LOGISCHRAPPORT. All other columns (including the ones coming from RAPTAALMETADATA are retrieved properly as part of the intial SELECT.
REPORT_COMMENTS is of the HUGEBLOB type in Oracle and I've mapped in in my Entity as follows:
#Lob
#Basic
#Column(name = "REPORT_COMMENTS")
private byte[] reportComments;
I now get tons of these:
SELECT t0.REPORT_COMMENTS
FROM dwhsd001.LogischRapport t0
WHERE t0.rapportnr = ? AND t0.rapporttype = ?
[params=(long) 1473, (String) RAP]
Can anyone point out why I might be seeing this behavior and how I can avoid it?
Its not a LAZY loading problem as I've specifically tested that case. I don't see any other reason why OpenJPA decides to retrieve this one column using separate statements.
Update 1
Interesting addition: as soon as I remove the fetch=FetchType.EAGER attribute from the #OneToMany annotation described above I start seeing the exact same behavior for the relationship as I've been getting for the REPORT_COMMENTS column. This means I'm also getting separate SELECT statements for retrieving the entity relationship on top of the seperate selects for the column thereby further degrading performance.
In other words I'm then also getting tons of these:
SELECT t0.isotaalcode, t0.rapportnr, t0.rapporttype,
t0.FUNCDESC_MODIFIED_BY, t0.FUNCDESC_MODIFIED_DATE,
t0.FUNCTIONAL_DESCRIPTION, t0.omschrijving, t0.titel
FROM dwhsd001.Raptaalmetadata t0
WHERE t0.rapportnr = ? AND t0.rapporttype = ?
Related
I would like someone to explain me why Hibernate is making one extra SQL statement in my straight forward case. Basically i have this object:
#Entity
class ConfigurationTechLog (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
val configurationId: Long,
val type: String,
val value: String?
) {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "configurationId", insertable = false, updatable = false)
val configuration: Configuration? = null
}
So as you can see, nothing special there. And when i execute this query :
#Query(value = "SELECT c FROM ConfigurationTechLog c where c.id = 10")
fun findById10() : Set<ConfigurationTechLog>
In my console i see this:
Hibernate:
/* SELECT
c
FROM
ConfigurationTechLog c
where
c.id = 10 */ select
configurat0_.id as id1_2_,
configurat0_.configuration_id as configur2_2_,
configurat0_.type as type3_2_,
configurat0_.value as value4_2_
from
configuration_tech_log configurat0_
where
configurat0_.id=10
Hibernate:
select
configurat0_.id as id1_0_0_,
configurat0_.branch_code as branch_c2_0_0_,
configurat0_.country as country3_0_0_,
configurat0_.merchant_name as merchant4_0_0_,
configurat0_.merchant_number as merchant5_0_0_,
configurat0_.org as org6_0_0_,
configurat0_.outlet_id as outlet_i7_0_0_,
configurat0_.platform_merchant_account_name as platform8_0_0_,
configurat0_.store_type as store_ty9_0_0_,
configurat0_.terminal_count as termina10_0_0_
from
configuration configurat0_
where
configurat0_.id=?
Can someone please explain me, what is happening here ? From where this second query is coming from ?
I assume you are using Kotlin data class. The kotlin data class would generate toString, hashCode and equals methods utilizing all the member fields. So if you are using the returned values in your code in a way that results in calling of any of these method may cause this issue.
BTW, using Kotlin data claases is against the basic requirements for JPA Entity as data classes are final classes having final members.
In order to make an association lazy, Hibernate has to create a proxy instance instead of using the real object, i.e. it needs to create an instance of dynamically generated subclass of the association class.
Since in Kotlin all classes are final by default, Hibernate cannot subclass it so it has to create the real object and initialize the association right away. In order to verify this, try declaring the Configuration class as open.
To solve this without the need to explicitly declare all entities open, it is easier to do it via the kotlin-allopen compiler plugin.
This Link can be useful for understand what kind (common) problem is that N + 1 Problem
Let me give you an example:
I have three Courses and each of them have Students related.
I would like to perform a "SELECT * FROM Courses". This is the first query that i want (+ 1) but Hibernate in background, in order to get details about Students for each Course that select * given to us, will execute three more queries, one for each course (N, there are three Course coming from select *). In the end i will see 4 queries into Hibernate Logs
Considering the example before, probably this is what happen in your case: You execute the first query that you want, getting Configuration Id = 10 but after, Hibernate, will take the entity related to this Configuration, then a new query is executed to get this related entity.
This problem should be related in specific to Relationships (of course) and LAZY Fetch. This is not a problem that you have caused but is an Hibernate Performance Issue with LAZY Fetch, consider it a sort of bug or a default behaviour
To solve this kind of problem, i don't know if will be in your case but ... i know three ways:
EAGER Fetch Type (but not the most good option)
Query with JOIN FETCH between Courses and Students
Creating EntityGraph Object that rappresent the Course and SubGraph that rappresent Students and is added to EntityGraph
Looking at your question, it seems like an expected behavior.
Since you've set up configuration to fetch lazily with #ManyToOne(fetch = FetchType.LAZY), the first sql just queries the other variables. When you try to access the configuration object, hibernate queries the db again. That's what lazy fetching is. If you'd like Hibernate to use joins and fetch all values at once, try setting #ManyToOne(fetch = FetchType.EAGER).
I am yet again stuck with trying to delete data with Hibernate..
I am at point where I am starting to just stack annotation, hoping something would work... so far nothing has.
I need to delete old calculation when starting new for same time period.
I have the following query
#Modifying
#QueryHints(value = #QueryHint(name = HINT_FETCH_SIZE, value = "10"))
#Query(value = "DELETE FROM Group a WHERE a.c_date BETWEEN :dateA AND :dateB")
void deleteOld(#Param("dateA") LocalDate dateA, #Param("dateB") LocalDate dateB);
which uses entity Group, which has (on top of normal String, LocalDate and long types) attribute
#OneToMany(cascade=CascadeType.ALL, mappedBy = "owner", orphanRemoval = true)
#JsonManagedReference
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Instrument> instruments = new ArrayList<>();
But I still get violated - child record found every time I try to run delete method.
I keep finding more and more annotations like this, from threads where people have the same kind of problems, but I would love to understand why is this a problem. From what I read Cascade and orphanRemoval should be all I need, but it sure does not seem to be.
Hibernate: 5.2.17.Final
Please help me to understand, why is this happening ?
The #OnDelete will delete records using a ON DELETE rule on the database, when using Hibernate to generate the schema. If you manage your own schema this will have no effect.
The #QueryHints you have specified doesn't really make sense here, for an DELETE query that is. Nothing will be fetched.
The fact that you are using an #Query basically bypasses the configuration in the #OneToMany, simply due to the fact that you write a query and apparently know what you are doing. So the mapping isn't taken into account.
If you want to delete the childs as then you have 3 options:
Add an additional query and first remove the childs, then the parents
Add an ON DELETE rule to your database, to automatically remove the childs
Retrieve the Group and remove using EntityManager.remove which will take the #OneToMany mappings into account as now Hibernate needs to manage the dependencies between the entities.
Ran into this issue today and I was not able to find any solution anywhere else. Actual entities in my application are much bigger, so here is a simplified version of the problem I am facing. I have an entity which has multiple child entities:
public class ParentEntity {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parentEntity", cascade = CascadeType.ALL)
#Filter(name = "childEntity_1_filter", condition="state = 'ACTIVE'")
private Set<ChildEntity_1> childEntity_1;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parentEntity", cascade = CascadeType.ALL)
#Filter(name = "childEntity_2_filter", condition = "entity_name not in ('x', 'y')")
private Set<ChildEntity_2> childEntity_2;
}
Now I am fetching the parent entity from the database using Hibernate Criteria.
So I specify both the joins and the filters from above, and then I specify the FetchMode for all these associations as FetchMode.JOIN.
This is where things don't work as expected. My records were not being returned properly and I debugged the SQL generated by hibernate. I saw something like:
# Note that this is hibernate generated sql
select
........
from
parent_entity pe,
childentity_1 ce1,
childentity_2 ce2
where
pe.id=ce1.id(+) and
ce1.state (+)= 'ACTIVE' and -- Notice the (+) here
pe.id=ce2.id(+) and
ce2.entity_name not in ('x', 'y'); -- Notice that (+) is missing here
As you can see above, the filter on the first child entity has the outer join properly applied to the query, while the "not in" filters do not have this join applied to them.
This is causing my query to not return records in some cases. Eg: If there are entities corresponding to the parent entity in the childentity_2 table, but those entities have entity_name = 'x'. This means that even though there are records after the join (since it is an outer join), all of them will be filtered out with the not in filter.
I tried changing the filter to use not equals condition:
#Filter(name = "childEntity_2_filter", condition = "entity_name != 'x' and ...")
In this case hibernate tried to add the (+), but failed spectacularly. It added the (+) between the ! and =. I tried various other combinations of the query like <>, but could not get anything to do what I wanted.
Just to be clear, I know how to fix the query. My question is how to tell hibernate to fix the query.
How do I fix the above #Filter annotation? My problem is that I have a lot of child entities which all have the equals #Filter and they all work fine, but hibernate is not generating the correct query when using not in #Filter.
I'm trying to understand EclipseLink behaviour in case if I use native query. So I have Entity like this:
class Entity {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="other_entity_id")
private OtherEntity otherEntity;
#Column(name = "name")
private String name;
//gets ... sets ...
}
and corresponding table looks like:
**ENTITY**
INTEGER ID;
VARCHAR NAME;
OTHER_ENTITY_ID;
And then I run native query
Query query = getEntityManager().runNativeQuery("select * from ENTITY", Entity.class);
query.getResultList()
Within Entity I have declared OtherEntity otherEntity which is annotated with FetchType.LAZY, however my query selects (*) - all of the columns, including OTHER_ENTITY_ID. The question is - if I run native query that fetches all columns, will fields annotated with FetchType.LAZY populated as if they were FetchType.EAGER or not? I've never worked with EclipseLink before and tyring to decide is it worth using it or not so I would really appreciate any help
Thanks, Cheers
My first advice is to turn on EclipseLink's SQL logging, and execute the equivalent JPQL to load what you are looking for and see the SQL EclipseLink generates to accomplish that to get an understanding of what is required to build objects in your native queries based on your current mappings.
Relationships generally loaded with a secondary query using the values read in from the foreign keys, so eager or lazy fetching is not affected by the native query to read in "Entity" - the query requires the other_entity_id value regardless of the fetch type. When required based on eager/lazy loading, EclipseLink will issue the query required by the mapping.
You can change this though by marking that the relationship is to use joining. In this case, EclipseLink will expect not only the Entity values to be in the query, but the referenced OtherEntity values as well.
I have these classes:
#Entity
public class Invoice implements Serializable {
#Id
#Basic(optional = false)
private Integer number;
private BigDecimal value;
//Getters and setters
}
#Entity
public class InvoiceItem implements Serializable {
#EmbeddedId
protected InvoiceItemPK invoiceItemPk;
#ManyToOne
#JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
//Getters and setters
}
When i run this query:
session.createQuery("select i from InvoiceItem i").list();
It executes one query to select the records from InvoiceItem, and if I have 10000 invoice items, it generates 10000 additional queries to select the Invoice from each InvoiceItem.
I think it would be a lot better if all the records could be fetched in a single sql. Actually, I find it weird why it is not the default behavior.
So, how can I do it?
The problem here is not related to Hibernate but to JPA.
Prior to JPA 1.0, Hibernate 3 used lazy loading for all associations.
However, the JPA 1.0 specification uses FetchType.LAZY only for collection associations:
#OneToMany,
#ManyToMany
#ElementCollection)
The #ManyToOne and #OneToOne associations use FetchType.EAGER by default, and that's very bad from a performance perspective.
The behavior described here is called the [N+1 query issue][5], and it happens because Hibernate needs to make sure that the #ManyToOne association is initialized prior to returning the result to the user.
Now, if you are using direct fetching via entityManager.find, Hibernate can use a LEFT JOIN to initialize the FetchTYpe.EAGER associations.
However, when executing a query that does not explicitly use a JOIN FETCH clause, Hibernate will not use a JOIN to fetch the FetchTYpe.EAGER associations, as it cannot alter the query that you already specified how to be constructed. So, it can only use secondary queries.
The fix is simple. Just use FetchType.LAZY for all associations:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
More, you should use the Hypersistence Utils to assert the number of statements executed by JPA and Hibernate.
Try with
session.createQuery("select i from InvoiceItem i join fetch i.invoice inv").list();
It should get all the data in a single SQL query by using joins.
Yes there is setting you need: #BatchSize(size=25). Check it here:
20.1.5. Using batch fetching
small cite:
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 you can configure batch fetching: on the class level and the collection level.
Batch fetching for classes/entities is easier to understand. Consider the following example: at runtime you have 25 Cat instances loaded in a Session, and each Cat has a reference to its owner, a Person. The Person class is mapped with a proxy, lazy="true". If you now iterate through all cats and call getOwner() on each, Hibernate will, by default, execute 25 SELECT statements to retrieve the proxied owners. You can tune this behavior by specifying a batch-size in the mapping of Person:
<class name="Person" batch-size="10">...</class>
With this batch-size specified, Hibernate will now execute queries on demand when need to access the uninitialized proxy, as above, but the difference is that instead of querying the exactly proxy entity that being accessed, it will query more Person's owner at once, so, when accessing other person's owner, it may already been initialized by this batch fetch with only a few ( much less than 25) queries will be executed.
So, we can use that annotation on both:
collections/sets
classes/Entities
Check it also here:
#BatchSize but many round trip in #ManyToOne case
In this Method there are Multiple SQLs fired. This first one is fired for retrieving all the records in the Parent table. The remaining are fired for retrieving records for each Parent Record. The first query retrieves M records from database, in this case M Parent records. For each Parent a new query retrieves Child.