Do we have to explitly use Left Join query while using JPA in SpringBoot? - spring

I am working in SpringBoot and I am using JPA repositories for DB access. I have two entities say
Class A{
#Id
private String primarykeyColumnA;
#OneToMany(mappedBy="campaign",fetch = FetchType.EAGER,cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH })
private Set<B> b;
....
}
Class B{
#Id
private Long primaryKeyColB;
#ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE })
#JoinColumn(name = "primarykeyColumnA")
private A a;
}
So I have to join these two tables and it is one to many(for one A, there will be many B) relationship. It works fine. But I want to make sure I use Left join. Is it normally Left join or should I specifically use a query?
Some help is greatly appreciated.

I think it depends on the JPA provider you use. In the case of Hibernate, with the fetch=EAGER attribute, Hibernate will not perform what you are expecting when you load some A entities.
Instead the following steps will be performed:
Perform a SQL query to load the A entities, without loading the B entities.
For each A entity, perform a SQL query to load its related B entities.
If the B entities are not in the cache, you will have the 1+n queries problem.
If you want to load the A entities with the related B entities with a single SQL query, you have to use a query using the join fetch clause (left, inner, ... depending on your case).

Related

How to do lazy loading of the non-owning entity in a unidirectional one-to-one mapping?

#Entity
#Table(name = "...")
public class A
{
private Integer id;
...
...
private B b;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "b_id")
getB() {
return b;
}
setB(B b) {
this.b = b;
}
...other methods...
}
#Entity
#Table(name = "...")
public class B
{
private Integer id;
...
...
...methods...
}
When using
Query query = sessionFactory.getCurrentSession().createQuery()
with
query.uniqueResult()
or
query.list()
to fetch an instance of object A from the db,
I am getting the following error message:
org.hibernate.LazyInitializationException: could not initialize proxy [com.project.core.entity.B#261493] - no Session
The above query method is deprecated, however, I am working on an old project that uses Spring (not Spring Boot), and this is the only way I can go about it.
Now, I know that this may be because the hibernate session is already closed, however, I am using join fetch in my hibernate queries above.
I cannot use a shared primary key for the entities because in the future more entities like C, D, etc. will be added which will also have a unidirectional one-to-one mapping with B.
How do I go about fixing this problem? Thank you.

Join query in Spring Data JPA

I have two tables:
Car_company which has the attributes of:
C_id (primary key),
C_name
Car_model which has the attributes of:
Com_id (referenced to C_id of Car_company),
Model_year
Warranty
I wish to access both of these tables individually and also I would like to perform a join operation on them and display all of the car_models along with their car_company name. I tried using both JPQL and native query but nothing worked. I also made sure to use the OneToMany and ManyToOne associations but I ended up getting infinite nesting,i.e, the models have car_company as field, this inturn has car_models as a list, and this keeps going. Please help me with entity classes and DAOs.
You can get a List of CarModel for each car company in the CarCompany entity through the oneToMany annotation like this:
#OneToMany(mappedBy = "carCompany", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<CarModel> carModels;
or get all car models with their company field in the CarModel entity like this:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Com_id", referencedColumnName = "C_id", nullable = false)
private CarCompany carCompany;
Just try to define your relationships as LAZY for performance purpose and to prevent circular nesting when you map your responses to a Data Transfer Object (DTO).

#Batchsize annotation not working for OneToMany

I have following classes and on annotating #BatchSize annotation it is not working and I am getting n+1 select query.
Class Shipment{
#OneToMany(fetch = FetchType.LAZY, mappedBy = order.shipment, cascade = CascadeType.ALL,
orphanRemoval = true)
#BatchSize(size=20)
Set<Orders> orders = new Hashset(); <---- Batch size annotation not working
}
Order.class
class Order{
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "item_fk")
Item item;
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "shipment_fk")
Shipment shipment; }
Item.class
class Item{
String id;
String name;
}
What is mistake in implementation that i am getting n+1 queries?
Try to use List<Orders> instead of Set<Orders>.
Please note as it's mentioned in the documentation:
However, although #BatchSize is better than running into an N+1 query issue, most of the time, a DTO projection or a JOIN FETCH is a much better alternative since it allows you to fetch all the required data with a single query.
Your N + 1 query issue is due to the fact that you do eager fetching of Item in Order. Change to LAZY there and you should be good to go.

Dynamic JPA query

I have two entities Questions and UserAnswers. I need to make an api in spring boot which returns all the columns from both the entities based on some conditions.
Conditions are:
I will be give a comparator eg: >, <, =, >=, <=
A column name eg: last_answered_at, last_seen_at
A value of the above column eg: 28-09-2020 06:00:18
I will need to return an inner join of the two entities and filter based on the above conditions.
Sample sql query based on above conditions will be like:
SELECT q,ua from questions q INNER JOIN
user_answers ua on q.id = ua.question_id
WHERE ua.last_answered_at > 28-09-2020 06:00:18
The problem I am facing is that the column name and the comparator for the query needs to be dynamic.
Is there an efficient way to do this using spring boot and JPA as I do not want to make jpa query methods for all possible combinations of columns and operators as it can be a very large number and there will be extensive use of if else?
I have developed a library called spring-dynamic-jpa to make it easier to implement dynamic queries with JPA.
You can use it to write the query templates. The query template will be built into different query strings before execution depending on your parameters when you invoke the method.
This sounds like a clear custom implementation of a repository method. Firstly, I will make some assumptions about the implementation of your entities. Afterwards, I will present an idea on how to solve your challenge.
I assume that the entities look basically like this (getters, setters, equals, hachCode... ignored).
#Entity
#Table(name = "questions")
public class Question {
#Id
#GeneratedValue
private Long id;
private LocalDateTime lastAnsweredAt;
private LocalDateTime lastSeenAt;
// other attributes you mentioned...
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserAnswer> userAnswers = new ArrayList();
// Add and remove methods added to keep bidirectional relationship synchronised
public void addUserAnswer(UserAnswer userAnswer) {
userAnswers.add(userAnswer);
userAnswer.setQuestion(this);
}
public void removeUserAnswer(UserAnswer userAnswer) {
userAnswers.remove(userAnswer);
userAnswer.setQuestion(null);
}
}
#Entity
#Table(name = "user_answers")
public class UserAnswer {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "task_release_id")
private Question question;
}
I will write the code with the knowledge about the JPA of Hibernate. For other JPAs, it might work similarly or the same.
Hibernate often needs the name of attributes as a String. To circumvent the issue of undetected mistakes (especially when refactoring), I suggest the module hibernate-jpamodelgen (see the class names suffixed with an underscore). You can also use it to pass the names of the attributes as arguments to your repository method.
Repository methods try to communicate with the database. In JPA, there are different ways of implementing database requests: JPQL as a query language and the Criteria API (easier to refactor, less error prone). As I am a fan of the Criteria API, I will use the Criteria API together with the modelgen to tell the ORM Hibernate to talk to the database to retrieve the relevant objects.
public class QuestionRepositoryCustomImpl implements QuestionRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Question> dynamicFind(String comparator, String attribute, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Question> cq = cb.createQuery(Question.class);
// Root gets constructed for first, main class in the request (see return of method)
Root<Question> root = cq.from(Question.class);
// Join happens based on respective attribute within root
root.join(Question_.USER_ANSWER);
// The following ifs are not the nicest solution.
// The ifs check what comparator String contains and adds respective where clause to query
// This .where() is like WHERE in SQL
if("==".equals(comparator)) {
cq.where(cb.equal(root.get(attribute), value));
}
if(">".equals(comparator)) {
cq.where(cb.gt(root.get(attribute), value));
}
if(">=".equals(comparator)) {
cq.where(cb.ge(root.get(attribute), value));
}
if("<".equals(comparator)) {
cq.where(cb.lt(root.get(attribute), value));
}
if("<=".equals(comparator)) {
cq.where(cb.le(root.get(attribute), value));
}
// Finally, query gets created and result collected and returned as List
// Hint for READ_ONLY is added as lists are often just for read and performance is better.
return entityManager.createQuery(cq).setHint(QueryHints.READ_ONLY, true).getResultList();
}
}

spring data jpa findAll() not working properly

I am having below classes
#Entity
#Table(name = "USR_E_GROUPS")
public class GroupEntity {
#Id
#Column(name = "UIDUSERGROUP")
#GenericGenerator(name = "generator", strategy = "uuid2")
#GeneratedValue(generator = "generator")
private String id;
.........
#OneToMany(mappedBy = "group", cascade = CascadeType.PERSIST)
private List<UserGroupEntity> users;
same is for UserGroupEntity
now if I use groupRepoository.findAll()
It's is firing select query for every Group and inside different select query for UserGroupEntity. so it's taking too much time.
I want to make it to fire select with join so it will be a single query.
This is probably an n + 1 issue.
From the docs
By default, Hibernate3 uses lazy select fetching for collections and
lazy proxy fetching for single-valued associations. These defaults
make sense for most associations in the majority of applications.
By default the children are fetched lazily. Use JOIN FETCH to get the result in a single query.
In your GroupRepoository
#Query("SELECT g FROM GroupEntity g JOIN FETCH g.users gu")
List<GroupEntity> findAllEager();

Resources