Join Table and Spring Data Repository - jpa

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.

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 many to many child list not persisted

I have a senario where there is an entity Article, an entity Author and a join table Article_Author. Article and Author have a many to many relationship ( on article can have many authors and an author can have many articles).
here are my entities classes :
Author class:
#Entity
#Table(name="AUTHOR")
#NamedQuery(name="Author.findAll", query="SELECT a FROM Author a")
public class Author implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="AUTHOR_CODE")
private String authorCode;
private String nom;
//bi-directional many-to-many association to Article
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(
name="ARTICLE_AUTHOR"
, joinColumns={
#JoinColumn(name="AUTHOR")
}
, inverseJoinColumns={
#JoinColumn(name="ART_REF")
}
)
private List<Article> article;
}
//getters and setters + hashcode and equals methods
Article class :
#Entity
#Table(name="ARTICLE_TEST")
#NamedQuery(name="ArticleTest.findAll", query="SELECT a FROM ArticleTest a")
public class ArticleTest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="ART_REF")
private String artRef;
#Column(name="ART_TITRE")
private String artTitre;
//bi-directional many-to-many association to AutheurTest
#ManyToMany(mappedBy="articleTests", fetch=FetchType.EAGER)
private List<AutheurTest> autheurTests;
//getters and setters + hashcode and equals methods
}
and here is the code where I create an article and attach one author (for testing) :
//.....
AutheurTest aut1 = new AutheurTest();
AutheurTest aut2 = new AutheurTest();
aut1.setAutheurCode("1");
aut1.setNom("AUT1");
articlemodelBean.createautheurtest(aut1);
ArticleTest art1 = new ArticleTest();
art1.setArtRef("A");
art1.setArtTitre("ART1");
articlemodelBean.dmlArticle_test(art1,"insert"); //persist art1
AutheurTest newaut = articlemodelBean.getAuteurActive_test(aut1.getAutheurCode());
ArticleTest foundedart = articlemodelBean.findArticle_test(art1.getArtRef()); //getting the article
foundedart.getAutheurTests().add(newaut);
articlemodelBean.artaut(foundedart); //merge foudedart
//.....
So I'm not getting any error, both the article and author are added to database! but the foreign keys are not inserted into the join table. so basically the author is not attached to the article.

Load data from two OneToMany relationships

I want to display data from a database with JPA on the frontend, i.e. display the Person's details. Details can be 0..n Adresses and 0..m Phones. The entities are shown below:
#Entity
public class Person implements Serializable {
#Id #GeneratedValue
private int id;
private String name;
#OneToMany(mappedBy="person")
private List<Address> addresses = new ArrayList<Address>();
#OneToMany(mappedBy="person")
private List<Phone> phones = new ArrayList<Phone>();
// plus getter and setter
}
#Entity
public class Address implements Serializable {
#Id #GeneratedValue
private int id;
#ManyToOne
private Person person;
private String onestring; // plus getter and setter
}
#Entity
public class Phone implements Serializable {
#Id #GeneratedValue
private int id;
#ManyToOne
private Person person;
private String anotherstring; // plus getter and setter
}
As lazy loading is activated, ...
#PersistenceContext
private EntityManager em;
public Person getPerson(int id) {
return em.find(Person .class, id);
}
... would only provide proxies on adresses and phones.
Questions:
What is good way displaying the all data on the frontend, i.e. the person and all its addesses and phones? (Except for setting FetchType to EAGER).
Is there a way to fetch both addresses and phones into the same instance of Person or do I have to fetch the Person twice (one time with addresses, one time with phones) to omit a cartesian product?

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

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.

JPA: When parent entity got removed, child entity still remain

Customer Entity (Parent Entity)
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy="customer", cascade=CascadeType.ALL)
private List<Facility> facilities;
//Setter and Getter for name and facilities
public void addFacility(Facility facility){
if(this.facilities == null){
this.facilities = new ArrayList<Facility>();
}
this.facilities.add(facility);
facility.setCustomer(this);
}
}
Facility Entity (Child Entity)
#Entity
public class Facility {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name="CUSTOMER_FK")
private Customer customer;
private String name;
//Setter and Getter, equals and hashcode
...
}
in Customer entity, I use CascadeType.ALL, however when I remove a customer, the associated facilities are still there. I delete customer by
Query query = em.createNamedQuery("Customer.delete");
query.setParameter("id", customerId);
query.executeUpdate();
where
#NamedQuery(name="Customer.delete", query="delete from Customer c where c.id = :id")
Bulk delete operations are not cascaded, per JPA specification:
4.10 Bulk Update and Delete Operations
...
A delete operation only applies to
entities of the specified class and
its subclasses. It does not cascade to
related entities.
...
If you want to benefit from cascading, load the entity and then call EntityManager#remove(Object) on it.
Try with:
#Inject
EntityManager em;
Customer customer =...;
em.remove(customer);
This always cascades operations.