I am new to Hibernate and I am trying to figure out how to query a many to many relationship mapped as an entity due to the need of an extra column.
In particular, following the example I found at codejava.net (http://www.codejava.net/frameworks/hibernate/hibernate-many-to-many-association-with-extra-columns-in-join-table-example) I mapped the relation like this:
Student.java
#Entity
public class Student implements Serializable {
#Id
#Column
private String email;
#Column(nullable = false)
private String password;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String surname;
// Constructor, getters, setters, hashcode, equals
}
Course.java
#Entity
#Table(
uniqueConstraints = #UniqueConstraint(
columnNames {"name","year"})
)
public class Course implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private long id;
#Column
private String name;
#Column
private String year;
#OneToMany(mappedBy = "course")
private Set<Student_Course> students = new LinkedHashSet<>();
// Constructor, getters, setters, hashcode, equals
}
Student_Course.java
#Entity
public class Student_Course implements Serializable {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
#Column
private long id;
#ManyToOne
#JoinColumn(name = "student_email")
private Student student;
#ManyToOne
#JoinColumn(name = "course_id")
private Course course;
#Column(nullable = false,
columnDefinition = "int default 0")
private int score;
// Constructor, getters, setters, hashcode, equals
}
Now what I want to achieve is to find out, with an hql query, the names and surnames of students enrolled in a given course (I know the name and the year of the course).
I know this is probably easy, but I can't produce a working query in HQL.
Thank you in advance.
I believe that this query can do what you want:
SELECT sc.student.name, sc.student.surname
FROM Course c JOIN c.students sc
WHERE c.name = :name AND c.year = :year
Select name, surname from Student where email in
(Select student.email from Student_Course
where Student_course.course.name=:courseName and Student_course.course.year= :year)
and then set both courseName and year
Update
Select student.name,student.surname from Student_Course
where Student_course.course.name=:courseName and Student_course.course.year= :year
Related
In Student class, I am passing the id manually and all my entity-relationship are in LAZY mode.
But because I am passing the id, Spring DATA JPA (Hibernate) will treat as a merge request and make a SELECT call and will try to merge it. But I can not understand why it is trying to JOIN all relationships.
I was expecting select call for student table and not joining other tables. I can not understand why it is happening ?
select
student0_.uuid as uuid1_2_3_,
student0_.address_uuid as address_3_2_3_,
student0_.name as name2_2_3_,
address1_.uuid as uuid1_0_0_,
address1_.city_uuid as city_uui5_0_0_,
address1_.lat as lat2_0_0_,
address1_.lon as lon3_0_0_,
address1_.pincode as pincode4_0_0_,
city2_.uuid as uuid1_1_1_,
city2_.name as name2_1_1_,
subjects3_.student_uuid as student_4_3_5_,
subjects3_.uuid as uuid1_3_5_,
subjects3_.uuid as uuid1_3_2_,
subjects3_.course_start_date as course_s2_3_2_,
subjects3_.name as name3_3_2_,
subjects3_.student_uuid as student_4_3_2_
from
student student0_
inner join
address address1_
on student0_.address_uuid=address1_.uuid
left outer join
city city2_
on address1_.city_uuid=city2_.uuid
left outer join
subject subjects3_
on student0_.uuid=subjects3_.student_uuid
where
student0_.uuid=?
#Entity
public class Student {
#Id
private UUID uuid;
#Column(nullable = false)
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
private Address address;
#OneToMany(mappedBy = "student", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Subject> subjects;
}
#Entity
public class Subject {
#Id
#GeneratedValue
private UUID uuid;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private ZonedDateTime courseStartDate;
#ManyToOne(fetch = FetchType.EAGER)
private Student student;
}
#Entity
public class City {
#Id
#GeneratedValue
private UUID uuid;
#Column(nullable = false)
private String name;
}
#Entity
public class Address {
#Id
#GeneratedValue
private UUID uuid;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private City city;
#Column(nullable = false)
private String pincode;
#Column
private double lat;
#Column
private double lon;
}
Test Case :
#ExtendWith(SpringExtension.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = NONE)
public class StudentRepositoryIT {
#PersistenceContext
private EntityManager entityManager;
#Rollback(value = false)
#Test
public void should_fetch_student() {
// GIVEN
UUID uuid = UUID.randomUUID();
Student john = new Student().setName("John Wick").setUuid(uuid);
City newyork = new City().setName("Newyork City");
Address address = new Address().setCity(newyork)
.setLat(12.3)
.setLon(13.4)
.setPincode("010101");
john.setAddress(address);
Subject maths = new Subject().setName("maths").setCourseStartDate(ZonedDateTime.now()).setStudent(john);
Subject english = new Subject().setName("english").setCourseStartDate(ZonedDateTime.now()).setStudent(john);
Subject hindi = new Subject().setName("hindi").setCourseStartDate(ZonedDateTime.now()).setStudent(john);
Subject geology = new Subject().setName("geology").setCourseStartDate(ZonedDateTime.now()).setStudent(john);
Subject physics = new Subject().setName("physics").setCourseStartDate(ZonedDateTime.now()).setStudent(john);
Subject science = new Subject().setName("science").setCourseStartDate(ZonedDateTime.now()).setStudent(john);
john.setSubjects(List.of(maths, english, hindi, geology, physics, science));
studentRepository.saveAndFlush(john);
TestTransaction.end();
// WHEN
TestTransaction.start();
Student student = entityManager.find(Student.class, uuid);
System.out.println(student);
System.out.println(student.getAddress());
Address address1 = student.getAddress();
System.out.println(address1.getLat());
// THEN
check query console
}
}
Entity Relationship diagram
It does so because you are using CascadeType.ALL which means that Hibernate should cascade also for the merge operation. In order to cascade, Hibernate must load the data first and instead of loading the data by doing individual selects, Hibernate uses joins.
I'm not sure about City, but for the others Hibernate needs the join to determine if the respective field is null or not, which it needs to know before returning your root entity.
I have question about access to data.
I have that DB:
[country: id, country_name],
[city: id, country_id, city_name],
[address: id, shop_data_id, city_id, address_data],
[shop_data: id, data]
My relations country-city one to many, city-address one to many, address-shop_data one to one.
I'm looking for information that can I do that SQL query with ORM, or what is the best way do do it in ORM.
UPDATE shop_data
INNER JOIN country ON country.id=1
INNER JOIN city ON country.id=city.country_id
INNER JOIN address ON city.id= address.city_id
INNER JOIN shop_data ON address.shop_data_id=shop_data.id
SET shop_data.data="shop data string"
WHERE shop_data.id=address.shop_data_id
I know that in SQL I should start by shop_data, but by doing this I want to show that I want start in ORM by country entity.
I wrote entities in Hibernate with annotation
#Entity
#Table(name="country")
public class Country{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "country")
private String country;
#OneToMany(mappedBy = "country", fetch=FetchType.LAZY)
#JsonBackReference
private List<City> cities = new ArrayList<>();
// getters/setters ..
}
#Entity
#Table(name="city")
public class City{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "city")
private String city;
#OneToMany(mappedBy = "city", fetch=FetchType.LAZY)
private List<Address> adresses = new ArrayList<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="country_id")
#JsonIgnore
#JsonManagedReference
private Country country;
// getters/setters ..
}
#Entity
#Table(name="address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "address")
private String address;
#Column(name = "district")
private String district;
#Column(name = "post_code")
private String postCode;
#OneToOne
#JoinColumn(name="shop_data_id")
private ShopData shopData;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="city_id")
private City city;
// getters/setters ..
}
#Entity
#Table(name="shop_data")
public class shopData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "data")
private String data;
// getters/setters ..
}
I know that I can use getters starting from country that returns List<ObjectType> and from there get my object. Next run function update and update right row. But in this way are done some number of queries.
Is it possible to do by Java Hiberante ORM by one query? Or which way is the best to minimize query amount? By this method I also want to update next also address data.
You should definitely read a book about JPA/Hibernate to understand what JPQL or HQL supports. You can just do joins as you do them with SQL, except for DML statement, but you don't need that. In your case a simple subquery is enough to model what you need. A possible query could look like the following:
UPDATE ShopData s
SET s.data="shop data string"
WHERE EXISTS (
SELECT 1
FROM Country c
WHERE c.id = 1 AND c.city.address.shopData.id = s.id
)
I want to get data from multiple table.
public class Student{
private int id;
private String name;
private List<Course> course;
}
public class Course{
private int id;
private String name;
private int studentId;
}
I want to fetch data from student and course table using spring data jpa and map to student object.
How can I do that in efficient way?
#Data
#NoArgsConstructor
public class Student{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#OneToMany(mappedBy="studentId",cascade=CascadeType.ALL,fetch=FetchType.Eager)
private Set<Course> course;
}
You May Use Set Instead of List.
Always Use Mapped By in OneToMany Side, If you use it manyToOne side it will create an
extra table.
You can use Fetch Type eager or lazy. By default, it is lazy with You have
to use #transactional of Lazy.
#Data
#NoArgsConstructor
public class Course{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name="studentId")
private int studentId;
}
Hope this Answer Solve your query Happy Coding!.
Note that the starting point might be wrong. I assume that a student can choose multiple courses and a course can be chosen by multiple students. So it is actually a #ManyToMany relationship but not #ManyToOne or #OneToMany.
You will definitely need a joint table to map their primary keys from two tables into the joint table.
#Entity
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#EqualsAndHashCode.Exclude
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinTable(
name = "courses",
joinColumns = #JoinColumn(name = "student_id"),
inverseJoinColumns = #JoinColumn(name = "course_id"))
private Set<Course> courses;
}
#Entity
#Data
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#JsonIgnore
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "courses",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private Set<Student> students;
}
Note all the modifications I have here.
For the data persisted into database, Long is a better choice than int. Similarly, e.g., use Boolean instead of boolean.
Think the Student as the side managing the many-to-many relationship, and Course as the target side. On the target side, use #JsonIgnore and #ToString.Exclude annotations to avoid an infinite recursion, StackOverflow or OOM.
#JsonIgnore
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "courses",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Use Set instead of List if a student is not supposed to select the exact same course. It ensures that one can still select 2017 fall Maths and 2018 fall Maths, while one cannot select 2017 fall Maths twice.
I am looking to create a DAO which represents a join of two tables with Java Hibernate. Here is the SQL I'd like to represent (Postgres 9.6 incase that matters):
SELECT tableOneValue, tableTwoValue
FROM table_one, table_two
WHERE table_one_filter = 2 AND table_one_id = table_two_id;
These tables have a OneToOne relationship.
Table1.java
#Entity
#Data
#Table(name="table_one")
public class TableOneDao implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "table_one_id")
private int tableOneId;
#Column(name = "table_one_value")
private String tableOneValue;
#Column(name = "table_one_filter")
private int tableOneFilter;
}
Table2.java
#Entity
#Data
#Table(name="table_two")
public class TableTwoDao implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "table_twp_id")
private int tableTwpId;
#Column(name = "table_two_value")
private String tableTwoValue;
}
I'm very new to hibernate so maybe this isn't the right way to think with it. What I would love to do is define a SomeDao class where I can do: daoManager.findAll(SomeDao.class, Pair.of("tableOneFilter", 2));
This would return a List<SomeDao> where we get all the rows that satisfy tableOneFilter == 2.
You need to use the #OneToOne and #JoinColumn annotation.
Pay special attention to the userDetail attribute mapping.
For example, the user class:
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_ID")
private long id;
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "PASSWORD")
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="USR_DET_ID")
private UserDetail userDetail;
// Add Constructor, Setter and Getter methods
}
And this user details class:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_DET_ID")
private long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL")
private String email;
#Column(name = "DBO")
private LocalDate dob;
// Add Constructor, Setter and Getter methods
}
Check the full code here.
Here is a JPA query which will work with your existing entity structure with the latest version of hibernate.
SELECT t1.tableOneValue, t2.tableTwoValue
FROM TableOneDao AS t1 JOIN TableTwoDao AS t2 ON t1.table_one_id = t2.table_two_id
WHERE t1.table_one_filter = ?
You can write a JPQL statement which is much better. Here is the sample solution:
SELECT NEW com.test.package.dao(t1.valueOne, t2.valueTwo)
FROM table_one t1 JOIN table_two t2
WHERE t1.filter = 2 AND t1.id = t2.id;
Please refer to this link and jump to the section where it mentions Result Classes (Constructor Expressions). Hope it helps. Thanks.
I need to create a table EMPLOYEE_REMARK from a table EMPLOYEE.
And need to do it with Annotation Hibernate.
EMPLOYEE
EMP_ID, EMP_FNAME, EMP_LNAME
EMPLOYEE_REMARK
EMP_REMARK_ID, EMP_ID, REMARK
it will be a OnetoOne relationship i.e, for each EMP_ID there will be one REMARK. REMARK could be null.
please help me with the solution...
Can it be done by creating one class from employee and populate the EMPLOYEE_REMARK from it???
Basically here is the way of doing what you want.
Employee
#Entity
#Table(name = "EMPLOYEE")
public class Employee implements Serializable {
#Id
#Column(name = "EMP_ID")
private Long id;
#Column(name = "EMP_FNAME")
private String firstName;
#Column(name = "EMP_LNAME")
private String lastName;
#OneToOne(mappedBy = "employee", cascade = CascadeType.ALL,
orphanRemoval = true)
private EmployeeRemark employeeRemark;
public void setRemark(String remark) {
this.employeeRemark = new EmployeeRemark();
this.employeeRemark.setRemark(remark);
this.employeeRemark.setEmployee(this);
}
public String getRemark() {
return employeeRemark == null ? null : employeeRemark.getRemark();
}
//getters and setters
}
Employee Remark
#Entity
#Table(name = "EMPLOYEE_REMARK")
public class EmployeeRemark implements Serializable {
#Id
#Column(name = "EMP_REMARK_ID")
private Long id;
#OneToOne
#JoinColumn(name = "EMP_ID")
private Employee employee;
#Column(name = "REMARK")
private String remark;
//getters and setters
}
When saving employee, just call save on employee. EmployeeRemark will cascade to all operations and will be removed along with employee or if it become an orphan in other way.