Based on the following mapping (at the bottom of question) I need to know how to set an specific value in the "department_id" in the Employee class.
Employee
--------------------------------------------
id | firstname | lastname | department_id
--------------------------------------------
1 | David | Smith | 1
Department
-----------
id | name
-----------
1 | Dep A
2 | Dep B
3 | Dep C
saveEmployee method (EmployeeController class):
#RequestMapping(value = "/saveEmployee", method = RequestMethod.POST)
public String saveEmployee(#ModelAttribute("employee") Employee employee){
/* I need to set the department "id" (foreign key) into the Employee
table directly in this method. */
int id = 1; // 2 or 3...
/* The "department_id" in the Employee class should
to receive the "id" value. */
employee.setDepartment(id); // It does not work.
employeeService.saveEmployee(employee);
return "redirect:/employees";
}
Employee class:
#Entity
public class Employee{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String firstname;
private String lastname;
#ManyToOne
#JoinColumn(name = "department_id")
private Department department;
// Getters and Setters
}
Department class:
#Entity
public class Department{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
// Getters and Setters
}
Look carefully at your Employee class:
#Entity
public class Employee{
...
#ManyToOne
#JoinColumn(name = "department_id")
private Department department;
/* THIS IS NOT AN INTEGER DATA TYPE, IT'S A DEPARTMENT DATA TYPE.
SO THE SETTER FOR THIS WILL LOOK SOMEWHAT LIKE THIS:*/
//Setter
public void setDepartment(Department department) {
this.department = department
}
...
// Getters and Setters
}
In order to set a department create an instance your Department and then send it through setter:
#RequestMapping(value = "/saveEmployee", method = RequestMethod.POST)
public String saveEmployee(#ModelAttribute("employee") Employee employee){
int id = 1; // 2 or 3...
Department temporaryDepartment = new Department();
temporaryDepartment.setId(id);
employee.setDepartment(temporaryDepartment);
employeeService.saveEmployee(employee);
return "redirect:/employees";
}
Actually your setDepartment receives a Department instance. So you have to do this:
int id = 1;
Department department = new Department(); //Or you can use Autowired
department.setId(id); // Set Id Department
employee.setDepartment(department); // A Department instance
employeeService.saveEmployee(employee);
return "redirect:/employees";
Actually, You have set Generation strategy as shown below
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
on Department class which means you would want hibernate to generate the ids for you so that you don't have to worry about setting the department id. You just need to set the department name.
Below is the working code, JPAEmployeeTest.java:
package com.chatar.hibernate.receipes.example;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import com.chatar.hibernate.receipes.example.domain.annotations.Department;
import com.chatar.hibernate.receipes.example.domain.annotations.Employee;
public class JPAEmployeeTest {
public static void main(String[] args) {
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( "employee" );
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
Employee employee = new Employee();
Department department = new Department();
department.setName("Engineering");
employee.setFirstName("Johny");
employee.setLastName("Walker");
employee.setDepartment(department);
entityManager.persist(employee);
entityManager.getTransaction().commit();
entityManager.close();
}
}
And my domain object, Employee.java
package com.chatar.hibernate.receipes.example.domain.annotations;
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
#Entity
public class Employee implements Serializable {
private static final long serialVersionUID = -5641563180459243167L;
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "department_id" )
private Department department;
public long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
And Department.java
package com.chatar.hibernate.receipes.example.domain.annotations;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class Department implements Serializable {
private static final long serialVersionUID = -598469568850009702L;
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
My persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="employee" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.chatar.hibernate.receipes.example.domain.annotations.Employee</class>
<class>com.chatar.hibernate.receipes.example.domain.annotations.Department</class>
<properties>
<property name="javax.persistence.jdbc.driver"
value = "org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="javax.persistence.jdbc.url" value = "jdbc:derby://localhost:1527/BookShopDB" />
<property name="javax.persistence.jdbc.user" value="book" />
<property name="javax.persistence.jdbc.password" value = "book" />
<property name="hibernate.dialect" value = "org.hibernate.dialect.DerbyDialect" />
</properties>
</persistence-unit>
</persistence>
My Database output:
ij> select * from employee;
ID |FIRST_NAME |LAST_NAME |DEPARTMENT&
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
163840 |Johny |Walker |1
1 row selected
ij> select * from department;
ID |NAME
----------------------------------------------------------------------------------------------------------------
1 |Engineering
1 row selected
ij>
Note - I'm using GenerationType.TABLE as somehow AUTO was not working for my database i.e. Debry. Also, I have set CascadeType.ALL so that when I save Employee entity, Hibernate saves all references entities e.g. Department in my case.
Related
I have two entities with bidirectional OneToMany relationship . eg
Employee.java
package com.jpademo.entities;
import java.io.Serializable;
import javax.persistence.*;
/**
* Entity implementation class for Entity: Employee
*
*/
#Entity
#Table(name = "Employee")
#SequenceGenerator(name = "Emp_Id_Gen", sequenceName = "EMP_ID_SEQ_GEN", allocationSize = 1)
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "Emp_Id_Gen")
private Integer employeeId;
private String Name;
#ManyToOne(cascade = { CascadeType.ALL })
private Department department;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
Department.java
package com.jpademo.entities;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
/**
* Entity implementation class for Entity: Department
*
*/
#Entity
#Table(name = "Department")
#SequenceGenerator(name = "Dept_Id_Gen", sequenceName = "DEPT_ID_SEQ_GEN", allocationSize = 1)
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "Dept_Id_Gen")
private Integer deptId;
private String deptName;
#OneToMany(mappedBy = "department", cascade = { CascadeType.MERGE,
CascadeType.PERSIST })
private List<Employee> employees;
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
EmployeeDAO.java
package com.jpademo.dao;
import java.util.Arrays;
import java.util.List;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import com.jpademo.entities.Department;
import com.jpademo.entities.Employee;
#Stateless(name = "employeeDAO")
#LocalBean
public class EmployeeDAO {
#PersistenceContext(unitName = "JPA2Pratice")
private EntityManager em;
public void saveEmployee() {
Employee emp = new Employee();
emp.setName("Mukesh Saini");
Department dept = new Department();
dept.setDeptName("Physics");
emp.setDepartment(dept);
em.persist(emp);
}
public void addEmployeeToDepartment() {
TypedQuery<Department> departmentQuery = em
.createQuery(
"SELECT dept FROM Department dept WHERE dept.deptId = :departmentId",
Department.class);
departmentQuery.setParameter("departmentId", 1);
Department dept = departmentQuery.getSingleResult();
// Employee already in department
List<Employee> currentEmpinDept = dept.getEmployees();
// Employee that need to be add in department
TypedQuery<Employee> employeeQuery = em.createQuery(
"SELECT emp FROM Employee emp WHERE emp.employeeId IN :empIds",
Employee.class);
employeeQuery.setParameter("empIds", Arrays.asList(2, 3));
List<Employee> employees = employeeQuery.getResultList();
currentEmpinDept.addAll(employees);
dept.setEmployees(currentEmpinDept);
em.merge(dept); // this does not update employee department
}
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="JPA2Pratice">
<jta-data-source>jdbc/MySQLDataSource</jta-data-source>
<properties>
<property name="eclipselink.ddl-generation" value="create-tables" />
<property name="eclipselink.logging.level.sql" value="FINE" />
<property name="eclipselink.logging.parameters" value="true" />
</properties>
</persistence-unit>
</persistence>
In method addEmployeeToDepartment in EmployeeDAO em.merge(dept) is not updating the Employee Department, what am I doing wrong here. Or Does cascade works from Parent to child only ?
Thanks.
I don't think you need to worry about a merge here, you just need to update both sides of the relationship. The employee objects returned from your query are already managed/attached.
But according to here and here it's not enough to simply set the new list of Employee objects on the parent Department object. You have to update the reference to the Department on each child Employee as well.
So you could just add:
// ...
List<Employee> employees = employeeQuery.getResultList();
// NEW: Update owning side
for (Employee e : employees) {
e.setDepartment(dept);
}
currentEmpinDept.addAll(employees);
dept.setEmployees(currentEmpinDept);
//em.merge(dept); - DON'T THINK THIS IS NEEDED
}
I'm using the latest version of Hibernate Tools from JBoss Studio / Eclipse to generate Java POJOs from my database schema.
When there is a get method that gets further objects I want to have these returned in a specific order. Obviously I can just edit the class file however these changes will then be lost if these files are re-generated if there are any further schema changes.
Research has showed it should be possible to add these options using hibernate reverse engineering XML config or possibly a hibernate hbm xml file.
I can't seem to get these to work and am hoping somebody could provide an example config that would work.
I have created a very simple DB to illustrate the problem.
SQL Schema:
--
-- Table structure for table `pupil`
--
DROP TABLE IF EXISTS `pupil`;
/*!40101 SET #saved_cs_client = ##character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `pupil` (
`ID` int(11) NOT NULL,
`school_id` int(11) NOT NULL,
`forename` varchar(100) NOT NULL,
`surname` varchar(100) NOT NULL,
`gender` enum('M','F') NOT NULL,
PRIMARY KEY (`ID`),
KEY `fk_pupils_schools_idx` (`school_id`),
CONSTRAINT `fk_pupils_schools` FOREIGN KEY (`school_id`) REFERENCES `school` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION)
ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = #saved_cs_client */;
--
-- Table structure for table `school`
--
DROP TABLE IF EXISTS `school`;
/*!40101 SET #saved_cs_client = ##character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `school` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = #saved_cs_client */;
This results in two POJOs.
Pupil.java
package hibernateExample.db;
// Generated 20-Sep-2014 15:34:31 by Hibernate Tools 4.0.0
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
/**
* Pupil generated by hbm2java
*/
#Entity
#Table(name = "pupil", catalog = "test")
public class Pupil implements java.io.Serializable {
private int id;
private School school;
private String forename;
private String surname;
private String gender;
public Pupil() {
}
public Pupil(int id, School school, String forename, String surname,
String gender) {
this.id = id;
this.school = school;
this.forename = forename;
this.surname = surname;
this.gender = gender;
}
#Id
#Column(name = "ID", unique = true, nullable = false)
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "school_id", nullable = false)
public School getSchool() {
return this.school;
}
public void setSchool(School school) {
this.school = school;
}
#Column(name = "forename", nullable = false, length = 100)
public String getForename() {
return this.forename;
}
public void setForename(String forename) {
this.forename = forename;
}
#Column(name = "surname", nullable = false, length = 100)
public String getSurname() {
return this.surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
#Column(name = "gender", nullable = false, length = 2)
public String getGender() {
return this.gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
School.java. The getPupils method needs to have an OrderBy annotation added on POJO creation. This probably needs to be a List rather than a Set too?
package hibernateExample.db;
// Generated 20-Sep-2014 15:34:31 by Hibernate Tools 4.0.0
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
/**
* School generated by hbm2java
*/
#Entity
#Table(name = "school", catalog = "test")
public class School implements java.io.Serializable {
private Integer id;
private String name;
private Set<Pupil> pupils = new HashSet<Pupil>(0);
public School() {
}
public School(String name, Set<Pupil> pupils) {
this.name = name;
this.pupils = pupils;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "name", length = 200)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "school")
public Set<Pupil> getPupils() {
return this.pupils;
}
public void setPupils(Set<Pupil> pupils) {
this.pupils = pupils;
}
}
Thanks
Example to add the an annotation via reverse engineering xml:
You can see how easy it is to tweak the scope class mete attibute in below example.
<table name="organization">
<meta attribute="scope-class">#Proxy(lazy=false) public</meta>
<meta attribute="extra-import">org.hibernate.annotations.Proxy</meta>
....rest of config.....
</table>
You could on a field level using scope-field attribute.
Full meta attributes at this link
Please help me with this error. Follow my code below:
This my class/entity Person:
package entities;
import java.util.Calendar;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
#Entity
public class Person {
#Id
#GeneratedValue
private long id;
#Column
private String name;
#Temporal(TemporalType.DATE)
private Calendar dateBirth;
#Column
private String email;
// MappedBy: Name of the instance that will indicate to whom One belongs, that is, an experience.
// It should say which person is associated
// TargetEntity: Informs the entity we are associating
// FetchType.Lazy: Was chosen for performance
// Cascade: ALL to allow changes in all relationships.
#OneToMany(mappedBy = "person", targetEntity = Experience.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Experience> experiences;
// Getters and setters
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Calendar getDateBirth() {
return dateBirth;
}
public void setDateBirth(Calendar dateBirth) {
this.dateBirth = dateBirth;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Experience> getExperiences() {
return experiences;
}
public void setExperiences(List<Experience> experiences) {
this.experiences = experiences;
}
}
This is my class/entity Experience:
package entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
#Entity
public class Experience {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name="idPerson", referencedColumnName="id")
private Person person;
#Column
private String company;
#Column
private String activities;
// Getters and setters
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getActivities() {
return activities;
}
public void setActivities(String activities) {
this.activities = activities;
}
}
My persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="professionalmanagementDB">
<!-- JPA implementation -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- Entities -->
<class>entities.Person</class>
<class>entities.Experience</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/professionalmanagementDB" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="" />
</properties>
</persistence-unit>
</persistence>
So, I'm trying to use this code to make a query:
EntityManagerFactory factory = Persistence.createEntityManagerFactory("professionalmanagementDB");
EntityManager manager = factory.createEntityManager();
String jpql = "select p ";
jpql += "from Person";
Query query = manager.createQuery(jpql);
List<Person> list = query.getResultList();
But always happen this error:
java.lang.NoSuchMethodError: javax.persistence.JoinColumn.foreignKey()Ljavax/persistence/ForeignKey;
org.hibernate.cfg.AnnotationBinder.bindManyToOne(AnnotationBinder.java:2881)
org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1795)
org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:963)
org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:796)
org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3790)
org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3744)
org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1410)
org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844)
org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:843)
org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:397)
org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:842)
org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:73)
org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:54)
javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:63)
javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:47)
dao.PersonDAO.readAll(PersonDAO.java:65)
manager.PersonManager.getPersons(PersonManager.java:17)
org.apache.jsp.HomeScreen_jsp._jspService(HomeScreen_jsp.java:87)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
My maven dependencies is as follows:
javaee-api:7.0
hibernate-jpa-2.0-api:1.0.1.Final
hibernate-entitymanager:4.3.4.Final
mysql-connector-java:5.1.29
I don't know why Hibernate is wanting that foreignKey attribute on the #JoinColumn annotation, but it was added in JPA 2.1. Use the JPA 2.1 API dependency instead of the 2.0.
The many to one mapping with #JoinColumn(name="idPerson", referencedColumnName="id") means idPerson in the Experience class is related to the id of the Person table.
#ManyToOne
#JoinColumn(name="idPerson", referencedColumnName="id")
private Person person;
Here there is no idPerson I believe that's the problem. You must specify a foreign key for the mapping, between Person and Experiance like this
Person
id col col1
-----------
1 s s
Experience
id personId col col1
---------------------
1 1 s d
2 1 f d
Now you can specify multiple Experience for a person by
#ManyToOne // more than one experiance for a person
#JoinColumn // for condition
(name="personId", referencedColumnName="id") // for condition Experience.personId==person.id
I have a question regarding circular relationships in JPA, and here in particular with Eclipselink JPA implementation. Sorry if the question is a bit long, but I try to be as precise as possible.
Let's take the simple example of Department and Employee where a Department has a one-to-many "employees" relationship (and hence the reverse many-to-one "department" relationship from Employee to Department). Now let's add a one-to-one relationship "manager" from Department towards Employee (one of the Employees of the Department is the manager of that same Department). That introduces a circular relationship between the two entities and both tables will have a foreign key referencing the other table.
I would like to be able to do all the inserts without getting a Foreign key constraint violation. So, my idea was to first insert all employees (without setting the department relationship), then insert the Department (with its manager being set), and eventually update all the employees to set their Department.
I know that I could use flush() to force the order of insert execution but I was told that it should be avoided and hence wondering if there is a way to tell JPA/Eclipselink that Department should be inserted first, then Employee.
In Eclipselink, I did try to add Employee as a constraint dependency of the classdescriptor of the Department class but it still gives error randomly.
Here is a code example illustrating this (the issue occurs randomly):
Department class:
package my.jpa.test;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Persistence;
/**
* Entity implementation class for Entity: Department
*
*/
#Entity
public class Department implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(fetch = FetchType.EAGER)
private List<Employee> employees;
#OneToOne
#JoinColumn(name = "manager", nullable = false)
private Employee manager;
private static final long serialVersionUID = 1L;
public Department() {
super();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-jpa");
EntityManager em = emf.createEntityManager();
Department d = new Department();
Employee manager = new Employee();
manager.setLastName("Doe");
d.setManager(manager);
Employee e1 = new Employee();
e1.setLastName("Doe");
Employee e2 = new Employee();
e2.setLastName("Smith");
em.getTransaction().begin();
em.persist(d);
manager.setDepartment(d);
e1.setDepartment(d);
e2.setDepartment(d);
em.persist(e1);
em.persist(e2);
em.persist(manager);
em.persist(d);
manager.setDepartment(d);
e1.setDepartment(d);
e2.setDepartment(d);
em.merge(manager);
em.merge(e1);
em.merge(e2);
em.getTransaction().commit();
em.clear();
Department fetchedDepartment = em.find(Department.class, d.getId());
System.err.println(fetchedDepartment.getManager().getLastName());
System.err.println(new ArrayList<Employee>(fetchedDepartment.getEmployees()));
}
public Employee getManager() {
return manager;
}
public void setManager(Employee manager) {
this.manager = manager;
}
}
Employee class:
package my.jpa.test;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
/**
* Entity implementation class for Entity: Employee
*
*/
#Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String lastName;
#ManyToOne
private Department department;
#OneToOne(mappedBy = "manager")
private Department managedDepartment;
public Employee() {
super();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public Department getManagedDepartment() {
return managedDepartment;
}
public void setManagedDepartment(Department managedDepartment) {
this.managedDepartment = managedDepartment;
}
#Override
public String toString() {
return "Employee " + getLastName();
}
}
persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="test-jpa">
<class>my.jpa.test.Department</class>
<class>my.jpa.test.Employee</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />
<property name="javax.persistence.jdbc.user" value="sa" />
<property name="javax.persistence.jdbc.password" value="" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
<property name="eclipselink.ddl-generation.output-mode" value="database" />
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.logging.parameters" value="true"/>
</properties>
</persistence-unit>
</persistence>
Maven dependencies:
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.172</version>
</dependency>
</dependencies>
IMHO with this model you don't really have the choice.
Insert department (without manager)
insert employee (with departments)
flush
update department manager.
Deleting will probably be a mess too
Otherwise you could create an association table between department and employee to hold an isManager attribute.
Or put this last in employee table (not very normalized but well...)
From a general point of view it seems that circular reference are not advised in a relational model :
In SQL, is it OK for two tables to refer to each other?
I think that if you configure the departament column in Employee to allow null and set cascades correctly it can solve the problem. And please, do not use flush
I have an Entity called Person which has name property as String and person petnames has collection. When mapping to Hibernate I am getting exception. How can I resolve this problem?
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.myapp.struts;
import java.io.Serializable;
import java.util.Set;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
/**
*
* #author hyva
*/
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setPetname(Set<String> petname) {
this.petname = petname;
}
public void setId(Long id) {
this.id = id;
}
private String name;
private Set<String> petname;
#ElementCollection
#CollectionTable(name = "petname", joinColumns =
#JoinColumn(name = "PERSON_ID"))
public Set<String> getPetname() {
return petname;
}
}
*Exception:*
Initial SessionFactory creation failed.org.hibernate.MappingException: Could not determine type for: java.util.Set, for columns: [org.hibernate.mapping.Column(petname)]
Exception in thread "main" java.lang.ExceptionInInitializerError
at com.myapp.struts.HibernateUtil.<clinit>(HibernateUtil.java:28)
at com.myapp.struts.HibernateComplexValue.main(HibernateComplexValue.java:19)
Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Set, for columns: [org.hibernate.mapping.Column(petname)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:266)
aJava Result: 1
Move #CollectionTable over to be an annotation on getPetname as shown below:
private Set<String> petname;
#ElementCollection
#CollectionTable(name = "pets", joinColumns =#JoinColumn(name = "PERSON_ID"))
public Set<String> getPetname(){
return petname;
}
Your annotation #CollectionTable is located after your property petName! Is is a copy/paste error?
You have not mapped your petname column. And your default property name is the same as your table name (petname)...
What is supposed to be the name of the column holding the pet names in this table?
Try adding #Column(name="YOUR_COLUMN_NAME") to force the mapping to this column.