In hibernate, when I use the joined strategy. does hibernate support polymorphism?
for example:
#Entity
#Table(name = "PERSON")
#Inheritance(strategy=InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
#Column(name = "PERSON_ID")
private Long personId;
#Column(name = "FIRSTNAME")
private String Fullname;
public Person() {
}
public Person(String fullname) {
this.Fullname= fullname
}
}
and the derived class:
#Entity
#Table(name="EMPLOYEE")
#PrimaryKeyJoinColumn(name="PERSON_ID")
public class Employee extends Person {
#Column(name="department_name")
private String departmentName;
public Employee() {
}
public Employee(String fullname, String departmentName,) {
super(fullname);
this.departmentName = departmentName;
}
}
also all the fields include the getter and setters.
so in my main, when I'll do this:
session.beginTransaction();
person e = new Employee();
e.setFullname("james");
e.setdepartmentName("R&D");
session.getTransaction().commit();
I know for a fact that if e was of Employee type, hibernate would have created a row for both Employee and Person tables.
but for this example will hibernate generate queries for person and employee?
in other words, will hibernate support the polymorphic behavior?
When I understand you question correctly than you want to know if hibernate does in that case automtically return Employees when you query for all Persons. Additionally if it inserts an Employee when the object is declared as Person p = new Employee().
Short answer to both yes. More detailed.
The insert operation is based on the actual type of the object and not on exactly the type written in your sourcecode.
Related to querying of Persons. Hibernate does left outer join all subtypes so you will get also Employess back when you do the following:
Query query = session.createQuery("From Person ");
List<Person> persons = query.getResultList();
Related
I have
#Entity
public class Person{
...
#OneToMany
#JoinColumn(name = "person_id")
private List<Email> emailList = new ArrayList<>();
...
}
#Entity
public class Email{
...
private String emailAddress;
...
}
I need to get Person by emailAddress. I saw this answer: dont like.
But i wonder - can i do this with only spring data, i mean - without query.
This should work:
Person findPersonByEmailList_EmailAddress(String email);
And this as well:
#Query("select p from Person p join p.emailList l where l.emailAddress = ?1")
Person getPersonByEmail(String email);
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);
#Entity
#Table(name = "PERSON")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class Person {
#Id
#GeneratedValue
#Column(name = "PERSON_ID")
private Long personId;
#Column(name = "FIRSTNAME")
private String firstname;
#Column(name = "LASTNAME")
private String lastname;
// Constructors and Getter/Setter methods,
}
Employee class extends Person
#Entity
public class Employee extends Person {
#Transient
private Date joiningDate;
// Copy Constructors and Getter/Setter methods,
}
The Employee class has only a transient object So I am not sure about using of #DiscriminatorColumn and #DiscriminatorValue, When i tried without using Discriminator that throws error
session.save(employee);
I am trying to save Employee object that throws Unknown column 'DTYPE' in 'field list' error
I have encountered the same issue using EclipseLink and I have tried to use #discriminator annotation ... without success
because in this solution, a specific column must be added in 2 tables.
I don't have the possibility to add columns in tables, so I have found following solution :
#Entity
#Table(name = "PERSON")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person {
I have just changed inheritance type from SINGLE_TABLE to TABLE_PER_CLASS
I have found useful explanation on Thoughts on Java
Just for information, my 2 tables are in reality 1 table and a view on same table with 2 or 3 joins.
You should use #DiscriminatorColumn and #DiscriminatorValue. You can find a good example here.
D_TYPE is basically name of your child class.
Let's suppose you have:
Abstract Employee(id, name);
class FullTimeEmployee extends Employee(Double salary)
with Inheritence.SINGLE_TABLE strategy.
A table employee will be created with these columns
Employee (D_TYPE, id, name, salary)
where D_TYPE will be name of your child classes.
employeeRepository.insert(Employee emp);
where you can pass emp as new FullTimeEmployee(id, name, salary);
After insert it will automatically populate d_type as Full_Time_Employee
If you want to change the name of D_TYPE column use
#DiscriminatorColumn(name="customizedNameofD_TYPE")
Just FYI: mostly used strategy is InheritanceType.JOINED
Persist child entity rather than persisting parent entity.
#MappedSuperclass
public class Person {
And
#Entity
#Table(name = "PERSON")
public class Employee extends Person {
This should resolve your issue.
Really confused by how one to many works in JPA, all the documents that I read, uses both one to many and many to one in their example, and I don't know if they are necessary or not, and it doesn't work when I tried it.
My question is, suppose I have two tables, and I want to populate the College object using findCollegeData() method, so that all the student in this college are in a list when I initialize the object.
Below is my approach, I am able to store all the students in the college list using storeCollegeData() method, but I am not able to retrieve the college object fully, the student list is always empty, even though the data is in the database, and it works if I try to search for student using college name directly.
public static EntityManager entityManager = something;
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public College {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int cId;
private String collegeName;
private int numOfStudent;
#OneToMany(mappedBy="collegeName", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Student> studentList = new ArrayList<>();
}
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public Student {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int sId;
private String name;
private String collegeName;
private String city;
}
// college.getStudentList is always empty and I don't know why
public findCollegeData(String collegeName) {
College college = entityManager.find(College.class, collegeName);
}
// Student data in the studentList are inserted into student table
public storeCollegeData(College college) {
entityManager.persist(college);
}
// This method works
public findStudent(String collegeName) {
CriteriaBuilder cb = provider.get().getCriteriaBuilder();
CriteriaQuery<Student> query = cb.createQuery(Student.class);
Root<Student> student = query.from(Student.class);
query.where(
cb.and(
cb.equal(student.get("collegeName"), collegeName)
)
);
JobStatisticDB Student = provider.get().createQuery(query).getSingleResult();
}
Am i missing something??? Is join more appropriate than map here??? I dont know wat to do man
EDITED:
Got it to work by changing both of the collegeName as the primary key of table by adding #Id annotation, however though, how can I add an sId and cId to the table, so they can have duplicate college name???? Right now, I can't have duplicate college with the same name, and student that that goes to the same college!
Final Edited:
Changed database design to use foreign key see solution below
The accepted answer is incorrect: you define relationships between entities. The mappings should be as below for a bi-directional #OneToMany
College:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public College {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int cId;
private String collegeName;
private int numOfStudent;
#OneToMany(mappedBy="college", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Student> studentList = new ArrayList<>();
}
Student:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public Student {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int sId;
private String name;
private String city;
//student table has a FK column college_id
#ManyToOne
#JoinColumn(name = "college_id")
private College college;
}
EntityManager find() takes the PK as an argument:
public findCollege(int collegeId) {
College college = entityManager.find(College.class, collegeId);
college.getStudents(); //will be populated
}
public findStudent(int studentId) {
Student student = entityManager.find(Student.class, studentId);
student.getCollege(); //will be populated
student.getCollege().getStudents(); //will be populated
}
If you want to find a college by name create a JPQL or Criteria query:
The field you reference in mappedBy must contain a value that equates to College's id field. Change it to collegeName instead of city, and it should work.
I created two entities for the tables HOBBY and STUDENT, but I have difficulties to retrieve the information I need from the Join Table.
This is the schema:
STUDENT STUDENT_HOBBY HOBBY
-------- ----------- --------
id ------------------ student_id
name hobby_id ---------- id
lastname hobby_name
--------- ---------- --------
where
Student to student_hobby = one to many
Hobby to student_hobby = one to many
(that means a student may have many hobbies and a hobby can belong to more than one student)
This is the Student entity class:
#Entity
#Table(name="STUDENT")
public class Student {
#OneToMany
#JoinTable(name="student_hobbies", joinColumns=#JoinColumn(name="student_id"),
inverseJoinColumns=#JoinColumn(name="hobby_id"))
private Collection<Hobby> hobbies;
#Id
#Column(name="ID")
#GeneratedValue
private Integer id;
#Column(name="NAME")
private String name;
#Column(name="LASTNAME")
private String lastName;
// Getters and setters here
}
This is the Hobby entity class:
#Entity
#Table(name="HOBBY")
public class Hobby {
#OneToMany
#JoinTable(name="student_hobbies", joinColumns=#JoinColumn(name="hobby_id"),
inverseJoinColumns=#JoinColumn(name="student_id"))
private Collection<Student> students;
#Id
#Column(name="ID")
#GeneratedValue
private Integer id;
#Column(name="HOBBY_NAME")
private String hobby_name;
// Getters and setters here
}
Now I would like to implement the following DAO, but I don't know exactly how to do:
public interface MyDAO {
public void addHobbyForStudent(int student_id, int hobby_id);
public void RemoveHobbyForStudent(int student_id, int hobby_id);
}
Should I create an other entity class for the Join Table? Could someone give me some indication on the way to follow?
You can create an entity class for the join table, but you don't need to and you probably shouldn't. This will just create more code to maintain. You should create an entity for the join table if there is data in the join table you need to query. EG: if there was a start_time or something in the join table and you wanted to be able to see the start_time.
You should be using the #ManyToMany annotation when you're avoiding a join table entity. The documentation I linked to gives some good examples of how to do this:
Example 1:
// In Customer class:
#ManyToMany
#JoinTable(name="CUST_PHONES")
public Set<PhoneNumber> getPhones() { return phones; }
// In PhoneNumber class:
#ManyToMany(mappedBy="phones")
public Set<Customer> getCustomers() { return customers; }
Example 2:
// In Customer class:
#ManyToMany(targetEntity=com.example.PhoneNumber.class)
public Set getPhones() { return phones; }
// In PhoneNumber class:
#ManyToMany(targetEntity=com.example.Customer.class, mappedBy="phones")
public Set getCustomers() { return customers; }
Example 3:
// In Customer class:
#ManyToMany
#JoinTable(name="CUST_PHONE",
joinColumns=
#JoinColumn(name="CUST_ID", referencedColumnName="ID"),
inverseJoinColumns=
#JoinColumn(name="PHONE_ID", referencedColumnName="ID")
)
public Set<PhoneNumber> getPhones() { return phones; }
// In PhoneNumberClass:
#ManyToMany(mappedBy="phones")
public Set<Customer> getCustomers() { return customers; }