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
Related
I'm trying to persist two entities Categorie and Question in a PostgreSQL db with one to many (categorie has many questions and a question is in one categorie).
After a lot of search and trying, adding CascadeType.PERSIST to the both entities is the only solution I found to the error but with CascadeType.PERSIST on the question side the category table we'll be full of duplicates. Is there any better solution because the categories should be unique in the table.
#Entity
#Table(name = "Category")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CID")
private int categoryId;
#Column(name = "CNAME")
private String categoryName;
#OneToMany(mappedBy = "category" , cascade = CascadeType.PERSIST)
public List<Question> questions;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Category)) {
return false;
}
Category category = (Category) o;
return categoryId == category.categoryId
&& getCategoryName().equals(category.getCategoryName())
&& questions.equals(category.questions);
}
#Override
public int hashCode() {
return Objects.hash(getCategoryName());
}
#Entity
#Table(name = "Question" )
public class Question {
#Id
#Column(name = "QID")
private int id;
#Column(name = "QText")
private String question;
#ManyToOne()
private Category category;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Question)) {
return false;
}
Question question = (Question) o;
return getId() == question.getId();
}
#Override
public int hashCode() {
return Objects.hash(getId());
}
public persist(){
EntityManager em = getEntityManager();
em.getTransaction().begin();
for (Category c : data.getCategories()) {
em.persist(c);
}
em.getTransaction().commit();
em.close();
}
The only thing I have done to fix your problem in my local machine, was to change the type of your entities id: int to Long
Working example based on your code :
Category entity
package io.ahenteti.java;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
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.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#Entity
#Table(name = "Category")
#AllArgsConstructor
#NoArgsConstructor
#NamedQueries({#NamedQuery(name = Category.FIND_ALL, query = "SELECT c FROM Category c")})
public class Category {
public static final String FIND_ALL = "Category.findAll";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CID")
private Long categoryId;
#Column(name = "CNAME")
private String categoryName;
#OneToMany(mappedBy = "category", cascade = CascadeType.PERSIST)
public List<Question> questions = new ArrayList<>();
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Category)) {
return false;
}
Category category = (Category) o;
return categoryId == category.categoryId && getCategoryName().equals(category.getCategoryName()) && questions
.equals(category.questions);
}
#Override
public int hashCode() {
return Objects.hash(getCategoryName());
}
}
Question
package io.ahenteti.java;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#Entity
#Table(name = "Question")
#AllArgsConstructor
#NoArgsConstructor
#NamedQueries({#NamedQuery(name = Question.FIND_ALL, query = "SELECT q FROM Question q")})
public class Question {
public static final String FIND_ALL = "Question.findAll";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "QID")
private Long id;
#Column(name = "QText")
private String question;
#ManyToOne()
private Category category;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Question)) {
return false;
}
Question question = (Question) o;
return getId() == question.getId();
}
#Override
public int hashCode() {
return Objects.hash(getId());
}
}
persistence.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
version="2.2">
<persistence-unit name="persistence-unit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>io.ahenteti.java.Question</class>
<class>io.ahenteti.java.Category</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:demo"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
Main
package io.ahenteti.java;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import java.util.List;
public class Main {
public static void main(String[] args) {
EntityManager em = Persistence.createEntityManagerFactory("persistence-unit").createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(createCategory("C1"));
em.persist(createCategory("C2"));
em.persist(createCategory("C3"));
transaction.commit();
System.out.println("number of categories in database: " + getAllCategories(em).size());
System.out.println("number of questions in database: " + getAllQuestions(em).size());
}
private static List<Question> getAllQuestions(EntityManager em) {
TypedQuery<Question> getAllQuestions = em.createNamedQuery(Question.FIND_ALL, Question.class);
return getAllQuestions.getResultList();
}
private static List<Category> getAllCategories(EntityManager em) {
TypedQuery<Category> getAllCategories = em.createNamedQuery(Category.FIND_ALL, Category.class);
return getAllCategories.getResultList();
}
private static Category createCategory(String category) {
Category res = new Category();
res.setCategoryName(category);
for (int i = 0; i < 5; i++) {
res.getQuestions().add(createQuestion(res, category + " - Q" + i));
}
return res;
}
private static Question createQuestion(Category category, String question) {
Question q1 = new Question();
q1.setQuestion(question);
q1.setCategory(category);
return q1;
}
}
output
number of categories in database: 3
number of questions in database: 15
Hope it helps :)
Without much details it is hard to infer the root of your problems; so if what you want to accomplish is to persist a collection of an entity with a one to many association with another entity, then you surely do it semi brute force programmatically without chasing the problem with your annotations configuration.
First remove all CascadeType annotations from your entities.
Then write your persist method as follows:
public void persist(Collection<Category> categories) {
EntityManager em = getEntityManager();
em.getTransaction().begin();
// Persist all categories first, without persisting the questions
categories.forEach(cat -> safelyPersistCategory(em, cat));
// Associated the not yet persisted questions with the persisted categories
categories.stream().forEach(cat -> cat.getQuestions().stream().forEach(question -> question.setCategory(cat)));
// Finally persist each question
categories.stream().flatMap(cat -> cat.getQuestions().stream()).forEach(question -> em.persist(question));
em.getTransaction().commit();
em.close();
}
// Safely persists Category without trying to create a relationship to
// potentially not persisted Questions
private void safelyPersistCategory(EntityManager em, Category category) {
List<Question> questions = category.getQuestions();
category.setQuestions(null);
em.persist(category);
category.setQuestions(questions);
}
As you can see in the comments in the code you manually:
Add the Category entities to the persistence context safely (without
associating them to potentially non-managed Questions).
Then associate these managed Category entities with their Question entities.
Finally add the Question entities to the persistence context.
From there you can commit the transaction.
The reason for this sequence is that the Question entity is the owner of the relationship, so it must relate to an already persisted/managed entity (Category), otherwise it will try to link to create an association to a non-persisted/non-managed entity which would cause problems (and which I suspect was what was happening with your original IllegalStateException).
Finally, by persisting the Category entities first, you can link the Question entities to already managed entities and not create them on the fly.
Complete code on GitHub
Hope this helps.
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
}
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.
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 am using OpenJPA with Eclipse to persist object. I created a simple one to one unidirectional application. But it is giving Foreign key null error.
Student Entity
#Entity
public class Student implements Serializable {
#Id
private int id;
private String name;
private static final long serialVersionUID = 1L;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "dept_id", unique = true, nullable = true, insertable = true, updatable = true, referencedColumnName = "id")
private Department department;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String toString() {
return "\n\nID:" + id + "\nName:" + name + "\n\n" + department;
}
}
Department Entity
#Entity
#Table(name = "department")
public class Department implements Serializable {
#Id
private int id;
private String name;
private static final long serialVersionUID = 1L;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String deptName) {
this.name = deptName;
}
public String toString() {
return "Department id: " + getId() + ", name: " + getName();
}
}
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="IBMJPADemo" transaction-type="RESOURCE_LOCAL">
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
<class>com.ibm.jpa.onetoone.model.Department</class>
<class>com.ibm.jpa.onetoone.model.Student</class>
<properties>
<property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost:3306/test" />
<property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver" />
<property name="openjpa.ConnectionUserName" value="root" />
<property name="openjpa.ConnectionPassword" value="root" />
<property name="openjpa.Log" value="DefaultLevel=WARN, Tool=INFO" />
<property name="openjpa.RuntimeUnenhancedClasses" value="supported"/>
<property name="openjpa.Log" value="DefaultLevel=WARN, Tool=INFO"/>
<!-- property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)" /-->
<!-- property name="openjpa.jdbc.MappingDefaults"
value="ForeignKeyDeleteAction=restrict, JoinForeignKeyDeleteAction=restrict" /-->
</properties>
</persistence-unit>
</persistence>
Client Program
public class OneToOneClient {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence
.createEntityManagerFactory("IBMJPADemo");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Student student = new Student();
student.setId(2537);
student.setName("K.Senthuran");
Department dept = new Department();
dept.setId(100);
dept.setName("IT");
student.setDepartment(dept);
em.persist(student);
em.flush();
tx.commit();
em.close();
}
}
Error
Exception in thread "main" org.apache.openjpa.persistence.PersistenceException: null keys not allowed
at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:1817)
at org.apache.openjpa.kernel.DelegatingBroker.flush(DelegatingBroker.java:1037)
at org.apache.openjpa.persistence.EntityManagerImpl.flush(EntityManagerImpl.java:652)
at com.ibm.jpa.onetoone.client.OneToOneClient.main(OneToOneClient.java:32)
Caused by: java.lang.NullPointerException: null keys not allowed
at org.apache.commons.collections.map.AbstractReferenceMap.put(AbstractReferenceMap.java:248)
at org.apache.openjpa.kernel.ManagedCache.assignObjectId(ManagedCache.java:189)
at org.apache.openjpa.kernel.BrokerImpl.assignObjectId(BrokerImpl.java:4949)
at org.apache.openjpa.kernel.BrokerImpl.setStateManager(BrokerImpl.java:4046)
at org.apache.openjpa.kernel.StateManagerImpl.assertObjectIdAssigned(StateManagerImpl.java:636)
at org.apache.openjpa.kernel.StateManagerImpl.afterFlush(StateManagerImpl.java:1084)
at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2162)
at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:2037)
at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:1808)
... 3 more
Please help to solve this issue.
Thanks & Regards,
K.Senthuran
Since you are using Uni-Directional Mapping, so persisting your Student will not persist your Department too. So, you need to make sure that, while persisting the Student, the Department entity reference used is already persisted in the database, else you will get exception.
So, just persist the department, before you persist the student. I think that will solve your issue.
If you want that, persisting your student also persist the department, then you would need to use bi-directional mapping. i.e. Use a reference of Student in Department, with #OneToOne mapping, specifying a mappedBy attribute.
I found the solution for this issue.
First of all we have to mention the primary key attribute with #Id and #Column annotation.
Then we have to add the following line in persistence.xml.
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)" />