JPA: Mapping ManyToMany relationship with additional property [duplicate] - java

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.

Related

In Spring Boot, how do you set a (extended) property using extended setters and getters?

I am using Spring Boot, and have the following Entity definitions (abridged):
package com.vw.asa.entities;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.PreUpdate;
import javax.validation.constraints.NotNull;
public abstract class CmsModel extends Model {
#Basic(optional = false)
#NotNull
#Column(name = "is_active")
private short isActive;
public short getIsActive() {
return isActive;
}
public void setIsActive(short isActive) {
this.isActive = isActive;
}
public void setIsActive(String isActive) {
if (isActive.equals("true")) {
this.isActive = IS_TRUE;
} else if (isActive.equals("1")) {
this.isActive = IS_TRUE;
} else {
this.isActive = IS_FALSE;
}
}
}
Then I have several models which extend this 'base' model, following this flavor:
package com.vw.asa.entities.cms;
import com.vw.asa.entities.CmsModel;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;
/**
* #author Barry Chapman
*/
#Entity
#Table(name = "cms_extra_questions", schema = "asa")
public class CmsExtraQuestions extends CmsModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
...
}
When I initialize an instance of CmsExtraQuestions as a result of a hibernate query, if I call setActive(true) on the object, it has no effect on the members of that object. When I copy the setters and getters from the CmsModel base class into the CmsExtraQuestions class, it works fine.
$entity = new CmsExtraQuestions();
$entity->setActive(true);
Why does this not set the member properties of the instantiated object when calling the extended setter? If this is normal - is there a way to add these properties and member functions to the base model so that they can be inherited also?
Rookie mistake, I forgot to add #MappedSuperClass to the inherited model, CmsModel. That allows JPA to map the properties from that class as though they were defined in the model that was inheriting that base class.
#MappedSuperClass
public abstract class CmsModel extends Model {
#Basic(optional = false)
#NotNull
#Column(name = "is_active")

#OneToOne or #ManyToOne on 'univ_orientation.dz.Entity.cursusGlobale.Student_idStudent' references an unknown entity: int

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

Does Class name matter during ORM process?

I have defined 3 classes as follows:
I tried to generate the schema in MySQL.
It won't succeed. But the wired thing is after I have renamed one class, CerifLink--->MyLink, it works.
Can anyone give a reasonable explanation?
package org.epos.grdb.jpa.entity;
import java.io.Serializable;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
#Entity
public class CerifLink implements Serializable {
/**
*
*/
private static final long serialVersionUID = -9162577962410473641L;
#Id
#ManyToOne
#JoinColumns(value = { #JoinColumn(referencedColumnName = "id"),
#JoinColumn(referencedColumnName = "cfid") }, foreignKey = #ForeignKey(value = ConstraintMode.CONSTRAINT, foreignKeyDefinition = "foreign key (`cfClassId`) references `cfClass` (`cfClassId`)"))
private Class clazz;
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
}
package org.epos.grdb.jpa.entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
public class MyEntity implements Serializable {
/**
*
*/
private static final long serialVersionUID = 5185624925049306788L;
#Id
protected String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
package org.epos.grdb.jpa.entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
#Entity
public class Class implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1573828246619342971L;
#Id
protected String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Id
#ManyToOne
#JoinColumn(name = "cfid", referencedColumnName = "id")
private MyEntity classSchema;
public MyEntity getClassSchema() {
return classSchema;
}
public void setClassSchema(MyEntity classSchema) {
this.classSchema = classSchema;
}
}
But the wired thing is after I have renamed one class, CerifLink--->MyLink, it works.
Can anyone give a reasonable explanation?
Yes class name matter, you should check the NamingStrategy of Hibernate, you can modify it depending on the hibernate version you are using, but for what are you doing I guess you can use the annotation table and assign a specific name
#Table(name = "myname")

Difficulties to use Hibernate in a Spring: Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class:

I am new in Spring ORM and I am trying to implement this Spring+Hibernate tutorial:
http://it4beginners.wordpress.com/2012/10/05/spring-3-and-hibernate-4-for-beginners/
The only difference is that I am not using PostgreSQL but I am using MySql databse, so I have insert the right MySql connector in the pom.xml file and I have change the mainDataSource property in the ApplicationContext.xml configuration file...
The problem is that when I run my App.java class (the main class that contain the main method) I obtain the following error message in the stacktrace:
Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: org.andrea.myexample.myHibernateSpringExample.entityclasses.ChemicalStructure.occurence[java.lang.Object]
at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1057)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:733)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:242)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:372)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:357)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
... 12 more
I don't understand where is the problem !!!
This is the code of the class that seems present the problem:
package org.andrea.myexample.myHibernateSpringExample.entityclasses;
import java.io.Serializable;
import java.util.Set;
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;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
#Entity
#Table(name = "chemical_structure")
public class ChemicalStructure implements Serializable {
private Long id;
private String structureKey;
private String structureData;
private Set<Object> occurence;
/**
* #return the id
*/
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "chem_structure_seq_gen")
#SequenceGenerator(name = "chem_structure_seq_gen", sequenceName = "seq_chemical_structure")
#Column(name = "structure_id")
public Long getId() {
return id;
}
/**
* #param id
* the id to set
*/
public void setId(Long id) {
this.id = id;
}
/**
* #return the structureKey
*/
#Column(name = "structure_key", unique = true)
public String getStructureKey() {
return structureKey;
}
/**
* #param structureKey
* the structureKey to set
*/
public void setStructureKey(String structureKey) {
this.structureKey = structureKey;
}
/**
* #return the structureData
*/
#Column(name = "chemical_structure")
public String getStructureData() {
return structureData;
}
/**
* #param structureData
* the structureData to set
*/
public void setStructureData(String structureData) {
this.structureData = structureData;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.chemicalStructure", cascade = CascadeType.ALL)
public Set<Object> getOccurence() {
return occurence;
}
public void setOccurence(Set<Object> occurence) {
this.occurence = occurence;
}
}
Do you have some idea?
Tnx
The reason for that error lies in the fact that you try to map a Set of plain Objects. Object is not a JPA-entity and therefore it cannot be mapped to a relational type.
You'll need to create an entity named Occurence and map it the way you did it with the Set of Objects.
I think the problem is here mappedBy = "pk.chemicalStructure"
Check that you have a ManyToOne relation in this class like:
#ManyToOne
#JoinColumn(name = "chemicalstructure_id", nullable = false)
#ForeignKey(name = "fk_chemicalstructure_id")
private Occurence occurence;
then mappedBy = "occurence"
The error says that this bi directional relation is missing.

Hibernate non-negative value constraint

I have the table, the snippet below.
package test;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
#Entity
#Table(uniqueConstraints = { #UniqueConstraint(columnNames = "code")},
name = "coupons")
public class Coupon implements Serializable {
private static final long serialVersionUID = 5534534530153298987L;
#Id
#GeneratedValue
#Column(name = "id")
private long id;
#Column(name = "available_count")
private Integer availableCount = 1;
public Integer getAvailableCount() {
return availableCount;
}
public void setAvailableCount(Integer availableCount) {
this.availableCount = availableCount;
}
}
How to make constraint to allow for availableCount be only non-negative?
If you need an actual database constraint, and your schema is generated by Hibernate, you can use #Check annotation:
#Entity
#Table(uniqueConstraints = { #UniqueConstraint(columnNames = "code")},
name = "coupons")
#Check(constraints = "available_count >= 0")
public class Coupon implements Serializable { ... }
make use of Hibernate Validator project
You can use #Min.
public class Coupon implements Serializable {
#Min(0)
#Column(name = "available_count")
private Integer availableCount = 1;
}
Min documentation: The value of the field or property must be an integer value greater than or equal to the number in the value element
Check all JPA constaints here
They are valid to be used in Hibernate
The easy way would be to make it like this:
public void setAvailableCount(Integer availableCount) {
if(availableCount < 0){
throw new IllegalArgumentExcpetion("Must be possive value");
}
this.availableCount = availableCount;
}
This won't create a databse constraint.
edit:
If you take use of JPA-Annotations, you can create an #PrePerist-Annotated method:
#PrePersist
public void something(){
if(availableCount < 0){
throw new IllegalArgumentExcpetion("Must be possive value");
}
}
The commit should fail, loading should work.

Categories

Resources