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();
}
Related
I am new to Reactive programming. I have to develop a simple spring boot application to return a json response which contains the company details with all its sub companies and employees
Created a spring boot application (Spring Webflux + Spring data r2dbc )
Using following Database Tables to represent the Company and Sub Company and Employee relationship (it is a hierarchical relationship with Company and Sub Company where a company can have N number of sub companies, and each of these sub companies can have another N number of sub companies etc and so on)
Company
id
name
address
Company_SubCompany
id
sub_company_id (foreign key references id of above company table )
Employee
id
name
designation
company_id ( foreign key references id of above company table )
Following are the java model classes to represent the above tables
Company.java
#Data
#ToString
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Company implements Serializable {
private int id;
private String name;
private String address;
#With
#Transient
private List<Company> subCompanies;
#With
#Transient
private List<Employee> employees;
}
Employee.java
#Data
#ToString
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Employee implements Serializable {
#Id
private int id;
private String name;
private String designation;
}
Following repositories are created
#Repository
public interface CompanyRepository extends ReactiveCrudRepository<Company, Integer> {
#Query("select sub.sub_company_id from Company_SubCompany sub inner join Company c on sub.sub_company_id = c.id where sub.id = :id")
Flux<Integer> findSubCompnayIds(int id);
#Query("select * from Company c where id = :id")
Mono<Company> findCompanyById(Integer id);
}
#Repository
public interface EmployeeRepository extends ReactiveCrudRepository<Employee, Integer> {
#Query("select * from Employee where company_id = :id")
Flux<Employee> findEmployeeByCompanyId(Integer id);
}
In Company_SubCompany table, the super company is represented with id -1. So using this id we are now able to get the super parent company .
With the below service code i am now able to get first level of company and its employees. But i am not sure how to get all the nested sub companies and add to it
#Service
public class ComanyService {
#Autowired
CompanyRepository companyRepository;
#Autowired
EmployeeRepository employeeRepository;
public Flux<Company> findComapnyWithAllChilds() {
Flux<Integer> childCompanyIds = companyRepository.findSubCompnayIds(-1);
Flux<Company> companies = childCompanyIds.flatMap(p -> {
return Flux.zip(companyRepository.findCompanyById(p),
employeeRepository.findEmployeeByCompanyId(p).collectList(),
(t1, t2) -> t1.withEmployees(t2));
});
return companies;
}
}
I am very new to reactive, functional programing and r2dbc, so please help me to how to resolve my problem. If there is any other better approach available will use that one also. The requirement is to get the company , all its employees and sub companies (upto N leavel) .
this code can help you, this approach fill the List objects from database calls
public Flux<Company> findComapnyWithAllChilds(){
return companyRepository.findAll()
.flatMap(companies ->
Mono.just(companies)
.zipWith(employeeRepository.findEmployeeByCompanyId(companies.getId()).collectList())
.map(tupla -> tupla.getT1().withEmployees(tupla.getT2()))
.zipWith(anotherRepository.findAnotherByCompanyId(companies.getId()).collectList())
.map(tupla -> tupla.getT1().withAnother(tupla.getT2()))
);
}
I came up with an example demonstrating the one-to-one relationship between Employee class and EmployeeDetail class:
public class Employee {
private Long empId;
private String name;
private EmployeeDetail employeeDetail;
//gettter and setter
}
public class EmployeeDetail{
private Long empDetailsId;
private String empFullName;
private String empMailId;
private Employee employee;
//getter and setter..
}
In the Employee class, there's an EmployeeDetail field, and in EmployeeDetail class, there's an Employee field. I understand that as each Employee has its own EmployeeDetail and each EmployeeDetail belongs to only one Employee, but there're 2 points that confuse me:
What if two or more Employees have the same EmployeeDetail (and vice versa)? Is there any way to handle this in Java code or I can only do that in a relational database management system?
In SQL, foreign keys (IDs) represent the relationship between two tables, but in the above example, they use class objects instead. Please help me explain that
In an one-to-one relation, an EmployeeDetail can only belong to one Employee. If an EmployeeDetail should be able to belong to multiple Employees you will need a Many-to-one relationship (Many Employees to one Employee Detail).
The reason the foreign keys are noted by class objects is that this is most likely a Hibernate example, which uses Java Objects for database management. (Even if it misses some annotation for a clear Hibernate example)
Here you can find an example about Hibernate and database relations
look at this ex :
#Entity
#Table(name="d_agent_auth")
public class AgentAuth implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int idAuth;
#NotNull
private String password;
private Date dateCreation;
#OneToOne
#JoinColumn(name="code_agent")
private Agent agent;
public AgentAuth() {
super();
}
}
there is two way a navigable one sens and two sens that's means in the agent class you will not find a key reference agentAuth or two sens means that's in the agent you will find it :
#Entity
#Table(name="d_agent")
public class Agent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String codeAgent;
#ManyToMany(mappedBy="DAgents", cascade=CascadeType.MERGE)
private List<Profil> profil;
#OneToMany(mappedBy="DAgent")
private List<SuiviDossier> DSuiviDossiers;
#OneToMany(mappedBy="DAgent")
private List<SuiviLot> suiviLots;
#OneToMany(mappedBy="agent")
private List<Affectation> affecter;
public Agent() {
super();
}
}
Hibernate Mapping
How to implement such a code?
Each company has two properties, they are company name and estimated annual earnings.
There are two types of companies: 1- Main company, 2 - Subsidiary company.
The company can belong only to one company but can have a few child companies.
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String companyName;
private double estimatedAnnualEarnings;
private Company company; // here need to do a #OneToOne
private List<Company> subsidiaryCompany; // here need to do a #OneToMany
}
In your Implementation you should use :
The #Entity annotation in your class level, so the entity can be persisted to database.
The #Column annotation with the companyName and estimatedAnnualEarnings properties, so they can be persisted as columns in the database.
#ManyToOne annotation with the company field, so it can be mapped with a self-reference relationship.
The same goes with the subsidiaryCompany List which needs to be mapped with #OneToMany annotation to have a relationship too.
This is how should be your code:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String companyName;
#Column
private double estimatedAnnualEarnings;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="mainCompanyId")
private Company mainCompany;
#OneToMany(mappedBy="mainCompany")
private List<Company> subsidiaryCompanies;
//getters and setters goes here
}
Note
I changed the name of company field to mainCompany and
subsidiaryCompaniy to subsidiaryCompanies for better readability
and to make it fit the logic better.
If you want to give your entity a different name in the database you
should use #Table(name="differentName") in the class level with
#Entity annotation, the smae thing with the columns you can add
name property to the #Column annotation i.e
#Column(name="company_name") if you want different names.
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.
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.