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.
Related
I am studying at the university in my third year. We pass Spring and Hibernate. I am developing an application using Java and Hibernate.
It is not possible to check for the presence of a category, and create it if it is not found
Hello everyone, I am studying at the university in my third year. We pass Spring and Hibernate. I am developing an application using Java and Hibernate.
It is not possible to check for the presence of a category, and create it if it is not found
class Category
package oleg94.catalog.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.Collection;
import java.util.List;
#Entity
#Table(name = "category")
#Getter
#Setter
public class Category {
#Id
#SequenceGenerator(name = "IdCategory", sequenceName = "category_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "IdCategory")
private Long id;
private String name;
#OneToMany(mappedBy = "category")
private List<Product> products;
#OneToMany(mappedBy = "category")
private List<Characteristic> characteristics;
}
class CreateCategory
package oleg94.catalog;
import oleg94.catalog.entity.Category;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import java.util.List;
import java.util.Scanner;
public class CreateCategory {
public static void main(String[] args) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("main");
EntityManager manager = factory.createEntityManager();
String temp;
System.out.println("Введите название категории:");
Scanner scanner = new Scanner(System.in);
try {
temp = scanner.nextLine();
TypedQuery<Category> categoryTypedQuery = manager.createQuery(
"select c from Category c where c.name like ?1", Category.class);
categoryTypedQuery.setParameter(1, "%" + temp + "%");
List<Category> categories = categoryTypedQuery.getResultList();
if (!categories.contains(temp)){
Category category = new Category();
category.setName(temp);
manager.persist(category);
} else {
System.out.println("Данная категория уже есть");
}
manager.getTransaction().commit();
} catch (Exception e) {
manager.getTransaction().rollback();
e.printStackTrace();
}
}
}
I'm working in a web app with Hibernate 5 and SpringMVC, when I worked just with simple tables (without relationships between any tables) everything work okay, update, save, delete..., but if I try to add some relationships between tables, and when I mapped, I got this error :
#OneToOne or #ManyToOne on 'univ_orientation.dz.Entity.cursusGlobale.Student_idStudent' references an unknown entity: int
I had searched to solve this issue, I found out that its a configuration problem, Hibernate doesn't recognize class cursusGlobale as an entity.
My question is why Hibernate doesn't recognize class cursusGlobale as an entity when I work with relationships, but it does if I work just with simple tables without relationships between tables?
package univ_orientation.dz.Entity;
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.OneToOne;
import javax.persistence.Table;
#Entity
#Table(name="cursusGlobale")
public class cursusGlobale {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="idcursusGlobale")
private int idcursusGlobale;
#Column(name="nbrRedoubler")
private int nbrRedoubler;
#Column(name="nbrDette")
private int nbrDette;
#Column(name="idStudent")
private int idStudent;
#OneToOne( cascade= CascadeType.ALL)
#JoinColumn(name="Student_idStudent")
private int Student_idStudent;
public cursusGlobale() {
}
public cursusGlobale(int nbrRedoubler, int nbrDette, int
student_idStudent) {
super();
this.nbrRedoubler = nbrRedoubler;
this.nbrDette = nbrDette;
idStudent = student_idStudent;
}
public int getIdcursusGlobale() {
return idcursusGlobale;
}
public void setIdcursusGlobale(int idcursusGlobale) {
this.idcursusGlobale = idcursusGlobale;
}
public int getNbrRedoubler() {
return nbrRedoubler;
}
public void setNbrRedoubler(int nbrRedoubler) {
this.nbrRedoubler = nbrRedoubler;
}
public int getNbrDette() {
return nbrDette;
}
public void setNbrDette(int nbrDette) {
this.nbrDette = nbrDette;
}
public int getIdStudent() {
return idStudent;
}
public void setIdStudent(int idStudent) {
this.idStudent = idStudent;
}
}
You have not create a relationship. You are using a java native type int instead of another class Student.
#OneToOne( cascade= CascadeType.ALL)
#JoinColumn(name="Student_idStudent")
private int Student_idStudent;
As the error message clearly says: #OneToOne or #ManyToOne on 'univ_orientation.dz.Entity.cursusGlobale.Student_idStudent' references an unknown entity: int, Student_idStudent is defined as an int, not as an Entity. You will need something more along the lines of
#OneToOne( cascade= CascadeType.ALL)
#JoinColumn(name="Student_idStudent")
private Student student;
Even though it is a class it will be persisted as a Foreign Key to the Student table in the database. This is part of the abstraction provided by JPA.
Reference: JPA Hibernate One-to-One relationship
This question already has answers here:
JPA many to many with extra column
(3 answers)
Closed 7 years ago.
I have many-to-many relationship between Employee and SkillSet table, with additional column numberOfYears for each relation
employeeId skillSetId numberOfYears
10 101 2
I am new to JPA and unable to define the entities with relationship. Should I define a new entity class for Employee_SkillSet table? Or can I have many to many relationship defined in both Employee and SkillSet class? Where do I specify numberOfYears column?
Edit: Seems duplicate, but I had explicit requirement of using #IdClass, and one of the entities was #MappedSuperclass, so have to define both ID instance, and referred entity object.
Since you need an additional field for the tuple (Employee, SkillSet), you have to make another entity.
#Entity
public class Employee implements Serializable {
private #Id Long id;
#OneToMany(mappedBy="employee")
private List<EmployeeSkillSet> skillSets;
}
#Entity
public class SkillSet implements Serializable {
private #Id Long id;
}
#Entity
public class EmployeeSkillSet implements Serializable {
private #Id Long id;
private #ManyToOne Employee employee;
private #ManyToOne SkillSet skillSet;
private #Basic int numberOfYears;
}
Of course you can choose to use a #IdClass to make ("employee", "skillSet") the primary key of EmployeeSkillSet like so:
#Entity #IdClass(EmployeeSkillSet.Key.class)
public class EmployeeSkillSet implements Serializable {
private #Id #ManyToOne Employee employee;
private #Id #ManyToOne SkillSet skillSet;
private #Basic int numberOfYears;
public static class Key implements Serializable {
private Long employee; // plus getter+setter
private Long skillSet; // plus getter+setter
// plus hashCode, equals
}
}
Although you can use #ManyToMany annotation is better for performance reasons define two many to one relations in order to model the Many To Many relationship.
You will need 4 artifacts, there are
Employee Entity
SkillSet Entity
EmployeeSkillSet Relation entity (here you can specify numberOfYears Column)
EmployeeSkillSetPK (Primary Key for EmployeeSkillSet Relation entity)
The code would be something like this
Employee
package <your_package>;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
*/
#Entity
#Table(name="EMPLOYEE")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="EMPLOYEEID")
private int id;
// Rest of columns and getter and setters for all
}
SkillSet
package <your_package>;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
*/
#Entity
#Table(name="SKILLSET")
public class SkillSet implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="SKILLSETID")
private int id;
// Rest of columns and getter and setters for all
}
EmployeeSkillSetPK
/**
*
*/
package <your_package>;
import java.io.Serializable;
import javax.persistence.Embeddable;
/**
*
*/
#Embeddable
public class EmployeeSkillSetPK implements Serializable {
#ManyToOne
private Employee emp;
#ManyToOne
private SkillSet sk;
/**
* #return the employee
*/
public Employee getEmployee() {
return emp;
}
/**
* #param employee the employee to set
*/
public void setEmployee(Employee employee) {
this.employee = employee;
}
/**
* #return the sk
*/
public SkillSet getSkillSet() {
return sk;
}
/**
* #param sk the sk to set
*/
public void setSkillSet(SkillSet sk) {
this.sk = sk;
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EmployeeSkillSetPK that = (EmployeeSkillSetPK) o;
if (employee != null ? !employee.equals(that.employee) : that.employee != null) {
return false;
}
if (sk != null ? !sk.equals(that.sk) : that.sk != null) {
return false;
}
return true;
}
public int hashCode() {
int result;
result = (employee != null ? employee.hashCode() : 0);
result = 31 * result + (sk != null ? sk.hashCode() : 0);
return result;
}
}
EmployeeSkillSet
/**
*
*/
package <your_package>;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
/**
*
*/
#Entity
#Table(name="EMPLOYEESKILLSET")
#AssociationOverrides({ #AssociationOverride(name = "pk.employee", joinColumns = #JoinColumn(name = "EMPLOYEEID")),
#AssociationOverride(name = "pk.sk", joinColumns = #JoinColumn(name = "SKILLSETID")) })
public class EmployeeSkillSet implements Serializable {
#EmbeddedId
private EmployeeSkillSetPK pk = new EmployeeSkillSetPK();
#Column
private Integer numberOfYears;
#Transient
public Employee getEmployee() {
return pk.getEmployee();
}
public void setEmployee(Employee employee) {
pk.setEmployee(employee);
}
#Transient
public SkillSet getSkillSet() {
return pk.getSkillSet();
}
public void setSkillSet(SkillSet sk) {
pk.setSkillSet(sk);
}
public Integer getNumbersOfYears() {
return numberOfYears;
}
public void setNumbersOfYears(Integer numberOfYears) {
this.numberOfYears = numberOfYears;
}
}
This is for JPA 1.0, I cannot test the code right now, but it should work.
Note that I wrote the table and columns names on my own. Adapt it as you wish.
I have 3 tables with a many to many relationship
A (id, name, ....) primary key id
B (id, name, ....) with primary key id
AB (id_a, id_b, date, ....) - relation table with no
primary key
Can i model the classes for hibernate so that I use IdClass and not EmbeddedId solution (so that I avoid using cascading unecessary level for calling - getId().getA())
PS tried to mix these solutions but it doesn't work:
http://www.mkyong.com/hibernate/hibernate-many-to-many-example-join-table-extra-column-annotation/
How to map a composite key with Hibernate?
When it comes to building relationships between entities,the closest wall next to me and my head often join... (Stephan)
Here is a working example of a many to many relationship between entities A and B:
A.java
import java.util.ArrayList;
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.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
#Entity
public class A {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
#Column(name="id_a")
private Integer id;
private String name;
#OneToMany(mappedBy="a",cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<AB> abAssociations = new ArrayList<>();
// Getters and setters...
}
B.java
import java.util.ArrayList;
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.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
#Entity
public class B {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
#Column(name = "id_b")
private Integer id;
private String name;
#OneToMany(mappedBy = "b", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<AB> abAssociations = new ArrayList<>();
// Getters and setters...
}
AB.java
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
#Entity
#IdClass(ABid.class)
public class AB {
#Id
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private A a;
#Id
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private B b;
#Temporal(TemporalType.TIMESTAMP)
private Date date;
// Getters and setters...
}
ABid.java
import java.io.Serializable;
// The IdClass MUST implement Serializable and override #hashCode and #equals
public class ABid implements Serializable {
private static final long serialVersionUID = -2834827403836993112L;
private A a;
private B b;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((a == null) ? 0 : a.hashCode());
result = prime * result + ((b == null) ? 0 : b.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ABid other = (ABid) obj;
if (a == null) {
if (other.a != null)
return false;
} else if (!a.equals(other.a))
return false;
if (b == null) {
if (other.b != null)
return false;
} else if (!b.equals(other.b))
return false;
return true;
}
}
pom.xml
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.7.Final</version>
</dependency>
Sample code
import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
// * Init entity manager
EntityManagerFactory emf = Persistence.createEntityManagerFactory("playground");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// * Create two entities and persist them
// We must persist the entities first alone before we build and flush their relation
A a = new A();
a.setName("foo");
em.persist(a);
B b = new B();
b.setName("bar");
em.persist(b);
// * Build relationships between the two previous entities
AB ab = new AB();
ab.setA(a);
ab.setB(b);
ab.setDate(new Date());
a.getAbAssociations().add(ab);
b.getAbAssociations().add(ab);
// * Flush our changements in the database
em.getTransaction().commit();
em.close();
emf.close();
}
}
Here is the sql code of the tables created by Hibernate on a Postgresql database.
CREATE TABLE a
(
id_a integer NOT NULL,
name character varying(255),
CONSTRAINT a_pkey PRIMARY KEY (id_a)
)
CREATE TABLE b
(
id_b integer NOT NULL,
name character varying(255),
CONSTRAINT b_pkey PRIMARY KEY (id_b)
)
CREATE TABLE ab
(
date timestamp without time zone,
b_id_b integer NOT NULL,
a_id_a integer NOT NULL,
CONSTRAINT ab_pkey PRIMARY KEY (a_id_a, b_id_b),
CONSTRAINT fk_3exna7nsxvj1kv9i9pntmwlf1 FOREIGN KEY (a_id_a)
REFERENCES a (id_a) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_n3jrq53nr1elew4rytocopkbu FOREIGN KEY (b_id_b)
REFERENCES b (id_b) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
In response to #valik's comment.
Try this :
#Entity
public class AB {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
#Column(name = "id_ab")
private Integer id;
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private A a;
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private B b;
#Temporal(TemporalType.TIMESTAMP)
private Date date;
// Getters and setters...
}
I have two tables connected with many-to-many relationship. Database is set on another server and I see really big performance problem when I'm trying to get informations about one of the records if these informations include total count of the second table.
First bean:
package dbaccess.beans.newsletter;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Column;
import javax.persistence.Table;
import dbaccess.beans.RegisteredUser;
#Entity
#Table(name="NEWSLETTER_LIST")
public class NewsletterList {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "G1")
#SequenceGenerator(name = "G1", sequenceName = "NEWSLETTER_LIST_SEQ", allocationSize = 1, initialValue= 1)
#Column(name = "LIST_ID", unique = true, nullable = false)
private Long listID;
#Column(name = "LIST_NAME", nullable = false, length = 50)
private String listName;
#ManyToMany(fetch = FetchType.LAZY, cascade = {})
#JoinTable(name = "NEWSLETTERLISTS_USERS", joinColumns = {
#JoinColumn(name = "LIST_ID", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "USER_ID", nullable = false) })
private Set<RegisteredUser> users = new HashSet<RegisteredUser>(0);
public Long getListID() {
return listID;
}
public void setListID(Long listID) {
this.listID = listID;
}
public Set<RegisteredUser> getUsers() {
return users;
}
public void setUsers(Set<RegisteredUser> users) {
this.users = users;
}
}
Second bean:
package dbaccess.beans;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Column;
import javax.persistence.Table;
import dbaccess.beans.newsletter.NewsletterList;
#Entity
#Table(name="USER")
public class RegisteredUser {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "G1")
#SequenceGenerator(name = "G1", sequenceName = "USER_SEQ", allocationSize = 1, initialValue= 1)
#Column(name = "USER_ID", unique = true, nullable = false)
private Long usrID;
#Column(name = "GIVENNAME", length = 20)
private String usrGivenName;
#Column(name = "FAMILYNAME", length = 20)
private String usrFamilyName;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "users", cascade = {})
public Set<NewsletterList> newsletterList = new HashSet<NewsletterList>();
public Long getUsrID() {
return usrID;
}
public void setUsrID(Long usrID) {
this.usrID = usrID;
}
public String getUsrGivenName() {
return usrGivenName;
}
public void setUsrGivenName(String usrGivenName) {
this.usrGivenName = usrGivenName;
}
public String getUsrFamilyName() {
return usrFamilyName;
}
public void setUsrFamilyName(String usrFamilyName) {
this.usrFamilyName = usrFamilyName;
}
public Set<NewsletterList> getNewsletterList() {
return newsletterList;
}
public void setNewsletterList(Set<NewsletterList> newsletterList) {
this.newsletterList = newsletterList;
}
#Override
public String toString() {
return "RegisteredUser[usrID=" + usrID + ", usrGivenName=" + usrGivenName + ", usrFamilyName=" + usrFamilyName + "]";
}
}
And the problem is when I try to execute this piece of code:
session = dbService.getSessionFactory().openSession();
Criteria c = session.createCriteria(NewsletterList.class);
c.add(Restrictions.eq("listID", listID));
List<NewsletterList> newsletterList = (List<NewsletterList>) c.list();
//below is most expensive
newsletterList.get(0).getUsers().size()
Is there any way to improve this performance?
Thanks in advance.
PS When I have approx. 70 users in one list, request to above code takes approx 5-6 seconds!
newsletterList.get(0).getUsers().size() makes Hibernate load all the users registered to the newsletter, only to get the number of registered users.
Use an ad hoc HQL query to count the number of registered users:
select count(user.usrID) from RegisteredUser user
inner join user.newsletterList newsLetter
where newsLetter.listID = :listId
Note that 5-6 seconds to execute the above code is way too much, though. You probably need to check if there is an index placed on the join columns of the join table.
Also note that you could simply use session.get(NewsLetter.class, listId) to get the list by ID.
And finally, everything would be easier and more readable if your IDs were all named id.
You might want to try using the Hibernate Criteria API to query for the number of users on a specific newsletter. It would look roughly like this:
Criteria crit = session.createCriteria(RegisteredUser.class);
crit.setProjection(Projections.rowCount());
crit.add(Restrictions.eq("listID", listID));
return (Long) crit.uniqueResult();