Hibernate relations fetch profile - java

I am developing an application with Struts2, Spring and Hibernate and I'm working on models fetch optimization. For this example, consider that the "example_table" may have more than 500k records, and all his OneToMany relation may have many more records (ex. document table in relation with document_row).
Here is the example code:
#Entity
#Table(name = "example")
public class Example extends BaseModel { // Base Model is mapped as superclass and contains the Id column and create,update,delete timestamps
private String exampleName;
/*
* ...
*/
/*
* This relation contains another relation inside it
* (ex. Set<ExampleRelationRelation> exampleRelationRelations)
*/
private ExampleRelation1 exampleRelation1;
private Set<ExampleRelation2> exampleRelations2;
// COSTRUCTORS --------------------------------------------------------
/*
* Entity constructors
*/
// GETTER AND SETTER --------------------------------------------------
#Column(name = "exampleName")
public String getExampleName() {
return exampleName;
}
public void setExampleName(String exampleName) {
this.exampleName = exampleName;
}
/*
* ...
*/
#OneToOne(mappedBy = "example_relation_1", cascade = CascadeType.ALL, fetch = FetchType.LAZY, targetEntity = ExampleRelation1.class)
#NotFound(action = NotFoundAction.IGNORE)
public ExampleRelation1 getExampleRelation1() {
return exampleRelation;
}
public void setExampleRelation1(ExampleRelation1 exampleRelation1) {
this.exampleRelation1 = exampleRelation1;
}
#OneToMany(mappedBy = "example", targetEntity = ExampleRelation2.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#NotFound(action = NotFoundAction.IGNORE)
public Set<ExampleRelation2> getExampleRelation2() {
return exampleRelation2;
}
public void setExampleRelation2(Set<ExampleRelation2> exampleRelation2) {
this.exampleRelation2 = exampleRelation2;
}
}
In my application I might need to load Example with ExampleRelation1 and his child relation exampleRelationRelations, but not always. Maybe next time I have to load Example with no relations or just with exampleRelation1 and exampleRelation2.
The best solution I've found is to implement the Fetch profiles in order to dynamically join the table with the relations I need. With this solution I can tell to Struts2 controller to retrieve the data I need.
Is this a good solution? or should i use other strategies?
* EDIT *
I've found also this question, it may be a solution?

Fetch Profiles can be an answer for your problem.
But you can control this behaviour on code, just lazy loading the entities you want calling the right method each time inside a transaction.
I might need to load Example with ExampleRelation1 and his child relation exampleRelationRelations
So, you can use something like that:
#Transactional // from Spring
public Example getExampleWithExampleRelation1AndExampleRelationRelations(String exampleName) {
Example example = em.find(Example.class, exampleName);
ExampleRelation1 exampleRelation1 = example.getExampleRelation1(); //lazy load
exampleRelation1.exampleRelationRelations().size(); //lazy load list
return example;
}
Maybe next time I have to load Example with no relations or just with exampleRelation1 and exampleRelation2.
Just create another method and call it:
#Transactional // from Spring
public Example getExampleWithExampleRelation1AndExampleRelation2(String exampleName) {
Example example = em.find(Example.class, exampleName);
example.getExampleRelation1(); //lazy load
example.getExampleRelation2(); //lazy load
return example;
}
You can also use JPQL and join with the FETCH word to bring the entities using only one query, like:
String jpql = "SELECT e FROM Example e " +
"JOIN FETCH e.ExampleRelation1" +
"JOIN FETCH e.ExampleRelation2 ";

Related

Fetch join to attribute from another object using Spring Specification

I have the following entities:
#Entity
#Table(name = "user_data")
public class UserData {
...
#ManyToOne
private User user;
...
}
#Entity
#Table(name = "user_cars")
public class UserCar {
...
private Integer userId;
...
}
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(mappedBy = "userId", cascade = CascadeType.ALL)
private List<UserCar> userCars;
...
}
As you can see, userCars are loaded lazily (and I am not going to change it). And now I use Specifications in order to fetch UserData:
public Page<UserData> getUserData(final SpecificationParameters parameters) {
return userDataRepository.findAll(createSpecification(parameters), parameters.pageable);
}
private Specification<UserData> createSpecification(final SpecificationParameters parameters) {
final var clazz = UserData.class;
return Specification.where(buildUser(parameters.getUserId()));
}
private Specification<UserData> buildUser(final Integer userId) {
return (root, criteriaQuery, criteriaBuilder) -> {
if (Objects.nonNull(userId)) {
final Join<UserData, User> joinParent = root.join("user");
return criteriaBuilder.equal(joinParent.get("id"), userId);
} else {
return criteriaBuilder.isTrue(criteriaBuilder.literal(true));
}
};
}
But I have no idea how to add there a fetch join clause in order to fetch user cars. I tried to add it in different place and I got either LazyInitializationException (so it didn't work) or some other exceptions...
Slightly different approach from the prior answer, but I think the idea jcc mentioned is on point, i.e. "Hibernate is complaining because it it unable to find the owner, user in this case, of the userCars relationship."
To that end, I'm wondering if the Object-Relational engine is getting confused because you have linked directly to a userId (a primitive) instead of a User (the entity). I'm not sure if it can assume that "userId" the primitive necessarily implies a connection to the User entity.
Can you try to re-arrange the mapping so that it's not using an integer UserId in the join table and instead using the object itself, and then see if it allows the entity manager to understand your query better?
So the mapping might look something like this:
#Entity
#Table(name = "user_cars")
public class UserCar {
...
#ManyToOne
#JoinColumn(name="user_id", nullable=false) // Assuming it's called user_id in this table
private User user;
...
}
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<UserCar> userCars;
...
}
It would also be more in line with
https://www.baeldung.com/hibernate-one-to-many
In addition of the suggestion #crizzis provided in the question comments, please, try to join fetch the user relationship as well; in the error you reported:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
Hibernate is complaining because it it unable to find the owner, user in this case, of the userCars relationship.
It is strange in a certain way because the #ManyToOne relationship will fetch eagerly the user entity and it will be projected as well while obtaining userData but probably Hibernate is performing the query analysis prior to the actual fetch phase. It would be great if somebody could provide some additional insight about this point.
Having said that, please, consider to set the fetch strategy explicitly to FetchType.LAZY in your #ManyToOne relationship:
#Entity
#Table(name = "user_data")
public class UserData {
...
#ManyToOne(fetch= FetchType.LAZY)
private User user;
...
}
Your Specification code can look like the following:
public Page<UserData> getUserData(final SpecificationParameters parameters) {
return userDataRepository.findAll(createSpecification(parameters), parameters.pageable);
}
private Specification<UserData> createSpecification(final SpecificationParameters parameters) {
final var clazz = UserData.class;
return Specification.where(buildUser(parameters.getUserId()));
}
private Specification<UserData> buildUser(final Integer userId) {
return (root, criteriaQuery, criteriaBuilder) -> {
// Fetch user and associated userCars
final Join<UserData, User> joinParent = (Join<UserData, User>)root.fetch("user");
joinParent.fetch("userCars");
// Apply filter, when provided
if (Objects.nonNull(userId)) {
return criteriaBuilder.equal(joinParent.get("id"), userId);
} else {
return criteriaBuilder.isTrue(criteriaBuilder.literal(true));
}
};
}
I did not pay attention to the entity relations themself previously, but Atmas give you a good advice indeed, it will be the more performant way to handle the data in that relationship.
At least, it would be appropriate to define the relationship between User and UserCars using a #JoinColumn annotation instead of mapping through a non entity field in order to prevent errors or an incorrect behavior of your entities. Consider for instance:
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<UserCar> userCars;
...
}

Overriding OneToOne with OneToMany Mapping in Hibernate Child Class

I have two tables in database orders and offers. Earlier there was #OneToOne mapping between two i.e. for a single order, there was a single offer. Corresponding domains are:
#Entity
#Table(name = "orders")
#DiscriminatorFormula("0")
#DiscriminatorValue("0")
class Order {
#OneToOne(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Offer offer;
public Offer getOffer() {
return this.offer;
}
public void setOffer(Offer offer) {
this.offer = offer;
}
}
#Entity
#Table(name = "offers")
class Offer {
}
Now, i want OneToMany mapping between two i.e. for a single order, there can be multiple offers now. But for that, i want to build new version of Domain so as not to effect existing functionality. As it is OneToMany mapping so i will have to use Set or List. So, effectively, i want did:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offer;
public Set<Offer> getOffer() {
return this.offer;
}
public void setOffer(Set<Offer> offer) {
this.offer = offer;
}
}
How can i achieve this as currently it is giving me error in getter method as overridden method cannot have different return type.
Actually your problem is that you are using a field with the same name offer as the field in the super class while both have different types, so it will be confusing because you will have the child getter for Set<Offer> overriding the parent getter for Offer, that's why you get the Exception:
error in getter method as overridden method cannot have different return type
What you will have to do here is to use a different name for the field in your child class, for example offers, so the Model will be correct and Hibernate will correctly map the objects:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order1", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offers;
public Set<Offer> getOffers() {
return this.offers;
}
public void setOffers(Set<Offer> offers) {
this.offers = offers;
}
}
Note:
You need to have two objects of type Order in your Offer class, one for the mapping of offer and the second for the offers mapping, notice the mappedBy = "order1" in the mapping.

How to join two tables using Criteria API if join relationship is on EmbeddedId

I have the following classes:
#Entity
public class EventOrderLine {
#EmbeddedId private EventOrderLineId id;
}
#Embeddable
public class EventOrderLineId implements Serializable {
#ManyToOne
#JoinColumn(name = "eventid")
#JsonIgnore
private Event event;
#ManyToOne
#JoinColumn(name = "orderlineid")
#JsonIgnore
private OrderLine orderLine;
}
#Entity
public class OrderLine {
#OneToMany
#JoinColumn(name = "orderlineid")
#JsonIgnore
private List<EventOrderLine> eventOrderLines = new ArrayList<>()
}
Basically I'm trying to join the two tables via the Criteria API but having issues since this is what I want to do:
Root eventOrderLine = criteriaQuery.from(EventOrderLine.class);
Join orderLine = eventOrderLine.join("orderLine");
Of course this give me this issue since the mapping isn't directly on the entities themselves:
Unable to locate Attribute with the the given name [orderLine] on this ManagedType [com.EventOrderLine]
I've been trying to tweak the join to drill into the embeddedId but not sure if I need to go a step further and modify how my entities are mapped. I feel like it's probably something simple I'm missing but having trouble finding this specific question.
The event field is a member of EventOrderLineId and not EventOrderLine. In your criteria query, you first need to navigate to id. The catch is that Root.path("id") returns an instance of Path, which does not allow further joins.
The trick is to use a 'fake' join with the id field like so: eventOrderLine.join("id").join("event")
eventOrderLine.get("id").get("event") would likely work just as well, but it wouldn't allow you to specify the join type.
first try to get the property id of EventOrderLine entity and then join. So, it would be -
Root eventOrderLine = criteriaQuery.from(EventOrderLine.class);
Join orderLine = eventOrderLine.get("id").join("orderLine")

Hibernate not updating ManyToMany associations during session

I am working on a project using Hibernate 4.3.4 to access a Postgres DB. We have two entities which are linked via a ManyToMany Association.
The code and the associations currently work, in that adding an EntityB to EntityA's collection will automatically add the EntityA to the EntityB's collection once the Session is committed. However, the issue I'm having is that when I try to work on the EntityB's EntityAs, which should include the EntityA I just created, EntityA is not in that collection (It is empty). Example code is here:
#Entity
#Table(name = "entity_a")
public class EntityA {
private Set<EntityB> entityBs = new HashSet<EntityB>(0);
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "entitya_entityb",
joinColumns = { #JoinColumn(name = "entitya_id") },
inverseJoinColumns = { #JoinColumn(name = "entityb_id") })
public Set<EntityB> getEntityBs()
{
return entityBs;
}
public void setEntityBs(Set<EntityB> entityBs)
{
this.entityBs = entityBs;
}
}
#Entity
#Table(name = "entity_b")
public class EntityB {
private Set<EntityA> entityAs = new HashSet<EntityA>(0);
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "entitya_entityb",
joinColumns = { #JoinColumn(name = "entityb_id") },
inverseJoinColumns = { #JoinColumn(name = "entitya_id") })
public Set<EntityA> getEntityAs()
{
return entityAs;
}
public void setEntityAs(Set<EntityA> entityAs)
{
this.entityAs = entityAs;
}
}
/**
* HTTP REST Resource to create Entities and persist them. We do some basic logic when we create them to show the problem.
*/
#Path("/battleRhythm")
#Singleton
public class HttpResource
{
#POST
#Consumes("application/json")
public void createEntityA() {
Session hibernateSession = SessionFactory.getCurrentSession(); // SessionFactory specifics not included
hibernateSession.getTransaction().begin();
// Add an EntityB to the new EntityA
EntityA entityA0 = new EntityA();
EntityB entityB0 = new EntityB0();
entityA.getEntityBs().add(entityB0);
// Persist the new EntityA
EntityADao.getInstance().save(entityA0);
// Try to get this EntityA from EntityB
Set<EntityA> associatedEntityAs = entityB0.getEntityAs(); // Doesn't contain any EntityAs!
hibernateSession.getTransaction().commit();
}
}
Here's the question:
Can I make Hibernate automatically add the EntityA0 to EntityB0's collection when I save EntityA0, without committing the transaction? Is this possible? How?
Caveat : The example above does not fully reflect this, but we perform similar operations on both Entities, so having an "owner" in the traditional Hibernate sense (using the mappedBy = "" Attribute configuration) is not an ideal option. I don't want to try to convince everyone to only ever use EntityB.getEntityAs().add(EntityB0) in CreateEntityA(). It's too confusing.
You don't have the choice. There MUST be an owner side, and there MUST be an inverse side. And it's YOUR responsibility to maintain both sides of the association: don't expect to have B inside A's collection of Bs when you only add A to B (and vice-versa)
Now, nothing forbids you to have a methods addB(B b) inside A that adds b to A's collection of Bs, and which adds this to B's collection of As. And you can of course also have a method addA(A a) in B that does the same thing.

JPA - Eager - Lazy best practice

JBoss EAP 6
Hibernate 4
I have a J2EE application with a web browser client. ( Apache click )
Both the internal business logic and the client use the same entity objects.
I would like to have all relations in the entities set to lazy loading. This way I have good performance.
But when using the entities in the client ( that is the server side code of apache click ) I would need a lot of the relations to be eager loaded. The client code is accessing the back-end through a session bean.
So I have a couple of ways I can solve this:
Create 2 of each JPA entities, one with eager loading and one with lazy loading. And then use the one with eager loading in the client, and the one with lazy loading in the server. Most of the server logic will be in a transaction, so lazy loading is fine here.
Make all relations lazy loading. When accessing the entities from the client, make sure there is a transaction. ( #TransactionAttribute(TransactionAttributeType.REQUIRED) )
and code the access to the necessary fields so they are accessible after session bean call.
But that means that I have to start a transaction when that is not required, i.e. if I am only getting some objects. And I have to maintain more code. And I have to know exactly what relations the client needs.
Create an inheritance hierarchy, where I have a super entity, and then 2 child, one with objects relations lazy loaded, and one with only values, no objects. i.e. :
Super
#MappedSuperclass
public class SuperOrder {
#Id
#Column(name = "id")
#GeneratedValue(.....)
private Long id;
#Column(name = "invoice", length = 100)
private String invoice;
Child 1
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "testorder")
#SequenceGenerator(....)
public class Order extends SuperOrder {
#ManyToOne(targetEntity = PrintCustomerEnt.class, fetch = FetchType.EAGER, optional = true)
#JoinColumn(name = "print_customer_id", nullable = true)
#ForeignKey(name = "fk_print_customer")
#Valid
private PrintCustomerEnt printCustomer;
public PrintCustomerEnt getPrintCustomer() {
return printCustomer;
}
public void setPrintCustomer(final PrintCustomerEnt printCustomer) {
this.printCustomer = printCustomer;
}
}
Child 2
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "testorder")
#SequenceGenerator(...)
public class LazyOrder extends SuperOrder {
#Transient
private String printCustomerName;
#Column(name = "print_customer_id", nullable = true)
private Long printCustomerId;
What is the best practice... or is there something other good way to do this.
Basically the problem is I want to use the same entities in different scenarios. Sometimes I need eager loading, and sometimes I need lazy loading.
I suggest that you create just one JPA entity with lazy relationships, and when you need to load eagerly some of them create a Service that uses JPQL(HQL) to do some FETCH trick. The idea is one JPA entity and many services.
I've been programing in JPA 2 for some a while now, and I can say there are couple of now written rules that I almost always apply:
Use LAZY Inicialization on all your OneToMany, ManyToMany Relations
Use EAGER Inicalization on all your OneToOne, ManyToOne Relations
This rules apply on 99% of my projects. I think these are best practices due to my personal experience and some research I've been doing.
Note: I must say I do not use JOIN FETCH on Lazy Inicialization, instead I write a Prefetch Method. Example:
#Entity
class Entity{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child1> collection1;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child2> collection2; }
And then we have the Controller:
class EntityController{
public Entity findCompraFolioFull(Integer id) {
EntityManager em = getEntityManager();
try {
Entity entity = em.find(Entity.class, id);
//Initialize Collections inside Transaccion, this prevents
//LazyInizialization No Proxy Exception later in code when calling
//hollow collections
cp.getCollection().size();
cp.getCollection().size();
return cp;
} finally {
em.close();
}
}
}
I don't recomend FETCH JOIN
public Entity findEntityByJoinFetch(Integer id) {
EntityManager em = getEntityManager();
try {
TypedQuery<Entity> tq = em.createQuery(
"SELECT e FROM Entity e\n"
+ "LEFT JOIN FETCH e.collection1\n"
+ "LEFT JOIN FETCH e.collection2\n"
+ "WHERE e.id = :id", Entity.class);
tq.setParameter("id", id);
return tq.getSingleResult();
} finally {
em.close();
}
}
Reasons I don't recomend Fetch Join Appoach:
If your collections are java.util.List type then this getSingleResult() will fail in hibernate due to lack of capacity to fetch MultipleBags without indexing notations on your OneToMany Relation.
You can always change the type of your collections to java.util.set in order to multiple bags to be fetched but this brings new kind of situations to deal with, Sets aren't ordered and HashCode() method won't work correctly so you'll have to #Override it inside Children Classes, and if you are using JAVAFX TableView to bind model to Items you won't be able to bind collections Set Type to Item Property of TableView, not directly at least.

Categories