Controlling lazy/eager loading of #Formula columns dynamically - java

We have a few entities with a bunch of properties annotated with Hibernate's #Formula annotation. The SQL snippets in the annotations mainly run scalar sub-queries (e.g. COUNT queries). As an example, we have a one-to-many relationship hierarchy that's four levels deep: A <- B <- C <- D (where <- marks a one-to-many association). Pretty often when fetching an entity of type A, we'd like to know the amount of associated entities of type D. For this we use a #Formula-annotated property in A.
As we don't need these values every time, we've declared the #Formula properties as lazy-loaded (we've enabled Hibernate's bytecode enhancement to make this possible). But for some queries, we'd like to load these properties eagerly. We often load hundreds of entities of type A in one query, and it'd be important performance-wise to control the eager/lazy loading of these properties dynamically. We already use JPA's entity graphs to control which properties get loaded eagerly for certain queries, but entity graphs don't seem to work here. Even if we list the #Formula properties in the entity graph, they're still loaded lazily.
Is it possible to control lazy/eager loading of #Formula columns dynamically on a per query basis? We're currently restricted to the JPA Criteria Query API, and named queries are not a possibility here.
Update:
The properties in question are not associations to other entities, but just some calculated values. This means that e.g. fetch profiles don't apply here, as they're only applicable to entity associations (or at least that's how I understood the Hibernate manual). Here's an example of one of our #Formula properties:
#Entity
public class A {
#Basic(fetch = FetchType.LAZY)
#Formula("(select count(*) from entity_D_table where ...)")
private int associatedDCount;
...
}

You could use the Critria api to make it return a DTO instead of an Entity.
In your criteria query use a Projection to select only the column you need.
ProjectionList properties = Projections.projectionList();
properties.add(Projections.property("id").as("id"));
properties.add(Projections.property("name").as("name"));
properties.add(Projections.property("lazyField").as("lazyField"));
criteria.setProjection(properties);
criteria.setResultTransformer(new AliasToBeanResultTransformer(MyEntityDTO.class));
That way the select query will only contains the fields you ask, whatever the mapping EAGER or LAZY.

You can try to have a look at Hibernate's fetch profiles https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch20.html#performance-fetching-profiles.
You can for example annotate an entity like that
#Entity
#FetchProfile(name = "country_states", fetchOverrides = {
#FetchProfile.FetchOverride(entity = Country.class, association = "states", mode = FetchMode.JOIN)
})
public class Country implements Serializable {...
and activate the JOIN mode when querying, like this:
session=getSession();
session.beginTransaction();
//enable fetch profile for EAGER fetching
session.enableFetchProfile("country_states");
As shown in http://www.concretepage.com/hibernate/fetchprofile_hibernate_annotation

It turns out it's not hard to pull this off without having to resort to bytecode instrumentation.
Create a "formula" entity mapped to the same table:
#Entity
#Table("A")
public class ACounts {
#Id
private Long id;
#Formula("(select count(*) from entity_D_table where ...)")
private int dCount;
public int getDCount() {
return dCount;
}
}
Then in your parent entity, A, use #ManyToOne to relate lazily to this "formula" entity:
#Entity
public class A {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", nullable = false, insertable = false, updatable = false)
private ACounts counts;
public ACounts getCounts() {
return counts;
}
...
}
Now the count query will only be issued when the count is requested (i.e. it's lazy!):
A a = ...
// lazily invoke count query now:
a.getCounts().getDCount()
ref: https://stackoverflow.com/a/55581854/225217

Related

How do I make JPA entity field truly write-only

I have a case where I'm persisting a large jsonb field into a PostGres table, but do not want to read it when I fetch the entity; if I do fetch it, my service goes OOM. A better design might be to separate this into a 1 to 1 table, but I can't do that at this time.
To plead that this is not a duplicate question, here's some of my research:
I'm not able to mark the column LAZY since I have a simple column not a join`
JPA/Hibernate write only field with no read
I tried the empty setter in this suggestion, which makes sense - but it still appears to read the column and I OOM: https://www.zizka.ch/pages/programming/java/hibernate/hibernate-write-only.html
I also tried omitting the setter altogether in my #Data class: Omitting one Setter/Getter in Lombok
So, I can not see the field, but I can't seem to keep it from being read into memory in the background. It seems like there must be some simple setting in JPA or Hibernate to exclude a column from read. Before I go try to make a complex repository hierarchy just to see if it works, I thought I would ask here in case I get lucky.
Thanks in advance!
Lazy loading attributes
Hibernate can load attribute lazily, but you need to enable byte code enhancements:
First you need to set the property hibernate.enhancer.enableLazyInitialization to true
Then you can annotate the field with #Basic( fetch = FetchType.LAZY ).
Here's the example from the documentation:
#Entity
public class Customer {
#Id
private Integer id;
private String name;
#Basic( fetch = FetchType.LAZY )
private UUID accountsPayableXrefId;
#Lob
#Basic( fetch = FetchType.LAZY )
#LazyGroup( "lobs" )
private Blob image;
//Getters and setters are omitted for brevity
}
You can also enable this feature via the Hibernate ORM gradle plugin
Named Native queries
You could also decide to not map it and save/read it with a named native query. It seems a good trade off for a single attribute - it will just require an additional query to save the json.
Example:
#Entity
#Table(name = "MyEntity_table")
#NamedNativeQuery(
name = "write_json",
query = "update MyEntity_table set json_column = :json where id = :id")
#NamedNativeQuery(
name = "read_json",
query = "select json_column from MyEntity_table where id = :id")
class MyEntity {
....
}
Long id = ...
String jsonString = ...
session.createNamedQuery( "write_json" )
.setParameter( "id", id )
.setParameter( "json", jsonString )
.executeUpdate();
jsonString = (String)session.createNamedQuery( "read_json" )
.setParameter( "id", id )
.getSingleResult();
In this case, schema generation is not going to create the column, so you will need to add it manually (not a big deal, considering that there are better tools to update the schema in production).
MappedSuperclass
You can also have two entities extending the same superclass (this way you don't have to copy the attributes). They have to update the same table:
#MappedSuperclass
class MyEntity {
#Id
Long id;
String name
...
}
#Entity
#Table(name = "MyEntity_table")
class MyEntityWriter extends MyEntity {
String json
}
#Entity
#Table(name = "MyEntity_table")
class MyEntityReader extends MyEntity {
// No field is necessary here
}
Now you can use MyEntityWriter for saving all the values and MyEntityReader for loading only the values you need.
I think you will have some problems with schema generation if you try to create the tables because only one of the two will be created:
If MyEntityWriter is the first table created, then no problem
If MyEntityWriter is the second table created, the query will fail because the table already exist and the additional column won't be created.
I haven't tested this solution though, there might be something I haven't thought about.

JPA 2.1 Load extra data after object is loaded

With JPA, can I run a query after an object is loaded from the database?
For example, I have an entity that has this field:
#OneToMany(mappedBy = "widget", fetch = FetchType.EAGER, cascade = {})
#OrderBy("happenedDate DESC")
public List<Widget> getWidgets() {
return widgets;
}
This will only load all of the associated widgets. What I really want are the first 20 in the result set with the order by the happenedDate. Is there an annotation that can specify a method to run after my object is loaded from the DB so I can run a query and get limited results, something like:
#AfterDataLoaded
List<Widget> loadLast20WidgetsWidgets(){
// Do query here
}
Does this annotation or pattern exist?
You can use a combination of #EntityListener (class level) and #PostLoad (method level) to achieve your goal.
You can achieve this using the Hibernate specific, non JPA compliant #Where annotation which takes a native sql clause to limit the results loaded to an associated collection.
https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-collection
How this query would look will depend on your database support for LIMIT, TOP etc.
#OneToMany(mappedBy = "widget", fetch = FetchType.EAGER, cascade = {})
#OrderBy("happenedDate DESC")
#Where(clause = "id in (select top 20 id from table_name order by some_field desc)")
public List<Widget> getLast20Widgets() {
return widgets;
}
There is no annotation to limit the no of records fetched. To accomplish that, you will have to run a JPA query, and that would be trivial.
Its not possible since JPA/Hibernate has to manage entire collection's transition say from persistent to removed state.
You can anyways use below alternatives:
1.HQL or Native SQL
2.Criteria Query

Hibernate Criteria taking off associations

I have a class Usuario. User have association with UsuarioPerfil:
public class Usuario{
/*Attributes*/
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_usuario_perfil", referencedColumnName = "id", foreignKey = #ForeignKey(name = "fk_usuario_id_usuario_perfil"))
#Getter
#Setter
private UsuarioPerfil usuarioPerfil;
}
public class UsuarioPerfil{
/*Attributes*/
}
I am performing queries using the Criteria, as follows:
Session sessao = this.getEntityManager().unwrap(Session.class);
sessao.createCriteria(Usuario.class).list();
However, in some cases wish list does not come in the data UsuarioPerfil entity, only the User. How can I accomplish this using Hibernate Criteria?
Note: I know this is possible using Hibernate or JPA Query
I don't believe you can explicitly do what you are asking with the Hibernate Criteria API because it is generally accepted practice to make associations LAZY and set them to EAGER on a case-by-case basis.
Therefore, change your mapping to use fetch = FetchType.LAZY and then in the cases where you need the association in your query, specify it as:
criteria.setFetchMode("usuarioPerfil", FetchMode.JOIN);
If you have access to JPA's EntityGraph annotations, I would strongly suggest you look into those as well. At a minimum, you can at least look at Hibernate's FetchProfile concept because those go along way to defining fetch strategies by name which helps keep code and queries much cleaner.

JPA Hibernate unexpectedly fetches records of #OneToOne mapped entity, should I change mapping to #ManyToOne or do something else?

I have an entity with #OneToOne mapped subentity:
#Entity #Table
public class BaseEntity {
#Id
private String key;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private InnerEntity inner;
}
#Entity #Table
public class InnerEntity {
private String data;
}
It was working perfectly on persist and merge operations until I decided to fetch all records in a named query (SELECT e FROM BaseEntity e). Problems are that after calling it, Hibernate fetches all records from BaseEntity and then executes distinct queries for each InnerEntity. Because table is quite big it takes much time and takes much memory.
First, I started to investigate if getInner() is called anywhere in running code. Then I tried to change fetchType to EAGER to check if Hibernate it's going to fetch it all with one query. It didn't. Another try was to change mapping to #ManyToOne. Doing this I've added updatable/insertable=false to #JoinColumn annotation. Fetching started to work perfectly - one SELECT without any JOIN (I changed EAGER back to LAZY), but problems with updating begun. Hibernate expects InnerEntity to be persisted first, but there's no property with primary key. Of course I can do this and explicity persist InnerEntity calling setKey() first, but I would rather solve this without this.
Any ideas?
If you want inner field to be loaded on demand and your relation is #OnToOneyou can try this
#OneToOne(fetch = FetchType.LAZY, optional = false)
When using HQL hibernate doesn't consider the annotations, so you should tell it how to work.
In your case you should right the HQL like this:
SELECT e FROM BaseEntity as e left join fetch e.inner

How to retrieve nested JPA entities in a single query

I am trying to retrieve entities using eclipselink JPA and am looking for a way to reduce the number of queries run to retrieve a single entity. I believe I should be using the #JoinFetch annotation to retrieve sub-entities in the same query as the main entity. This works fine for a single level of join, but not for multiple levels.
In the example below, EntityA contains a collection of EntityB which contains an EntityC. When I retrieve EntityA, I want a single query to return all 3 sets of entity data. In reality it generates 2 queries, 1 joining EntityA and EntityB and then a separate query joining EntityB and EntityC.
Is it possible to combine this into one query?
class EntityA {
#OneToMany(mappedBy = "entityALink", fetch = FetchType.EAGER)
#JoinFetch
private Collection<EntityB> entityBs;
}
class EntityB {
#JoinColumn(name = "X", referencedColumnName = "Y")
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private EntityA entityALink;
#JoinColumn(name = "A", referencedColumnName = "B")
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#JoinFetch
private EntityC entityCLink;
}
class EntityC {
#Id
#Basic(optional = false)
#Column(name = "SomeColumn")
private String someField
}
If you need reduce number of queries, you may using lazy initialization - FetchType.LAZY instead of FetchType.EAGER - in this way jpa get data from databases when need. But you must remember, this is not working when entity is disconnected from manager. So if you send this entity to other servers in serialize the form (ex. in multi-level application) you must again connected this entity with manager. If you application runs in one server, then you don't have this problem.
Summing up is not the exact answer to your question, but maybe helpful for optimize this code.
Exact answer for you question:
You may using named queries, but then query is parse to sql native query, and you don't sure that this working as you want. But maybe you may using native query method?
em.createNativeQuery("SELECT ... your queries")
For this purpose, please read about using #SqlResultSetMapping annotation to configure result entity class...
First write a query to get EntityA.
EntityA entity = <your Query> ;
then call
Collection<EntityB> entityB = entity.getEntityBs();
for(EntityB eachB : entityB){
EntityC entityCLink = eachB.getEntityCLink();
}
Note: Create setter & getters in each entity.

Categories

Resources