Nearly every table in our database has a FK to the Auditing table which logs created, updated and deleted status (date and username).
We mapped the auditing table to the Auditing class and use it like this:
#MappedSuperclass
public class BusinessObject extends DataObject {
private static final long serialVersionUID = -1147811010395941150L;
#OneToOne(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinColumn(name = "AUD_ID")
private AuditingObject auditing;
...
As you'd expect, nearly every entity extends from BusinessObject.
Is there an easy way of saying, for every businessObject, only receive "auditing.deleted is null".
I've tried adding a #Where and #WhereJoinTable in the businessObject but this doesn't seem to work as I expect.
Currently, i've done this to one of my queries and this works, but I'd hate to do this for all queries since we have about 150.
#NamedQuery(
name="allCountries",
query="SELECT c FROM Country c"
+ " LEFT JOIN FETCH c.labelDefinition "
+ " LEFT JOIN FETCH c.labelDefinition.translations "
+ " WHERE c.auditing.deleted is null"
+ " ORDER BY c.code"
)
IMO, the easiest way to implement a soft-delete would be to add a flag in your entities and to use:
the #SQLDelete annotation to override the default Hibernate delete (and perform an update of the flag)
the #Where (or #Filters?) annotation on your entities and associations to filter the deleted entities
Not sure how this can fit with your Auditing table though. Some further exploration and testing are required.
Resources
Soft deletes using Hibernate annotations
Related
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.
Following entities:
#Table
class AA1 {
#Id
Long id;
String a_number;
Category category;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name= 'a_number', referencedColumnName='a_number')
#JoinColumn(name= 'category', referencedColumnName='category')
BB1 bb1
...other fields...
}
#Table
class BB1 {
#Id
String a_number;
Category category;
String value;
}
JPQL query:
SELECT a FROM AA1 a LEFT JOIN a.bb1 b;
Hibernate produces correct sql query, but when it tries to collect data it makes additional call like:
SELECT b.a_number, b.category, b.value FROM BB1 b WHERE b.a_number = ? AND b.category = ?
I checked that query returns null.
How can I avoid such database queries?
My investigation: As far as I see Hibernate creates key by(AA1.a_number and AA1.category) and tries to retrieve entity from context. And for specific row 'left join' query returns null values and Hibernate asks context by key and context returns null, it leads to call to database for it.
You must add FETCH to your JPQL query :
SELECT a FROM AA1 a LEFT JOIN FETCH a.bb1 b;
but keep the LAZY loading, because Hibernante will always try to get ManyToOne or OneToOne relationship, which are EAGER by default, with an additional query.
Look at this article https://thorben-janssen.com/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/ from Th
By default, to make lazy loading work for #OneToOne and #ManyToOne, you need to enable "no proxy lazy associations". Otherwize, despite the FetchType.LAZY annotation, the associated object will be "fetched" and the "fetch" will be done with an extra sql query.
Therefore, one half-way solution to leverage performances without enabling "no proxy lazy associations" is to avoid extra queries by forcing a join fetch on the associated objet. Various technics allow to reach this goal : "LEFT JOIN FETCH" in JPQL queries or EntityGraph.
Just looking at the entity definition #ManyToOne(fetch = FetchType.LAZY) is the cause.
You are explicitly telling JPA to fetch BB1 only when it is needed/accessed.Hence when the first call to get the parent entity is made BB1 is not loaded.It is only when you are accessing the child that triggers JPA to fetch it.
If you change it to FetchType.EAGER , both the entities will be queried in a single call.
But be careful there are advantages/pit-falls with either approach.
Read more abt it here : https://thorben-janssen.com/entity-mappings-introduction-jpa-fetchtypes/
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
Given a Hibernate/JPA entity with cascading set to ALL for a related entity:
#Entity
public class Entity {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "entity")
private Set<RelatedEntities> relatedEntities;
}
Is it possible to temporarily turn off the cascading, e.g. to allow Entity to be persisted without also persisting its relatedEntities?
No, it is not possible to do it, and at least according to my modest opinion, it would not be a good thing to do so either. When other developers look at the mappings and the code that does persist/merge/delete... they would expect the cascades to be applied and introduce the unexpected behavior if they oversee that the cascades are temporarily disabled somewhere else for the code they are about to change.
However, you can map to the same table a new entity class which does not have the fields that are cascaded. Then just use that entity in situations in which you don't want the cascades to be applied.
You can't temporarily disable cascading (to my knowledge, at least), but since you use Hibernate you can insert new entity using HQL
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.createQuery( hqlInsert ).executeUpdate();
There is always a "manual" solution where you remember relatedEntities in a variable for later use, and set null value as its value on Entity instance before persisting it.
What exactly does JPA's fetch strategy control? I can't detect any difference between eager and lazy. In both cases JPA/Hibernate does not automatically join many-to-one relationships.
Example: Person has a single address. An address can belong to many people. The JPA annotated entity classes look like:
#Entity
public class Person {
#Id
public Integer id;
public String name;
#ManyToOne(fetch=FetchType.LAZY or EAGER)
public Address address;
}
#Entity
public class Address {
#Id
public Integer id;
public String name;
}
If I use the JPA query:
select p from Person p where ...
JPA/Hibernate generates one SQL query to select from Person table, and then a distinct address query for each person:
select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3
This is very bad for large result sets. If there are 1000 people it generates 1001 queries (1 from Person and 1000 distinct from Address). I know this because I'm looking at MySQL's query log. It was my understanding that setting address's fetch type to eager will cause JPA/Hibernate to automatically query with a join. However, regardless of the fetch type, it still generates distinct queries for relationships.
Only when I explicitly tell it to join does it actually join:
select p, a from Person p left join p.address a where ...
Am I missing something here? I now have to hand code every query so that it left joins the many-to-one relationships. I'm using Hibernate's JPA implementation with MySQL.
Edit: It appears (see Hibernate FAQ here and here) that FetchType does not impact JPA queries. So in my case I have explicitly tell it to join.
JPA doesn't provide any specification on mapping annotations to select fetch strategy. In general, related entities can be fetched in any one of the ways given below
SELECT => one query for root entities + one query for related mapped entity/collection of each root entity = (n+1) queries
SUBSELECT => one query for root entities + second query for related mapped entity/collection of all root entities retrieved in first query = 2 queries
JOIN => one query to fetch both root entities and all of their mapped entity/collection = 1 query
So SELECT and JOIN are two extremes and SUBSELECT falls in between. One can choose suitable strategy based on her/his domain model.
By default SELECT is used by both JPA/EclipseLink and Hibernate. This can be overridden by using:
#Fetch(FetchMode.JOIN)
#Fetch(FetchMode.SUBSELECT)
in Hibernate. It also allows to set SELECT mode explicitly using #Fetch(FetchMode.SELECT) which can be tuned by using batch size e.g. #BatchSize(size=10).
Corresponding annotations in EclipseLink are:
#JoinFetch
#BatchFetch
"mxc" is right. fetchType just specifies when the relation should be resolved.
To optimize eager loading by using an outer join you have to add
#Fetch(FetchMode.JOIN)
to your field. This is a hibernate specific annotation.
The fetchType attribute controls whether the annotated field is fetched immediately when the primary entity is fetched. It does not necessarily dictate how the fetch statement is constructed, the actual sql implementation depends on the provider you are using toplink/hibernate etc.
If you set fetchType=EAGER This means that the annotated field is populated with its values at the same time as the other fields in the entity. So if you open an entitymanager retrieve your person objects and then close the entitymanager, subsequently doing a person.address will not result in a lazy load exception being thrown.
If you set fetchType=LAZY the field is only populated when it is accessed. If you have closed the entitymanager by then a lazy load exception will be thrown if you do a person.address. To load the field you need to put the entity back into an entitymangers context with em.merge(), then do the field access and then close the entitymanager.
You might want lazy loading when constructing a customer class with a collection for customer orders. If you retrieved every order for a customer when you wanted to get a customer list this may be a expensive database operation when you only looking for customer name and contact details. Best to leave the db access till later.
For the second part of the question - how to get hibernate to generate optimised SQL?
Hibernate should allow you to provide hints as to how to construct the most efficient query but I suspect there is something wrong with your table construction. Is the relationship established in the tables? Hibernate may have decided that a simple query will be quicker than a join especially if indexes etc are missing.
Try with:
select p from Person p left join FETCH p.address a where...
It works for me in a similar with JPA2/EclipseLink, but it seems this feature is present in JPA1 too:
If you use EclipseLink instead of Hibernate you can optimize your queries by "query hints". See this article from the Eclipse Wiki: EclipseLink/Examples/JPA/QueryOptimization.
There is a chapter about "Joined Reading".
to join you can do multiple things (using eclipselink)
in jpql you can do left join fetch
in named query you can specify query hint
in TypedQuery you can say something like
query.setHint("eclipselink.join-fetch", "e.projects.milestones");
there is also batch fetch hint
query.setHint("eclipselink.batch", "e.address");
see
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html
I had exactly this problem with the exception that the Person class had a embedded key class.
My own solution was to join them in the query AND remove
#Fetch(FetchMode.JOIN)
My embedded id class:
#Embeddable
public class MessageRecipientId implements Serializable {
#ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
#JoinColumn(name="messageId")
private Message message;
private String governmentId;
public MessageRecipientId() {
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public String getGovernmentId() {
return governmentId;
}
public void setGovernmentId(String governmentId) {
this.governmentId = governmentId;
}
public MessageRecipientId(Message message, GovernmentId governmentId) {
this.message = message;
this.governmentId = governmentId.getValue();
}
}
Two things occur to me.
First, are you sure you mean ManyToOne for address? That means multiple people will have the same address. If it's edited for one of them, it'll be edited for all of them. Is that your intent? 99% of the time addresses are "private" (in the sense that they belong to only one person).
Secondly, do you have any other eager relationships on the Person entity? If I recall correctly, Hibernate can only handle one eager relationship on an entity but that is possibly outdated information.
I say that because your understanding of how this should work is essentially correct from where I'm sitting.