JPA - Join three tables. One with PK. The other two each have a part of the PK - jpa

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.

Related

Many To Many Relationship JPA with Entity

I have an issue trying to generate multiple relationship in JPA with three Entities.
Order
Product
Modifier
I have an Entity to handle the relationship many to many.
OrderProducts (order_id and product_id)
Contains the relationship of one order can have multiple products
OrderDetails (order_products_id and modifier_id)
Contains the id of the previous relationship Order-Products and the Id of the modifier which is a set of multiple values that can affect the price of the product.
Not quite sure how to handle this kind of relationship in JPA as I'm new to it.
You need a join entity with a composite key. You will need to research it further.
Your entities:
#Entity
#Table(name = "ordertable")
#Data
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "order")
#EqualsAndHashCode.Exclude
private Set<OrderProductModifier> products;
}
#Entity
#Table(name = "product")
#Data
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#EqualsAndHashCode.Exclude
private BigDecimal unitPrice;
}
#Entity
#Table(name = "modifier")
#Data
public class Modifier {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#EqualsAndHashCode.Exclude
private BigDecimal modifier;
}
And the entity that ties it all together will need to have the foreign keys for each of the above entities, as you have noted.
#Entity
#Table(name = "orderproductmodifier")
#Data
public class OrderProductModifier {
#EmbeddedId
private OrderProductModifierId id;
#MapsId("orderId")
#ManyToOne
#EqualsAndHashCode.Exclude
#ToString.Exclude
private Order order;
#MapsId("productId")
#ManyToOne
#EqualsAndHashCode.Exclude
private Product product;
#MapsId("modifierId")
#ManyToOne
#EqualsAndHashCode.Exclude
private Modifier modifier;
}
#SuppressWarnings("serial")
#Embeddable
#Data
public class OrderProductModifierId implements Serializable {
private Long orderId;
private Long productId;
private Long modifierId;
}
This is pretty simple to use:
private void run() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("UsersDB");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Product product = new Product();
product.setUnitPrice(BigDecimal.TEN);
em.persist(product);
Modifier modifier = new Modifier();
modifier.setModifier(new BigDecimal(".90"));
em.persist(modifier);
Order order = new Order();
em.persist(order);
OrderProductModifier opm = new OrderProductModifier();
opm.setId(new OrderProductModifierId());
opm.setOrder(order);
opm.setProduct(product);
opm.setModifier(modifier);
em.persist(opm);
em.getTransaction().commit();
em.clear();
Order o = em.createQuery("select o from Order o join fetch o.products where o.id = 1", Order.class).getSingleResult();
System.out.println("Order for " + o.getProducts());
System.out.println("Order cost " + o.getProducts().stream().map(p->p.getProduct().getUnitPrice().multiply(p.getModifier().getModifier()).doubleValue()).collect(Collectors.summingDouble(Double::doubleValue)));
}
The above query could be better, but that will give you something to work on.

JPA Get an entity by intermediate entity

I have 3 entities named Student, Course, and StudentCourse as follows
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue
private Integer id;
private String fullName;
}
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue
private Integer id;
private String courseName;
}
#Entity
#Table(name = "student_course")
public class StudeCourse {
#Id
#GeneratedValue
private Integer studentId;
private Integer courseId;
private String extraColumn;
}
Restrictions: There are a couple of restrictions
One student can have only one course or no course at all
An extra entity (StudentCourse) is required to hold the relation with primary key as studentId only
StudentCourse is required and hence cannot be skipped
Get Student with Course entity if there is one registered
Help required in some magical code to retrieve Course of Student if there is one assigned.
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue
private Integer id;
private String fullName;
// this is not correct code but just what I want
#JoinEntity(entity=StudentCourse, column="courseId")
private Course course;
}
StudentCourse is required and hence cannot be skipped
Ok, lets work with that.
One student can have only one course or no course at all
Implies that there is a #OneToOne relationship between Student and StudentCourse.
With the given information, the following entity model will work:
#Entity
#Table(name = "student")
public class Student {
#Column(name = "id")
#Id
#GeneratedValue
private Integer id;
#Column(name = "full_name")
private String full_name;
#OneToOne
#PrimaryKeyJoinColumn
private StudentCourse studentCourse;
}
#Entity
#Table(name = "student_course")
public class StudentCourse {
#Column(name = "id")
#Id
#GeneratedValue
private Integer id;
#JoinColumn(name = "id", nullable = false, updatable = false)
#MapsId
#OneToOne
private Student student;
#JoinColumn(name = "course_id")
#ManyToOne
private Course course;
...
}
A quick review:
#OneToOne on the Student.studentCourse field signifies that for every Student, there can be only one StudentCourse, and no more.
#PrimaryKeyJoinColumn on the Student.studentCourse field signifies that the value of the primary key column for Student should be used as the foreign key for the related entity, that is, StudentCourse.
#OneToOne on the StudentCourse.student field signifies that for every StudentCourse, there can be only one Student.
#MapsId on the StudentCourse.student field signifies that the primary key column for StudentCourse should be used as the join column for the association.
To check if a student has a course assigned, simply check if student.getStudentCourse() != null and then get the assigned course as student.getStudentCourse().getCourse().

JEE, JPA facade edit many to many relation

I'm facing an issue i can't figure out.
I got 2 entities : User and Course with a Many to Many relationship
User.java
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(
name="USR_COURSES",
joinColumns=#JoinColumn(name="USR_ID", referencedColumnName="ID"),
inverseJoinColumns=#JoinColumn(name="COURSE_ID",referencedColumnName="ID"))
private List<Course> courses;
...
Course.java
#Entity
#Table(uniqueConstraints=#UniqueConstraint(columnNames="CODE"))
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code;
private String name;
private String content;
#ManyToMany(mappedBy="courses",cascade = CascadeType.MERGE)
private List<User> users;
...
If i create a user and set him some courses, the join table will be updated with the new relations between the User and the Courses.
However if i want to edit a user by adding him courses, the join table is not update :
List<Course> test = myUser.getCourse();
test.add(facade.find(1l));
myUser.setCourse(test);
userFacade.edit(myUser);
I'm using NetBean and the AbstractFacade generated.
Thanks for helping !
In order for the cascade to work in that case you would need to set dependencies in the both sides of the ManyToMany relationship.
That means that you would need to do the following:
List<Course> test = myUser.getCourse();
Course course = facade.find(1l);
course.getUsers().add(myUser);
test.add(course);
myUser.setCourse(test);
userFacade.edit(myUser);
When you query for the Course you do not get the user list because you only have Cascade.MERGE set up on that relationship. This means that you need to set it manually as above.

Composite foreign key JPA TypeMismatchException

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.

Join Table and Spring Data Repository

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.