One-to-Many query for collection - java

I have a One-to-Many relation:
public class Account{
#OneToMany(cascade=CascadeType.ALL)
private List<Transaction>transactions = new ArrayList<>();
}
public class Transaction{
#ManyToOne
#JoinColumn(name="ACCOUNT_ID")
private Account account;
}
I want to get all Accounts for a User, and it works, but Transaction list is empty. Is this a matter of entity mapping or should I modify my query?
I started with (empty transaction list):
TypedQuery<Account>query = em.createQuery("SELECT a FROM Account a WHERE a.user.id = ?1",Account.class);
also tried to join like so (no accounts returned at all):
TypedQuery<Account>query = em.createQuery("SELECT a FROM Account a JOIN a.transactions t WHERE a.user.id = ?1",Account.class);
What's wrong here?

It seems you forgot to add mappedBy in the annotation properties:
#OneToMany(cascade=CascadeType.ALL, mappedBy="account")
private List<Transaction>transactions = new ArrayList<>();

Related

Recover a list of entities with related entities while omitting some fields using JPQL or criteria API

I have three entities: A, B, C
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class A {
private long id;
private String secret;
#Builder.Default
#Valid
#OneToMany(mappedBy = "a", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(value = FetchMode.SUBSELECT)
private List<B> bList = new LinkedList<>();
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class B {
#JoinColumn
private A a;
private long id;
private String secret;
#Builder.Default
#Valid
#OneToMany(mappedBy = "b", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(value = FetchMode.SUBSELECT)
private List<C> cList = new LinkedList<>();
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class C {
#JoinColumn
private B b;
private long id;
private String secret;
}
I would like to recover the values from the DB EXCEPT the "secret" where the id of A belongs to a List of ids. I do not want the secret of either A, B or C to be requested from the DB. (in reality the data is much more complex, and the fields I don't want take too much time to be fetched from the database).
I've been trying to create such a query using JPQL or the Criteria API, but without success, I am unable to get even a list of B without the secrets.
For example in JPQL:
Query query = entityManager.createQuery
("SELECT a " +
"FROM A a where a.id in :aIds");
query.setParameter("aIds", aIds);
This will work, but unfortunately it also requests the secret of A, B and C.
What I want instead would be something like (this code obviously does not work):
Query query = entityManager.createQuery
("SELECT a.id, a.bList.id, a.bList.cList.id" +
"FROM A a where a.id in :aIds");
query.setParameter("aIds", aIds);
I cannot change the entities (besides adding new constructors or methods) as they are used elsewhere and in other queries.
Is that even possible?
You can create dto class with fields you need and populate it. The problem is that database query is able to return plain result only. Persistence provider can convert it to entities. As for dto you have to solve the problem yourself.
You can get List<Object[]> as query result
select a.id, b.id, c.id
from A a join a.bList b join b.cList c
where a.id in(:aIds)
Then you should convert query result to dto

Get collections within an Entity when mapping it to DTO using Transformers

I have an Entity called Student
#Entity
#Table(name = "students")
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId;
#Column(name = "STUDENT_NAME", nullable = false, length = 100)
private String studentName;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student", cascade = CascadeType.ALL)
private List<Note> studentNotes;
// Some other instance variables that are not relevant to this question
/* Getters and Setters */
}
and an entity called as Note
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "NOTE_ID")
private Integer noteId;
#Column(name = "NOTE_CONTENT")
private String noteText;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "STUDENT_ID")
private Student student;
/* Getters and Setters */
}
As you can see the relationship dictates that a Student can have multiple number of notes.
For displaying some information about the student on a particular page I need only the studentName, count of notes and all the notes.
I created a StudentDTO for that and it looks something like this:
public class StudentDTO {
private Long count;
private String name;
private List<Note> notes;
/* Getters and setters */
}
And I am using the following code to map the Student and Notes returned from the DB to the StudentDTO
private static void testDTO() {
Session session = getSessionFactory().openSession();
String queryString = "SELECT count(n) as count, s.studentName as name, s.studentNotes as notes " +
"from Student s join s.studentNotes n where s.id = 3";
Query query = session.createQuery(queryString);
List<StudentDTO> list = query.setResultTransformer(Transformers.aliasToBean(StudentDTO.class)).list();
for (StudentDTO u : list) {
System.out.println(u.getName());
System.out.println(u.getCount());
System.out.println(u.getNotes().size());
}
}
The above code fails when there are notes fetched in the query but if I remove the notes and get only name and count it works fine.
When notes is included in the query, this is the error that is fired by Hibernate:
select
count(studentnot2_.NOTE_ID) as col_0_0_,
. as col_3_0_,
studentnot3_.NOTE_ID as NOTE_ID1_2_,
studentnot3_.NOTE_CONTENT as NOTE_CON2_2_,
studentnot3_.STUDENT_ID as STUDENT_3_2_
from
students studentx0_
inner join
notes studentnot2_
on studentx0_.STUDENT_ID=studentnot2_.STUDENT_ID
inner join
notes studentnot3_
on studentx0_.STUDENT_ID=studentnot3_.STUDENT_ID
where
studentx0_.STUDENT_ID=3;
And this is the error message that I get:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'as col_3_0_, studentnot3_.NOTE_ID as NOTE_ID1_2_, studentnot3_.NOTE_CONTENT as N' at line 1
Now I can see where the query is wrong but it is generated by Hibernate, not something that I have control on. Is there something that I need to change in my queryString to acheive the result that I need.
I do not want to manually map the results to my DTO, is there a way that I can directly map my studentNotes in Student.java to notes in StudentDTO.java
Looks like this query is wrong. The better way is to get just the student. You can always get collection of notes from a student.
Session session = getSessionFactory().openSession();
String queryString = from Student s where s.studentId = 3;
Query query = session.createQuery(queryString);
Student student = query.getSingleResult();
sysout(student.getNotes().size())
Also, I never retrieved collection this way in SELECT clause; so, not sure but do you really need
join s.studentNotes
in your query? Not sure if my answer is helpful.
Your query is wrong as you would need two joins to also select the count of notes, but that's not even necessary, as you could determine the count by just using the size of the notes collection.
I created Blaze-Persistence Entity Views for exactly that use case. You essentially define DTOs for JPA entities as interfaces and apply them on a query. It supports mapping nested DTOs, collection etc., essentially everything you'd expect and on top of that, it will improve your query performance as it will generate queries fetching just the data that you actually require for the DTOs.
The entity views for your example could look like this
#EntityView(Student.class)
interface StudentDTO {
#Mapping("studentName")
String getName();
#Mapping("studentNotes")
List<NoteDTO> getNotes();
default int getCount() { return getNotes().size(); }
}
#EntityView(Note.class)
interface NoteDTO {
// attributes of Note that you need
#IdMapping Integer getId();
String getNoteText();
}
Querying could look like this
StudentDTO student = entityViewManager.find(entityManager, StudentDTO.class, studentId);

How to make a select statement on a list contained within an object in spring?

How do you make a select statement or filter a List that is nested within an entity in spring? I have an object that looks like this...
#Entity
#Table(name = "employee")
public class Employee {
...
#OneToMany(mappedBy = "_employee", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference
Set<Deal> _deals;
#OneToMany(mappedBy = "_employee", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference //This is simply to avoid a stackoverflow error according to this link http://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue
Set<Recommendation> _recommendations;
#OneToMany(mappedBy = "_employee", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference //This is simply to avoid a stackoverflow error according to this link http://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue
Set<Event> _events;
public Employee() {
}
//getters and setters
....
I get employees with a repository that is accessed by a service class.
The repository looks like this.
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
public Employee getEmployeeById(Long _id);
public Employee getEmployeeBy_username(String username);
}
So bascially when I get an employee by its id, it returns the above lists. When an employee is retrieved I need to do a select statement or filter in some way _deals, _recommendations and _events. So that only those who have the boolean attribute _active=true returned. As it is now, all deals recommendations and events are returned whether they are active or not. How do I filter or select from these lists only active objects?
You almost always select a single Entity type per query, and preferably you would do the filtering in the database. If you want the Deals, Recommendations and Events belonging to a specific Employee, I would normally put these methods in the Repository belonging to entity type I'm trying to load, it could look like this:
#Repository
public interface DealRepository extends JpaRepository<Deal, Long> {
#Query("select d from Deal d where d.active= true and d.employee.id = :employeeId")
List<Deal> findActiveDeals(#Param("employeeId") long employeeId);
}

Hibernate nested JoinColumns results in a big query from the database with unnecessary data

I'm working on some personal project but i have a question about hibernate.
I have a class structure like this :
#Entity
public class User {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyUser")
private Company company = new Company();
}
But inside the company i have another join.
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
}
Now i do a query to get the estimate options.
But if i do it like this it loads lots of unnecessary stuff .
#RequestMapping(value = "/estimateoptions")
public EstimateOptions getCompanyEstimateOptions(#AuthenticationPrincipal Principal user) {
User getuser = userDao.findByEmail(user.getName());
EstimateOptions estimateOptions = getuser.getCompany().getEstimateOptions();
return estimateOptions;
}
is there a better approach for this ?
There are a lot of ways to do such optimization. The simplest one is add bidirectional associations to Company and EstimateOptions with lazy loading.
An example for Company ( I don't test. It is just a sketch.)
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
#OneToOne(mappedBy="company", fetch = FetchType.LAZY)
private User user;
}
And do something like this (this is HQL but you can use a criteria API too)
from EstimateOptions options inner join options.company company inner join company.user user where user.name = :userName
You can see HQL joined query to eager fetch a large number of relationships for additional thoughts.
Updated
I am not sure but may be you can do something like this (without additional associations)
select options from User user inner join user.company company inner join company.estimateOptions options where user.name = :userName

How to inner join two tables using Hibernate HQL or Criteria?

#Entity
public class doctor {
#Id
private int id;
private String username;
private String password;
private String phone;
private String email;
#OneToMany(targetEntity = patient.class, cascade = CascadeType.ALL, mappedBy = "doctor")
#Cascade(value = org.hibernate.annotations.CascadeType.ALL)
private Collection<patient> patients = new ArrayList<patient>();
}
#Entity
public class patient {
#Id
private int id;
private String name;
private String surname;
private String phone;
private int systolic;
private int diastolic;
#ManyToOne
private doctor doctor;
}
For now i can retreive only doctors information by this criteria:
Criteria query = session.createCriteria(doctor.class);
query.createCriteria("patients", "p");
query.add(Restrictions.eq("p.phone", phone));
List<doctor> doctorList = (ArrayList<doctor>) query.list();
How i can with hibernate criteria retreive by giving patient phone, his doctor information and some patients information?
Something like : phone=3423423424 , then answear is :
-------------doctor's info----------------------------------patientinfo(systolic,diastolic)-----------------------
1 "Dr dre" sdfssd 243242 drdre#gmail.com 160 170
where 160 170 are the patient's information
If not with criteria, with HQL?
What you want is the following.
With Hibernate Criteria API:
Criteria query = sessionFactory.getCurrentSession().
createCriteria(Patient.class, "patient");
query.setProjection(Projections.projectionList().
add(Projections.property("patient.doctor")).
add(Projections.property("patient.systolic")).
add(Projections.property("patient.diastolic")));
query.add(Restrictions.eq("patient.phone", phone));
return query.list();
With HQL (actually just JPQL):
select p.doctor, p.systolic, p.diastolic from Patient p where p.phone = ?1
What you get in the result is value of Type List<Object[]>.
Also add #Cascade(value=CascadeType.SAVE_UPDATE) to doctor field on your Patient class.
You have bidirectional mapping so from each doctor you can get his patients and from each patient you can get his doctor information. If you need a list with patients instead a list of doctors just create analogous Criteria for patients. session.createCriteria(patient.class), with needed restrictions. you don't have to make it eager. In most cases we don't need eager fetching. It is much better initialize (Hibernate.initialize) the collection or proxy if you would need the objects outside the hibernate session.
btw use camel case when you name java classes. It's widely used convention.
The reason why your query only returns the doctors information (and not the patient's information) is because for a OneToMany relation, FetchType by default is set to LAZY, if you specify fetch type to be EAGER, hibernate will also return patients.
#OneToMany(targetEntity = patient.class, cascade = CascadeType.ALL, mappedBy = "doctor", fetch = FetchType.EAGER)
In case you are using HibernateTemplate
String hql = "from Boo where id in (:listParam)";
String[] params = { "listParam" };
Object[] values = { list};
List boos = getHibernateTemplate().findByNamedParam(hql, params, values);
Quoted from Spring Forum

Categories

Resources