I've explained my scenario through simple Parent Child tables.
My composite primary key is also a composite foreign key referencing Parent table.
create table parent(
code varchar(10) not null,
id int not null,
parentcol varchar(10),
primary key(code,id)
);
create table child(
code varchar(10) not null,
id int not null,
childcol varchar(10) not null,
primary key(code, id),
foreign key(code, id) references parent(code,id)
);
Entities created (this is through Eclipse JPA plugin)
#Entity
#Table(name="parent")
#NamedQuery(name="Parent.findAll", query="SELECT p FROM Parent p")
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ParentPK id;
#Column(length=10)
private String parentcol;
//bi-directional one-to-one association to Child
#OneToOne(mappedBy="parent")
private Child child;
public Parent() {
}
/* getters and setters */
}
#Embeddable
public class ParentPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(unique=true, nullable=false, length=10)
private String code;
#Column(unique=true, nullable=false)
private int id;
/* getters and setters */
/** Overridden equals and hashcode **/
}
#Entity
#Table(name="child")
#NamedQuery(name="Child.findAll", query="SELECT c FROM Child c")
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ChildPK id;
#Column(nullable=false, length=10)
private String childcol;
//bi-directional one-to-one association to Parent
#OneToOne
#JoinColumns({
#JoinColumn(name="code", referencedColumnName="code", nullable=false, insertable=false, updatable=false),
#JoinColumn(name="id", referencedColumnName="id", nullable=false, insertable=false, updatable=false)
})
private Parent parent;
/* getters and setters */
}
#Embeddable
public class ChildPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(insertable=false, updatable=false, unique=true, nullable=false, length=10)
private String code;
#Column(insertable=false, updatable=false, unique=true, nullable=false)
private int id;
/* overridden equals and hashcode */
I am using Spring data to save my entities as below. Parent table consist of a record with code as "code" and Id as 1.
Child child = new Child();
ChildPK childPK = new ChildPK();
childPK.setCode("code");
childPK.setId(1);
child.setId(childPK);
child.setChildcol("child1");
childRepository.save(child);
It succeeds with the 1st run when it has to insert a new record. But the issue is on the 2nd run when it has to update let's say with,
child.setChildcol("child2");
I face an error
HHH000327: Error performing load command : org.hibernate.TypeMismatchException: Provided id of the wrong type for class com.xebia.eTechLog.entities.Parent. Expected: class com.xebia.eTechLog.entities.ParentPK, got class com.xebia.eTechLog.entities.ChildPK
In case I try to give a reference of ParentPk in the Child table as
#Entity
#Table(name="child")
#NamedQuery(name="Child.findAll", query="SELECT c FROM Child c")
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ParentPK id;
#Column(nullable=false, length=10)
private String childcol;
//bi-directional one-to-one association to Parent
#OneToOne
#JoinColumns({
#JoinColumn(name="code", referencedColumnName="code", nullable=false, insertable=false, updatable=false),
#JoinColumn(name="id", referencedColumnName="id", nullable=false, insertable=false, updatable=false)
})
private Parent parent;
It does work, but it won't in case there are more fields in the Parent class, which is my real scenario.
You should use a derived identity. Which means you should indicate that the child's reference to its parent maps the child's ID (with a #MapsId annotation):
#Entity
public class Child implements Serializable {
#EmbeddedId
private ChildPK id;
#Column(nullable=false, length=10)
private String childcol;
#OneToOne
#MapsId // <<< NB
#JoinColumns({
#JoinColumn(name="code", referencedColumnName="code"),
#JoinColumn(name="id", referencedColumnName="id")
})
private Parent parent;
...
}
Derived identities are discussed in the JPA 2.1 spec in section 2.4.1.
Related
I need help for this case.
I have the following entities (I removed getters/setters/hash/toString for easy reading):
#Entity
public class Company implements Serializable{
#Id
private String id;
}
#Entity
public class Document implements Serializable{
#Id
private String id;
}
#Entity
#IdClass(Inbox.PK.class)
public class Inbox implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Document document;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "inbox")
private Invoice invoice;
public class PK implements Serializable{
private Company company;
private Document document;
}
}
First question is, should I use Company and Document types in PK class or String and String?
And here ... the headache :
#Entity
#IdClass(Invoice.PK.class)
public class Invoice implements Serializable {
#Id
#OneToOne(fetch = FetchType.LAZY, mappedBy = "invoice")
// #MapsId // ???
#JoinColumn(name = "companyId")//, referencedColumnName = "company")// ???
#JoinColumn(name = "documentId")//, referencedColumnName = "document")// ???
// #PrimaryKeyJoinColumn // ????
private Inbox inbox;
#Data
public static class PK implements Serializable {
// private Inbox inbox; // ???
// private String company,document; // ???
// private String companyId,documentId; // ???
// private String inboxCompanyId,inboxDocumentId; // ???
}
}
The PK of the Invoice Entity is also the FK to Inbox (I would like constraints to be generated), and the PK of Inbox is composed of two Entities (Company and Document).
I prefer to use IdClass rather EmbeddedId.
How could I configure Invoice to have, at the end, (company_id,document_id) as PK AND FK to Inbox?
I saw your question posted in upwork. I think you should use string + string type fields with #Id and #Column annotations in PK class.
I have three entities:
Customer
It has a composite PK of... customer_id and company_id
Data
ID: data_id
FK: area_id (From Area below)
FK: customer_id (From Customer above)
Area
ID: area_id
FK: company_id (From Customer above)
How do I create the #Join annotations in JPA? I assume I have to use #JoinTable, but I don't know how to do it.
Customer
#Entity
#Table(name="customer")
#NamedQuery(name="Customer.findAll", query="SELECT c FROM Customer c")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private CustomerPK id;
//bi-directional many-to-one association to CustomColumnDataCustomer
#OneToMany(mappedBy="customer")
private List<CustomColumnDataCustomer> customColumnDataCustomers;
CustomerPK
#Embeddable
public class CustomerPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="customer_id")
private long customerId;
#Column(name="company_id")
private String companyId;
CustomColumnDataCustomer
#Entity
#Table(name="custom_column_data_customer")
#NamedQuery(name="CustomColumnDataCustomer.findAll", query="SELECT c FROM CustomColumnDataCustomer c")
public class CustomColumnDataCustomer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="custom_column_data_cust_uid")
private int customColumnDataCustUid;
//bi-directional many-to-one association to Customer
#ManyToOne
private Customer customer;
//bi-directional many-to-one association to AreaXCustomColumn
#ManyToOne
#JoinColumn(name="area_x_custom_column_uid")
private AreaXCustomColumn areaXCustomColumn;
AreaXCustomColumn
#Entity
#Table(name="area_x_custom_column")
#NamedQuery(name="AreaXCustomColumn.findAll", query="SELECT a FROM AreaXCustomColumn a")
public class AreaXCustomColumn implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="area_x_custom_column_uid")
private int areaXCustomColumnUid;
#Column(name="company_id")
private String companyId;
//bi-directional many-to-one association to CustomColumnDataCustomer
#OneToMany(mappedBy="areaXCustomColumn")
private List<CustomColumnDataCustomer> customColumnDataCustomers;
//bi-directional many-to-one association to CustomColumnDefinition
#ManyToOne
#JoinColumn(name="custom_column_definition_uid")
private CustomColumnDefinition customColumnDefinition;
A way to solve this would be with the annotation #EmbeddedId and #JoinColumn.
I needed a similar solution on a project I did recently. I think it'll be easier if I explain it by example:
I have three objects, a Platform, an EventMaster and a Membership.
The Membership is your Customer in this case, it has two PKs, the Platform ID and the EventMaster ID (this is solved by an #EmbeddedID):
#EmbeddedId
private MembershipKey id;
The MembershipKey class simply consists of both PKs of the other class:
#ManyToOne
#JoinColumn(name = "eventmaster_id")
private EventMaster eventMaster;
#ManyToOne
#JoinColumn(name = "mosplatform_id")
private MOSPlatform platform;
The Platform and the EventMasterclass both look the same (this is in the Platformclass):
#OneToMany(mappedBy = "id.platform")
private List<Membership> memberships;
I think that this should help you work out your solution.
EDIT: Code in the question was edited in.
This is my sample schema and I have generated jpa entities in eclipse.
I am using spring jpa repositories. I want to know if I need to create repository interface for student course table.
I am having doubt over addStudentCourse method of both student and course entity classes. List studentCourses will be always null for new entity, how can I fill student course table while registering student information in system i.e save method on studentRepository.
Student.java
#Entity
#NamedQuery(name="Student.findAll", query="SELECT s FROM Student s")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long studentid;
private String studentname;
//bi-directional many-to-one association to StudentCourse
#OneToMany(mappedBy="student")
private List<StudentCourse> studentCourses;
........
public StudentCourse addStudentCourse(StudentCourse studentCourse) {
getStudentCourses().add(studentCourse);
studentCourse.setStudent(this);
return studentCourse;
}
public StudentCourse removeStudentCourse(StudentCourse studentCourse) {
getStudentCourses().remove(studentCourse);
studentCours.setStudent(null);
return studentCourse;
}
Course.java
#Entity
#NamedQuery(name="Course.findAll", query="SELECT c FROM Course c")
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long courseid;
private String coursename;
//bi-directional many-to-one association to StudentCourse
#OneToMany(mappedBy="course")
private List<StudentCourse> studentCourses;
public StudentCourse addStudentCourse(StudentCourse studentCourse) {
getStudentCourses().add(studentCourse);
studentCourse.setCourse(this);
return studentCourse;
}
public StudentCourse removeStudentCourse(StudentCourse studentCourse) {
getStudentCourses().remove(studentCourse);
studentCourse.setCourse(null);
return studentCourse;
}
StudentCourse.java
#Entity
#Table(name="STUDENT_COURSE")
#NamedQuery(name="StudentCourse.findAll", query="SELECT s FROM StudentCourse s")
public class StudentCourse implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private StudentCoursePK id;
private String status;
//bi-directional many-to-one association to Course
#ManyToOne
#JoinColumn(name="COURSEID")
private Course course;
//bi-directional many-to-one association to Student
#ManyToOne
#JoinColumn(name="STUDENTID")
private Student student;
...
}
StudentCoursePK.java
#Embeddable
public class StudentCoursePK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(insertable=false, updatable=false)
private long studentid;
#Column(insertable=false, updatable=false)
private long courseid;
...
}
If I understood your question correctly what you want to do is to be able to save a student from the save method in StudentRepository, and that this inserts/updates the student and also inserts/updates the join table.
Since the Student entity is not the owning side (it's mapped by "student" in StudentCourse), saving a Student will not trigger a save on StudentCourse. To do so you can add a cascade property the list for insert, update... or just for everything:
#OneToMany(mappedBy="student", cascade = CascadeType.ALL)
private List<StudentCourse> studentCourses = new ArrayList<StudentCourse>();
Then you could a method on your #Service class that looks like this:
#Transactional
public void enrollInCourse(Student student, Course course) {
StudentCourse sc = new StudentCourse();
sc.setStudent(student);
sc.setCourse(course);
sc.setStatus("Enrolled");
student.getStudentCourses().add(sc);
studentRepository.save(student);
}
This will also populate the StudentCourse table.
So there's no need for a repository, although if the cascade doesn't work as expected you could create one and save the StudentCourse entity yourself manually.
If this does not work you could try changing your mappings. For n-ary relationships or join tables with extra columns I always define the #ManytoOne relationships inside the #Embeddable class, and in the entity that represents the join table I define getters as #Transient to allow access to the mapped objects which are inside the embedded composite Id.
You can see an example here, and a blog post about this approach here.
I got two entities like this (second one have a relation with first one) :
#Entity
#Table(name="FOA_ADRESSE_ICX")
public class FoaAdresseIcx implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="ID_ADRESSE", unique=true, nullable=false, precision=5)
private long idAdresse;
#Column(length=32)
private String bat;
#Column(name="COD_POSTAL", length=5)
private String codPostal;
// getters and setters ....
}
#Entity
#Table(name="FOA_INFOS_ICX")
public class FoaInfosIcx implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="COD_ICX", unique=true, nullable=false, length=8)
private String codIcx;
#Column(name="DATE_RAFFRAICHISEMENT")
#Temporal(TemporalType.TIMESTAMP)
private Date dateRaffraichisement;
#Column(name="LIB_AGENCE", nullable=false, length=98)
private String libAgence;
//uni-directional many-to-one association to FoaAdresseIcx
#ManyToOne
#JoinColumn(name="ID_ADRESSE", nullable=false)
private FoaAdresseIcx foaAdresseIcx;
// getters and setters....
}
I got a problem with the merge :
myEntityMgr.merge(myFoaInfosIcx);
Got this exception :
GRAVE: EJB Exception: : javax.persistence.EntityNotFoundException: Unable to find com.groupama.middlgan.entities.FoaAdresseIcx with id 0
In myFoaInfosIcx id is 0 because I don't initialise it, because I want JPA to create new FoaAdresseIcx in database if doesn't exist.
How can I do that ?
Using primitive types as DB ids has the downside you are currently experiencing. The default value is 0, which is a perfectly valid value for a DB id.
The persistence provider assumes that the entity is not new, but detached and behaves accordingly.
A solution is to use wrapper classes (or other non-primitive classes like UUID) as ids - Long in you case. Unless explicitly instantiated, the id attributes will be null and the provider will correctly identify an entity as new.
I have two or more tables resembles each other.
PARENT
ID | PK
NAME | VARCHAR
CHILD
ID |PK
NAME | VARCHAR
AGE | INT
It's not #Inheritance situation because they are independent entities and related to each other by #OneToMany or #ManyToOne.
I create entity class for each other.
#Entity
public class Parent {
#Id
private Long id;
private String name;
#ManyToOne(mappedBy = "parent")
private Collection<Child> children;
}
#Entity
public class Child {
#Id
private Long id;
private String name;
private int age;
#OneToMany
private Parent parent;
}
Is there any nice way to share common fields mappings?
// #MappedSuperclass // is this what it is exactly for?
public abstract class Base {
// #Id protected Long id; // ##?
#Column(name = "name", nullable = false)
private String name;
}
#Entity
public class Parent extends Base {
#Id
#TableGenerator(...)
#GeneratedValue(...)
protected Long id;
#ManyToOne(mappedBy = "parent")
private Collection<Child> children;
}
#Entity
public class Child extends Base {
#Id
#TableGenerator(...)
#GeneratedValue(...)
protected Long id;
private int age;
#OneToMany
private Parent parent;
}
Is this OK?
Is it even possible declaring #Id protected Long id; on the Base leaving #TableGenerator and #GeneratedVAlue on extended classes?
Is there any nice way to share common fields mappings?
MappedSuperclass is exactly right tool for that.
Is it even possible declaring #Id protected Long id; on the Base
leaving #TableGenerator and #GeneratedVAlue on extended classes?
No, it is not possible.