JPA distinct inner join with Condition on joined table - java

I have two tables below:
#Entity
#Table(name="COLLEGE")
public class College {
private Long collegeId;
private List<Student> students;
#Id
#Column(name = "COLLEGE_ID")
public Long getCollegeId() {
return this.collegeId;
}
public void setCollegeId(final Long collegeId) {
this.collegeId= collegeId;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "college")
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
public List<Student> getStudents() {
if (this.students== null) {
this.students= new ArrayList<>();
}
return this.students;
}
public void setStudents(List<Student> students){
this.students = students
}
}
#Entity
#Table(name="STUDENT")
public class Student {
private Long studentId;
private College college;
private String name;
private String department;
#Id
public Long getStudentId() {
return this.studentId;
}
public void setStudentId(Long studentId) {
this.studentId = studentId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COLLEGE_ID")
public getCollege() {
return this.college;
}
public setCollege(College college) {
this.college = college;
}
......
......
}
I want to get the college object with list of students of a specific department. The SQL query is below:
select *
from COLLEGE c
inner join STUDENT s on c.COLLEGE_ID= s.collegeId
where c.COLLEGE_ID=12345 and s.DEPARTMENT="ENGINEERING";
So far I have tried the below JPA query but it is returning multiple College objects.
SELECT DISTINCT c
FROM COLLEGE c
INNER JOIN c.students s
where c.collegeId= :collegeid and s.department = :department
How to return a single college object with list of students with filtered department?
NOTE: I can't alter the entity objects used here.

Try to use this query, using JOIN FETCH instead INNER JOIN:
SELECT DISTINCT c FROM College c
JOIN FETCH c.students s
WHERE c.collegeId= :collegeid
AND s.department = :department

Related

Save with Spring data jpa save method in Many to Many

There is a many-to-many relationship between students and an edition of a course, which would be the enrollment in that course. This relationship can be seen in the EditionEnrollment class.
public class EditionEnrollment {
#EmbeddedId
private Id id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "student_id", insertable = false, updatable = false)
protected Student student;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "edition_id", insertable = false, updatable = false)
protected E edition;
#ColumnDefault("false")
#Column(nullable = false)
protected Boolean isDropped = false;
public EditionEnrollment() {
}
public EditionEnrollment(Student student, E edition) {
this(student, edition, false);
}
public EditionEnrollment(Student student, E edition, Boolean isDropped) {
id = new Id(student.getId(), edition.getId());
this.student = student;
this.edition = edition;
this.isDropped = isDropped;
}
// methods
#Embeddable
public static class Id implements Serializable {
#Column(name = "student_id")
private Long studentId;
#Column(name = "edition_id")
private Long editionId;
public Id() {
}
public Id(Long studentId, Long editionId) {
this.studentId = studentId;
this.editionId = editionId;
}
//methods
}
Repository
public interface EditionEnrollmentRepository extends JpaRepository<EditionEnrollment, EditionEnrollment.Id> {}
Service
#Service
public record EditionEnrollmentService(CourseEnrollmentRepository repository) {
public void save(CourseEnrollment courseEnrollment) {
repository.save(courseEnrollment);
}
public Optional<CourseEnrollment> findById(CourseEnrollment.Id id) {
return repository.findById(id);
}
// other methods
}
There is a list of students to be enrolled, but it is possible that some of them are already enrolled before and they cannot be enrolled again. My solution to this problem was to ask if said enrollment existed and in that case, then I did not call the save method.
AtomicInteger count = new AtomicInteger();
AtomicInteger saved = new AtomicInteger();
selectedStudents.getListDataView().getItems().forEach(student -> {
count.getAndIncrement();
CourseEnrollment ce = new CourseEnrollment(student, edition);
if (courseEnrollmentService.findById(ce.getId()).isEmpty()) {
courseEnrollmentService.save(ce);
saved.getAndIncrement();
}
});
But with this solution, although it works, a query is executed twice when the student has not been enrolled, the one of the findById of the if and another that executes the save method
select ...
from course_enrollment courseenro0_
where courseenro0_.edition_id=?
and courseenro0_.student_id =?;
select ...
from course_enrollment courseenro0_
where courseenro0_.edition_id=?
and courseenro0_.student_id =?;
insert
into course_enrollment (is_dropped, evaluation_id, edition_id, student_id)
values (?, ?, ?, ?)
How could I improve this?

Hibernate - Foreign key is empty while using mappedBy

I've 2 tables Employee & Vehicle, where one employee can have multiple vehicles.
Below is the mapping that I've defined:
Employee.java
#Entity(name = "emp_details")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int empId;
#OneToMany(cascade = CascadeType.ALL, mappedBy="employee")
private List<Vehicle> vehicles = new ArrayList<>();
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public List<Vehicle> getVehicles() {
return vehicles;
}
public void setVehicles(List<Vehicle> vehicles) {
this.vehicles = vehicles;
}
}
Vehicle.java
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int vehicleId;
#ManyToOne
#JoinColumn(name="empId")
private Employee employee;
private String name;
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public int getVehicleId() {
return vehicleId;
}
public void setVehicleId(int vehicleId) {
this.vehicleId = vehicleId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Main class
public class HibernateTest {
public static void main(String[] args) {
Employee emp = new Employee();
Vehicle vehicle = new Vehicle();
vehicle.setName("Honda");
emp.getVehicles().add(vehicle);
SessionFactory sFactory = new Configuration().configure().buildSessionFactory();
Session session = sFactory.openSession();
session.beginTransaction();
session.save(emp);
session.getTransaction().commit();
session.close();
StandardServiceRegistryBuilder.destroy(sFactory.getSessionFactoryOptions().getServiceRegistry());
}
}
But when I execute this, Vehicle.employee_id is empty. I was expecting that my foreign key will be inserted there.
What am I missing?
Thank You
You need to show us the code that persists your entities, but my guess is that you are not setting the employee to the vehicle.
You need to manage both sides of bidirectional entity relationship. Your code should look something like this
employee.getVehicles().add(vehicle);
vehicle.setEmployee(employee);
session.save(employee);
UPDATE:
In this case, Vehicle is the owning side of the relation because the foreign key is in its database table. You just added the new vehicle to the employee's list of vehicles. When you save the employee, there's nothing to change in Employees database, and the save operation cascades to Vehicle. Vehicle does not have its employee set, it is null, so it puts null in empId column
Bottom line, you have to make sure both sides of bidirectional relationship are wired up correctly.
If you have a bidirectional relationship you have to
set the relation on both sides.
That means you have to set the employee for your vehicle also.
You can do this by calling
vehicle.setEmployee(emp);
and then store or update your entity (if it's not attached to session already).
Usually to set a bidirectional relationship you provide special methods in your entities.
public class Vehicle {
...
public void setEmployee(Employee employee) {
this.employee = employee;
employee.addVehicle(this)
}
...
}
public class Employee {
...
public void addVehicle(Vehicle v) {
if(!vehicles.contains(v)) {
vehicles.add(v);
}
if(!this.equals(v.getEmployee()) {
v.setEmployee(this);
}
}
...
}
In your Employee entity class, you haven't used #Column annotation on empId. In Vehicle class you are referencing Employee using #JoinColumn(name="employee_id"),so column employee_id must exist in emp_details table. So you need to modify your Employee class to something with
#Id
#GeneratedValue
#Column(name="employee_id")
private int empId;
I think need to edit your code. you miss the entity jpa rule. you can some search jpa entity. you read description for you. link : enter link description here
(Modified your Employee.java)
#Entity(name = "employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int idx;
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "employee")
private List<Vehicle> vehicle = new ArrayList<>();
...
}
(Modified your Vehicle.java)
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int idx;
#ManyToOne
#JoinColumn(name="employee_id")
private Employee employee;
...
}
You only need to cascade onetoMany from Employee Entity

Join external column to Hibernate entity using native SQL

I have a (simplified) table structure that looks something like that:
customer table:
id name
-------------------
1 customer1
alias table:
customer_id alias
-------------------------------
1 customer one
1 customer uno
When I run the following query I easily get the list of aliases per customer:
select * from customer_alias where customer_id=1;
I would like to use this query in my hibernate to populate a list of type String. I tried using #Formula as follows:
#Entity
#Table(name = "customer")
public class Customer {
#Id
#Column(name = "id")
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#Column(name="name")
private String name;
#Formula("(select alias from customer_alias where customer_id = id)")
private List<String> aliases;
// Getters, setters, etc...
}
It didn't work and I got this exception:
Could not determine type for: java.util.List, at table: customer, for columns: [org.hibernate.mapping.Formula( (select alias from customer_alias where customer_id = id) )]
Is there anyway to achieve this? Doesn't have to be with #Formula of course. Any reasonable way would be great.
Here is an SQLFiddle of my example
You could use #ElementCollection for having a List of related aliases without the need to map the whole entity:
#ElementCollection
#CollectionTable(name = "customer_alias", joinColumns = #JoinColumn(name = "customer_id") )
#Column(name = "alias")
private List<String> aliases;
See also:
Difference between #OneToMany and #ElementCollection?
I think you don't want to use OneToMany annotation as the second table is just a list of strings you want to find something more elegant that would not require me to create another entity.
You can use #ElementCollection as below:
#Entity
#Table(name="college")
class College implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="college_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int collegeId;
#Column(name="name")
private String collegeName;
#ElementCollection
#CollectionTable(name="student", joinColumns=#JoinColumn(name="college_id"))
#Column(name="student_name")
private Set<String> students;
public College() {
}
public Set<String> getStudents() {
return students;
}
public void setStudents(Set<String> students) {
this.students = students;
}
public int getCollegeId() {
return collegeId;
}
public void setCollegeId(int collegeId) {
this.collegeId = collegeId;
}
public String getCollegeName() {
return collegeName;
}
public void setCollegeName(String collegeName) {
this.collegeName = collegeName;
}
#Override
public String toString() {
return "College [collegeId=" + collegeId + ", collegeName=" + collegeName + ", students=" + students + "]";
}
}
I don't think #Formula annotation supports collection it can only be applied for single-valued properties. Can't say if if there exists any tweak.

Hibernate Criteria on Referenced table

I have a function that merges two tables, each of these tables has a column that I want to filter.
#Entity
public class Contacts {
#Id
private int id;
#ManyToOne //reference user_id = id
private User user;
#ManyToOne //reference people_id = id
private People people;
//getters and setters
}
#Entity
public class User {
private int id;
private int name;
private Enterprise enterprise;
//getters and setters
}
#Entity
public class People {
private int id;
private int name;
//..others fields
private Enterprise enterprise;
//getters and setters
}
I need to list all "Contacts" where my enterprise id = 1. On a simple select (SQLServer), it will be:
SELECT c.* FROM CONTACTS c
INNER JOIN User u ON u.id = c.user_id
INNER JOIN People p on p.id = p.people_id
WHERE u.empresa_id = 1
I can't figure out how to do it with Criteria API, I already tried the follow code, but I keep receiving an error.
//code..public List<Obj> list(int id) {
Criteria crit = session.createCriteria(Contacts.class);
crit.add(Restrictions.eq(user.enterprise.id, id)); //it doesn't work!
crit.list();
}
org.hibernate.QueryException: could not resolve property: user.enterprise.id of: sys.com.model.Contacts
here i am writing code for sample using criteria.
public List<Student_document> getUserById(int id) {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(
Student_document.class);
criteria.add(Restrictions.eq("user_document.user_Id", id));
return criteria.list();
}

How to retrieve values using FetchType.Eager?

I have three classes which are country,state and suburb. Each country has many states and each state has many suburbs.
My hibernate is 4.2.1.Final.
The problem is that although FetchType of states is defined EAGER, I cant retrieve suburbs of each state.
I read this answer as well. As I do not want to retrieve all the states ad suburbs any time I am retrieving the countries. So I suppose need to override the fetching strategy using criteria rather than using
#LazyCollection(LazyCollectionOption.FALSE)
Country
#Entity
public class Country {
private List<States> states;
...
public Country(){
this.states = new ArrayList();
}
#OneToMany (cascade = CascadeType.ALL)
public List<States> getStates() {
return states;
}
....
}
States
#Entity
public class States {
private long id;
private String name;
private List<Suburbs> suburbs;
...
public States(){
this.suburbs = new ArrayList();
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<Suburbs> getSuburbs() {
return suburbs;
}
}
Suburbs
#Entity
public class Suburbs {
private long id;
private String name;
....
}
Code
Criteria criteria = session.createCriteria(Country.class, "country")
.createAlias("country.states", "states");
ProjectionList pl = Projections.projectionList();
pl.add(Projections.property("states.id").as("id"));
pl.add(Projections.property("states.name").as("name"));
criteria.setProjection(pl);
criteria.setResultTransformer(new
AliasToBeanResultTransformer(States.class));
criteria.add(Restrictions.eq("country.id", id));
List<States> statesList = new ArrayList();
statesList = (List<States>) criteria.list();
System.out.println(">>>"
+ statesList.get(0).getSuburbs().size());
>>>>> returns Zero
Inspired by this and link
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();

Categories

Resources