Using JPA Criteria API to fetch a list - java

I have an entity Tutor with two relationships in class Schedule. Code below defines the relationship between the two entities.
public class Tutor{
//list of schedules created by tutor
#OneToMany
List<Schedule> schedules;
}
then
public class Schedule{
//tutor who created this schedule
#ManyToOne
Tutor tutor;
//tutors who would be examiners of this schedule
#ManyToMany
#JoinTable
List<Tutor> examiners;
}
how can i query Tutor and fetch all Schedule(s) where Tutor is an examiner using criteria api

Related

Many to Many relation with dependency inversion

I have a multimodule application with two modules:
department-management
communication-management
now in my department-management i have an entity Department and in communication-management module i have MailingGroup entity.
Also the communication-management depends on department-management module.
Now i want to have bidirectional ManyToOne relation between Department and MailingGroup
#Entity
public class Department {
#OneToMany(mappedBy = "department")
List<MailingGroup> mailingGroups;
}
#Entity
public class MailingGroup{
#ManyToOne
#JoinColumn(name = "DEPARTMENT_ID")
Department department;
}
This, of course, is not archiveable the way above, but can i archive this bidirectional relation using interfaces? My initial idea was to solve it like this:
public interface MailingGroupProvider {
Department getDepartment()
}
#Entity
public class Department {
#OneToMany(mappedBy = "department")
List<MailingGroupProvider> mailingGroups;
}
#Entity
public class MailingGroup implements MailingGroupProvider {
#ManyToOne
#JoinColumn(name = "DEPARTMENT_ID")
Department department;
}
But it raises questions:
Is this prefered solution in such cases?
What methods should my interface provide to be treated as Entity by JPA?
is this even possible what im trying to do?
First Approach is Perfect: You need to add a Relationship in Both Entity Sides.
departmentid is the foreign key of the mailing group Table. Apply Relationship in both entity sides it will work.
#OneToMany Relationship means u can have one Department can have Many Communication.
Bidirectional means suppose if u do any operation in Communication Management like deleting Communication it will affect on Department Table Also. It will Remove Departmentid matched in the Department Table

Cannot persist Entity with Shared ID

I am practicing JavaEE technologies. Now I am focusing in JPA with Hibernate. The project has the following entities:
Book:
#Entity #Table(name = "book")
public class Book extends BaseEntity<Long> {
#Id
private Long id;
#NotNull
private String name;
#OneToOne(mappedBy = "book", cascade = CascadeType.ALL)
private BookDetails details;
//getters/setters
}
BookDetails:
#Entity
#Table(name = "book_details")
public class BookDetails extends BaseEntity<Long> {
#Id
private Long id;
#MapsId
#OneToOne
private Book book;
#NotNull
#Column(name = "number_of_pages")
private int numberOfPages;
//getters/setters
}
Now, the respective EJB Service classes:
BookService:
#Stateless
public class BookService extends BaseEntityService<Long, Book> {
public void createBook(Book book) {
super.getEntityManager().persist(book);
}
//update, delete, find and findAll methods
}
BookDetailsService:
#Stateless
public class BookDetailsService extends BaseEntityService<Long, BookDetails> {
public void createBookDetails(BookDetails details) {
super.getEntityManager().persist(details);
//super.persist(details); //This method would not work to persisting entity with shared Id, because it verifies if ID is null
}
//update, delete and find methods
}
The problem:
When I try to persist a new book along with its details as follows:
Book b = new Book();
b.setId(123L);
b.setName("Bible");
bookService.createBook(b);
//The entity Book is correctly persisted in the DB.
BookDetails d = new BookDetails();
d.setNumberOfPages(999);
d.setBook(b);
//b.setDetails(d); //don't do this
bookDetailsService.createBookDetails(d);
//BookDetails is not persisted in the DB, throws exception....
Throws the following exception:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '123' for key 'PRIMARY'
The Book entity is persisted but not the BookDetails.
I followed this tutorials:
Hibernate Tips: How to Share the Primary Key in a One-to-One Association
The best way to map a #OneToOne relationship with JPA and Hibernate
Aditional Information:
JDK 1.8
Payara 5
MySQL 8.0
Hibernate 5.3.4
OmniPersistence library. (Utilities for JPA, JDBC and DataSources)
You can look at the project repository here.
I could already solve the problem and it was due to the way the entities persisted and the transactionality of the methods in a JavaEE application.
When calling a business method of an EJB in which classes are persisted, upon completion of this method the transaction ends, therefore the persistence context is closed and the attached classes (managed) become detached (unmanaged). See this.
So after persisting Book b:
bookService.createBook (b);
That transaction ends and that is why the book is persisted and, in addition, it is no longer managed. So when I link the book to the details:
d.setBook (b);
That is something that must be done to persist an entity that shares Id, the parent entity (in this case b) has to be managed.
There are two solutions that i could find:
Solución 1:
Attach book b to the persistence context of the details creation transaction. See this.
b = getEntityManager.merge(b);
Solución 2: (The one that I chose)
Make the call of the BookService business method within the business method of the BookDetailsService, which implies transferring the dependency injection. Thus, a single transaction is made by persisting the two entities.
//BookDetailsService class
#Inject
BookService bookService;
public void createBookDetails(BookDetails details) {
Book b = new Book();
b.setId(details.getId());
b.setName("Bible");
bookService.createBook(b);
details.setBook(b);//b still managed
super.persist(details);
}
I think this solution is more cleaner and coherent, because BookDetailsService will need always the services from BookService.
#OneToOne on BookDetails property of Book class indicates that there is a one-to-one association from Book to BookDetails. Also, note the use of cascade = CascadeType.ALL.Since BookDetails entity cannot exist without Book entity, with this cascade setting, BookDetails entity will be updated/deleted on subsequent update/delete on Book entity.
You have to add cascadetype on book details:
#OneToOne(mappedBy = "book", cascade = CascadeType.ALL)
private BookDetails details;
Refer here for more details
CascadeType.ALL will do all the operations such as save, delete and merge when book entity is saved.
Now as we have added CascadeType.ALL in Book entity you have to set bookdetails object in book. See below:
Book b = new Book();
BookDetails d = new BookDetails();
b.setBookDetails(d);
b.setId(123L);
b.setName("Bible");
d.setId(111L); //random id that is not existing if d is new record
d.setNumberOfPages(999);
bookService.createBook(b);
Saving book entity will save book as well as book details entity. You may also try hibernate.show_sql=true to see what all queries are executed when you save book entity.

JPA mappedBy don't persisting entity with relationship

For example, I have the following entities:
Room entity:
#Entity
public class Room extends Base {
#OneToMany(mappedBy = "room")
private List<Person> persons;
//getters and setters
}
And the Person entity:
#Entity
public class Person extends Base {
#ManyToOne
private Room room;
private String name;
//getters and setters
}
If I add person to room's persons list and then persist, the room is added, but person is not. I thought, enough is to add mappedBy parameter to OneToMany annotation, but.. what should I do else?
You need to set CascadeType=PERSIST or ALL on #OneToMany. And Also set room variable in Person class to populate foreign key in Person table.
Do the opposite, set a Room as the room field of a Person instance:
Room room = ...;
person.setRoom(room);
// persist the person
With the mappedBy, you specify that which entity owns the relationship. Updating the relationship between those two entities is the responsibility of the owning side.
In your example, the Person owns the relationship, so it's his responsibility to update the foreign key, not Room's, so when you add a Person to list of persons in Room and update the Room, Room wouldn't update the foreign key for you.

Use Join Table in Separate Entity

I have a ManyToMany relationship established with a join table that is exactly like the one described on the Java Persistence wiki Example of a ManyToMany relationship annotations. Using that wiki example of Employees & Projects as a reference, the code listed in the example works fine to create three tables: EMP, PROJ and the EMP_PROJ join table. What I would like to do is use that EMP_PROJ join table in a separate entity. I don't want to add additional columns to the EMP_PROJ join table. For example, suppose an administrator associates projects with an employee. That list is stored in EMP_PROJ. What I would like to do is create a separate entity called ManagerReport that will return, for an employee, the list of projects associated with that employee. The manager can then enter specific info regarding each project for the employee, like start date, end date, performance, etc.
Below are the tables and sample code pulled from the wiki page.
EMPLOYEE (table)
ID FIRSTNAME LASTNAME
1 Bob Way
2 Sarah Smith
EMP_PROJ (table)
EMP_ID PROJ_ID
1 1
1 2
2 1
PROJECT (table)
ID NAME
1 GIS
2 SIG
#Entity
public class Employee {
#Id
#Column(name="ID")
private long id;
...
#ManyToMany
#JoinTable(
name="EMP_PROJ",
joinColumns={#JoinColumn(name="EMP_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="PROJ_ID", referencedColumnName="ID")})
private List<Project> projects;
...
}
You will have to create a ManagerReportProject entity which maps to EMP_PROJ table with #Table annotation.
In Employee entity, instead of #ManyToMany mapping for a collection of Projects use #OneToMany mapping for collection mapping to ManagerReportProject entities.
You will still be able to get a list of employee's projects because each ManagerReportProject further points to Project. You can even create a helper getProjects() method inside Employee to get a list of projects. Method must be annotated with #Transient to mark getProjects() method as not JPA persitent (by default all properties or fields inside Entity mappings are persitent)
#Entity
public class Employee
{
#Id
#Column(name="ID")
private long id;
...
#OneToMany...
private List<ManagerReportProject> managerReportProjects;
...
/*
YOU DON'T NEED THIS ANYMORE:
#ManyToMany
#JoinTable(
name="EMP_PROJ",
joinColumns={#JoinColumn(name="EMP_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="PROJ_ID", referencedColumnName="ID")})
private List<Project> projects;
*/
#Transient
public List<Project> getProjects()
{
List<Project> projects = new ArrayList<Project>();
for(ManagerReportProject managerReportProject: managerReportProjects)
{
projects.add(managerReportProject.getProject());
}
return projects;
}
...
}
ManagerReportProject should point to Employee, Project and Manager entity with #ManyToOne association.
Put manager report specific columns into ManagerReportProject (start date, end date, performance, etc.).
ManagerReportProject maps to EMP_PROJ table with #Table annotation.
#Entity
#Table(name= "EMP_PROJ")
public class ManagerReportProject{
#Id
#Column(name="ID")
private long id;
//manager report columns
private Date startDate;
private Date endDate;
performance, etc.
#ManyToOne...
private Manager manager;
...
#ManyToOne...
private Employee employee;
#ManyToOne...
private Project project;
...
}
Create a Manager entity, use #OneToMany for collection mapping to ManagerReportProject entities:
#Entity
public class Manager {
#Id
#Column(name="ID")
private long id;
...
#OneToMany...
private List<ManagerReportProject> managerReportProjects;
...
}
Now you can enter specific info regarding each project for the employee, like start date, end date, performance, etc.
This is a sketch just to demonstrate an idea of how to edit existing manager report for a specific empolyee working for a specific manager:
Emyployee employee = ...
Manager manager = ...
List<ManagerReportProject> managerReportProjects= employee.getManagerReportProjects()
for(ManagerReportProject managerReportProject: managerReportProjects )
{
if(manager.equals(managerReportProject.getManager()))
{
Project project = managerReportProject.getProject();
managerReportProject.setStartDate(...);
managerReportProject.setEndDate(...);
managerReportProject.setperformance(...);
...
}
}

JPA 2.0 / Hibernate InheritanceType TABLE_PER_CLASS and OneToMany/ManyToOne bidirectional relation

We have 2 tables (an active table and an archive table) which have the same structure (ex. Employee and EmployeeArchive). To be able to leverage common code to use results for both tables we have an abstract parent class that defines all the methods and annotations.
We like to be able to perform queries that will use the same query for both tables and union the results together.
We have another entity/table (ex. Organization) with a onetomany/manytoone bidirectional relationship with Employee; Organization has a List of Employees and every employee has an organization.
When getting the employees of an organization via the association we only want the employees from the active table not the archive.
Is there a way to achieve what we are attempting or a viable workaround?
We have tried various implementations of #MappedSuperclass, #Entity/#InheritanceType.TABLE_PER_CLASS to try to achieve what we want. Each implementation would nearly achieve what we want but not completely. For example to be able to query both tables we could have an abstract parent Entity with InheritanceType.TABLE_PER_CLASS but then we could not have the mappedBy relationship to Employee in the Organization. We can use a MappedSuperclass as the parent to be able to have the correct relationship but then we cannot query both the Archive and Active tables via the union.
Here is basically what we are trying to layout:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractEmployee {
#ManyToOne
#JoinColumn(name="employeeId", nullable=false)
Organization org;
...
}
#Entity
public class Employee extends AbstractEmployee {
}
#Entity
public class EmployeeArchive extends AbstractEmployee {
}
#Entity
public class Organization {
#OneToMany(cascade=ALL, mappedBy="org")
List<Employee> employees;
...
}
Code
public List<AbstractEmployee> getAllEmployees()
{
Query query = em.createQuery("SELECT e FROM AbstractEmployee e where e.name = ‘John’", AbstractEmployee.class);
return query.getResultList();
}
public List<Organization> getOrganizations()
{
Query query = em.createQuery("SELECT e FROM Organization o ", Organization.class);
List<Organization> orgs = query.getResultList();
// fetch or eager fetch the Employees but only get the ones from the active employee table
return orgs;
}
We also tried to have the parent class extend the MappedSuperclass and put the implementation and annotations in the MappedSuperclass but we get an AnnotationException for the relationship of the Organization
#MappedSuperclass
public abstract class AbstractMapped {
#ManyToOne
#JoinColumn(name="employeeId", nullable=false)
Organization org;
}
#Entity
#Inheritance(#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS))
public abstract class AbstractEmployee extends AbstractMapped {
... `Constructors` ...
}
On deployment we get the following exception:
Caused by org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: Employee.org in Organizaztion.employees
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:685)
You can do this by changing the mapping of Organization to Employee so that it uses a relationship table, rather than having the org field in the Employee table. See the example in the Hibernate documentation, which for you would look something like:
#Entity
public class Organization {
#OneToMany(cascade=ALL)
#JoinTable(
name="ACTIVE_EMPLOYEES",
joinColumns = #JoinColumn( name="ORGANIZATION_ID"),
inverseJoinColumns = #JoinColumn( name="EMPLOYEE_ID")
)
List<Employee> employees;
...
}
However, I have to say that I think having two tables to represent current vs archived Employees is a bad idea. This sounds to me like a 'soft delete' kind of situation, which is better handled with an in-table flag (IS_ACTIVE, or something). Then you don't have these odd abstract classes to do your queries, multiple tables with the same kind of data, etc etc. A bit of a description of this strategy is here.
Then you can use the non-join table mapping that you've already got, and use the #Where annotation to limit the employees in an organization to ones that have IS_ACTIVE set to true. An example of this approach is here.
This is one of the annoying things about hibernate. The way to do this is to have another abstract class, AbstractMapped, which simply looks like this:
#MappedSuperclass
public abstract class AbstractMapped {
}
and then have AbstractEmployee extend AbstractMapped. Then you have AbstractEmployee as both an Entity and a Mapped Superclass, even though the two tags are mutually exclusive.
AbstractEmployee should be the #MappedSuperClass, and should not be an #Entity, which creates a table for the class.
Organization should contain a List<AbstractEmployee> not of Employee.

Categories