Order By child field in One To One relationship Spring Data Jpa - java

I have a one to one relationship like this:
#Entity
public class Modification {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="modification_id")
Long id;
#Column(name="second_line", length=1000)
String firstLine;
#OneToOne
#JoinColumn(name="tm_satus")
StatusOrder tmStatus;
// Constructors getters and setters
}
#Entity
public class StatusOrder {
#Id
#Column(name="status")
int status;
#Column(name="status_order")
int order;
// Constructors getters and setters
}
So every Modification has a StatusOrder.
Now I want to perform a query where I select from Modification table ordered by order field in StatusOrder.
Is there a way to have a method in my repository like:
Page<Modification> findAllOrderByStatusOrder(Pageable pageable);

Try these,
#Query("SELECT m FROM Modiciation m ORDER BY m.tmStatus.order DESC")
Page<Modification> findAllOrderByStatusOrder(Pageable pageable);
or (I don't know if this'll work.)
Page<Modification> findAllOrderBytmStatus_orderDesc(Pageable pageable);

Related

Access elements of Join Tables in spring through Service

I am trying to learn it and I am facing some conceptual issues.
Let's imagine to have the entities of Employee, Project and Department where:
Employee - Department is a ManyToOne relationship
Employee - Project is a ManyToMany relationship
Right now I have written this:
Employee.java
#Entity
public class Employee {
#Id #GeneratedValue
private Long id;
private String name;
#ManyToOne
#JoinColumn(name="department_id")
private String department;
#ManyToMany
#JoinTable(name="project_employee",
joinColumns=#JoinColumn(name="project_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="employee_id", referencedColumnName="id"))
private ArrayList<Project> projects;
//constructors, getters, setters...
}
Project.java
#Entity
public class Project {
#Id #GeneratedValue
private Long id;
private String client;
#ManyToMany(mappedBy="project")
private ArrayList<Employee> employees;
//constructors, getters, setters...
}
Department.java
#Entity
public class Department {
#Id #GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy="department")
private ArrayList<Employee> employees;
//constructors, getters, setters...
}
EmployeeService.java
#Service
public class EmployeeService {
#Autowired
private EmployeeRepository employeeRep;
#Autowired
private ProjectRepository projectRep;
#Autowired
private DepartmentRepository deptRep;
public List<Project> getProjectsByEmployee(Long emplId){
}
}
The first solution that came in my mind to implement that methods was:
Get the specific employee object through the employeeRep and then loop over its list project retrieving ids and using them to retrieve project info with projectRep.
But in this case it seems I am not exploiting the table relation I have between Project and Employee, so what's the point on having it then?
But in that case it seems to me that I am not exploiting the table
relation I have between Project and Employee, so what's the point on
having it then?
In fact you exploit that ManyToMany table when you access to the Project relationship from an Employee entity (JPA performs a query for you at that time) but you don't exploit it efficiently. I will explain.
The first solution that came in my mind to implement that methods was:
Get the specific employee object through the employeeRep and then loop
over its list project retrieving ids and using them to retrieve
project info with projectRep.
That is bad idea because you don't want to perform a lot of queries while a single one rather simple could achieve the same thing.
Similarly defining a repository by entity is not mandatory :
#Autowired
private EmployeeRepository employeeRep;
#Autowired
private ProjectRepository projectRep;
#Autowired
private DepartmentRepository deptRep;
if according to your requirements, your queries rely on a pivot table (for example the employee table), just declare :
#Autowired
private EmployeeRepository employeeRep;
And perform the join from employee to project to get the list of projects for a specific employee.
In JPQL you could use fetch join on projects to get projects associated to the matching employee such as :
public List<Project> getProjectsByEmployee(Long empId){
String jpql = "SELECT emp FROM Employee e" +
"INNER JOIN FETCH e.projects" +
"WHERE emp.id=:empId";
TypedQuery<Employee> query = em.createQuery(jpql, Employee.class)
.setParameter("empId", empId);
Employee emp = query.getSingleResult();
return emp.getProjects();
}

How to implement ligh Entity version with Jpa repository?

Have a "full Entity" class:
#Entity(name = "vacancy_dec_to_words")
public class VacancyDescriptionToWords {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JoinColumn(name = "vacancy_description_id")
#ManyToOne(cascade = CascadeType.ALL)
private VacancyDescription vacancyDescription;
#JoinColumn(name = "words_id")
#ManyToOne
private Words words;
#Column(name = "qty")
private int qty;
#Column(name = "create_date")
private Date date;
//...getters and setters
In some methods I need use only 2 column from this database: word_id and qty
I try the follow way:
Projections
https://docs.spring.io/spring-data/jpa/docs/2.1.2.RELEASE/reference/html/#projections
public interface QtyWords {
Long getWords();
Integer getQty();
}
JpaReposytory:
*Query, that I use tested and it workable, I use him in JpaRepository:
#Repository
public interface SmallVDTWRepository extends JpaRepository<VacancyDescriptionToWords, Long> {
#Query(nativeQuery = true,
value = "SELECT sum(qty), words_id FROM vacancy_desc_to_words WHERE vacancy_description_id IN (" +
"SELECT id FROM vacancy_description WHERE vacancy_id IN (" +
"SELECT id FROM vacancy WHERE explorer_id = :exp))" +
"GROUP BY words_id")
List<QtyWords> getDistinctWordsByExplorer(#Param("exp") long exp);
}
But I get some interesting result when I get list of entities:
List<QtyWords> list = vdtwService.getByExplorerId(72);
I am not get any exceptions, but I have the list with are unknowns objects. This objects contains my data, which I need(qty and words_id), but I cannot get them from him.
Can I use this method (Projection) to implement this task and, in general, how to correctly implement the 'Light Entity' in this case?
Spring provides two mechanisms that can be used to limit data to be fetched.
Projections
Projections can help you to reduce data, retrieved from database, by setting what exactly attributes you want to fetch.
Example:
#Entity
class Person {
#Id UUID id;
String firstname, lastname;
#OneToOne
Address address;
}
#Entity
static class Address {
#Id UUID id;
String zipCode, city, street;
}
interface NamesOnly {
String getFirstname();
String getLastname();
}
#Repository
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
Entity graph
Annotation EntityGraph can help you to reduce amount of queries to database, by setting what exactly related entities you need to fetch.
Example:
#Entity
#NamedEntityGraph(name = "GroupInfo.detail", attributeNodes = #NamedAttributeNode("members"))
public class GroupInfo {
#Id UUID id;
#ManyToMany //default fetch mode is lazy.
List<GroupMember> members = new ArrayList<GroupMember>();
}
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name); //Despite of GroupInfo.members has FetchType = LAZY, it will be fetched because of using EntityGraph
}
There are two types of EntityGraph:
EntityGraphType.LOAD - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated according to their specified or default FetchType.
EntityGraphType.FETCH - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated as FetchType.LAZY.
PS: Also remember that you can set lazy fetch type: #ManyToOne(fetch = FetchType.LAZY) and JPA will not fetching child entities when parent is being fetched.

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 select only specific fields using spring CrudRepository?

How can I select only specific fields from the following class hierarchy?
#Entity
public class MyEntity {
#Id private Long id;
#ManyToOne
#JoinColumn(name="fk_person_id", foreignKey = #ForeignKey(name="fk_person"))
private Person person; //unidirectional
private String fieldA, fieldB, ..field M;
//many more fields and some clobs
}
#Entity
public class Person {
#Id private Long id;
private String firstname;
private String lastname;
}
interface MyEntityRepository extends CrudRepository<MyEntity, Long> {
List<MyEntity> findByIdAndPersonFirstnameAndPersonLastname(long id, String firstname, String lastname);
}
This works perfectly, just the performance is very poor as MyEntity and also Person have some fields and associations that I would like to prevent to be fetched in this specific case (eg clob/texts).
Question: how can I write this query to find the result set, and just fetch the fields that are absolutely required (let's assume id, fieldA, fieldB from MyEntity?
Use lazy initialization for the non necessary fields:
FetchType.LAZY = Doesn’t load the relationships unless explicitly “asked for” via getter
FetchType.EAGER = Loads ALL relationships
For example, in Person:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="fk_person_id", foreignKey = #ForeignKey(name="fk_person"))
private Person person; //unidirectional
Mark the unwanted fields as lazy (beware of this documented warning though), or create a dedicated class with the properties you want, and use projections in your query:
select new com.foo.bar.MyEntityWithFirstNameAndLastName(m.id, person.firstname, person.lastname)
from MyEntity m
join m.person person
where ...
If Person is an often-used entity, and contains large blobs that should rarely be fetched, you should consider storing the blobs in a separate entity, and use a OneToOne lazy association.

Initializing many-to-many association in Hibernate with join table

I have a Company entity that I fetch with a JPQL query with Hibernate. The entity has a many-to-many association with a Keyword entity. Since the join table has an additional column is_active, this table has been mapped to a CompanyKeyword entity. So the association is like this:
Company <-- CompanyKeyword --> Keyword
Now, the association from the Company entity is lazy, and it is not initialized by my JPQL query, as I want to avoid creating a cartesian product performance problem. That is why I want to initialize the association after running the JPQL query, e.g. like this:
#Service
class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyRepository companyRepository;
#Transactional
public Company findOne(int companyId) {
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.companyKeywords());
return company;
}
}
For a "normal" many-to-many association, this would work great, as all of the associated entities would be fetched in a single query. However, since I have an entity between Company and Keyword, Hibernate will only initialize the first part of the association, i.e. from Company to CompanyKeyword, and not from CompanyKeyword to Keyword. I hope that makes sense. I am looking for a way to initialize this association all the way without having to do something like this:
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.getCompanyKeywords());
for (CompanyKeyword ck : company.getCompanyKeywords()) {
Hibernate.initialize(ck.getKeyword());
}
The above code is neither clean, nor good in terms of performance. If possible, I would like to stick to my current approach of using a JPQL query to fetch my Company entity and then initializing certain associations afterwards; it would take quite a bit of refactoring to change this in my project. Should I just "manually" fetch the association with a second JPQL query, or is there a better way of doing it that I haven't thought of?
Below are my mappings. Thanks in advance!
Company
#Entity
#Table(name = "company")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Size(max = 20)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<CompanyKeyword> companyKeywords = new HashSet<>();
// Getters and setters
}
CompanyKeyword
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyKeyword implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Keyword.class)
#JoinColumn(name = "keyword_id")
private Keyword keyword;
#Column(nullable = true)
private boolean isActive;
// Getters and setters
}
CompanyKeywordPK
public class CompanyServicePK implements Serializable {
private Company company;
private Service service;
public CompanyServicePK() { }
public CompanyServicePK(Company company, Service service) {
this.company = company;
this.service = service;
}
// Getters and setters
// hashCode()
// equals()
}
Keyword
#Entity
#Table(name = "keyword")
public class Keyword {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
// Fields and getters/setters
}
You'll indeed need to execute an additional JPQL query, fetching the company with its companyKeyWords and with the keyword of each CompanyKeyWord.
You could also doing it by simply looping and initializing every entity, and still avoid executing too many queries, by enabling batch fetching.

Categories

Resources