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.
Related
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.
I have two ManyToOne relationships in my application which are represented by Lists. For the relationship "ChapterSection - ManyToOne - Chapter, the foreign key is inserted in the table, when persisting the entity (in the table "ChapterSection" the foreign key for "Chapter" is stored). For the other relationship, which is "Chapter - ManyToOne - Document".
I use ddl.generation "drop-and-create-tables". In the Database I can see, that the column "Chapter.fk_document_iddocument" is marked as an indexed foreign key referenced to the document id. (I use EclipseLink and MySQL).
I don't see the difference between these two relationships and why one is working out but the other is not.
Document Entity:
#Entity
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="iddocument")
private Long id;
#Column(name="document_name")
private String documentName;
#OneToMany(mappedBy="Document", cascade = CascadeType.PERSIST)
private List<Chapter> chapters;
#Enumerated(EnumType.STRING)
#Column(name="document_type")
private DocumentTypes documentType;
//...getters, setters and other generated methods
Chapter Entity:
#Entity
public class Chapter implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="idchapter")
private Long id;
#Column(name="chapter_order")
private int chapterOrder;
#Column(name="parent_chapter")
private Long parentChapter;
#Column(name="chapter_name")
private String chapterName;
#ManyToOne(optional=false)
#JoinColumn(name="fk_document_iddocument")
private Document document;
#OneToMany(mappedBy="Chapter", cascade=CascadeType.PERSIST)
List<ChapterSection> chapterSections;
//...getters, setters and other generated methods
ChapterSection Entity:
#Entity
public class ChapterSection implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="idchaptersection")
private Long idChapterSection;
#Column(name="section_name")
private String sectionName;
#Column(name="section_order")
private int sectionOrder;
#Column(name="content")
private String content;
#ManyToOne
#JoinColumn(name="fk_chapter_idchapter")
private Chapter chapter;
//...getters, setters and other generated methods
The method I create the document with:
public void createDocument() {
List <Chapter> chapters = new ArrayList<>();
for (int i = 0; i <= 4; i++) {
Chapter chapter = new Chapter();
chapter.setChapterOrder(i);
chapter.setChapterName("Chapter "+i);
List <ChapterSection> chapterSections = new ArrayList<>();
for (int j = 0; j <= 4; j++) {
ChapterSection chapterSection = new ChapterSection();
chapterSection.setChapter(chapter);
chapterSection.setSectionName("Chapter "+i+" Section");
chapterSection.setSectionOrder(j);
chapterSection.setContent("Kapitel "+i+ ", Section "+j+" Content!");
chapterSections.add(chapterSection);
}
chapter.setChapterSections(chapterSections);
chapters.add(chapter);
}
document.setDocumentName("My Doc");
document.setChapters(chapters);
document.setDocumentType("My Doc Type");
documentDAO.persistDocument(document);
}
The mappedBy element of the #OneToMany annotation is defined in the JPA spcification as follows:
The field or property that owns the relationship. Required unless the relationship is unidirectional.
According to this definition your mappedBy elements must be set as (the value should be field name but not the class name):
#OneToMany(mappedBy="document", cascade = CascadeType.PERSIST)
private List<Chapter> chapters;
#OneToMany(mappedBy="chapter", cascade=CascadeType.PERSIST)
List<ChapterSection> chapterSections;
In you createDocument() method you haven't created the relationship between the Document and the Chapter. So you should tie them as follows:
chapter.setDocument(document);
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 use jBoss Fuse 6.1.0 with blueprint DSL with openJPA. I use Container Managed transaction (JTA) and transaction managed by Aspects that handles Commit and Rollback as of now
I have following Classes that are JPA entities.
#Entity
#Table(name="CLIENT")
#NamedQuery(name="Client.findAll", query="SELECT c FROM Client c")
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
//Had to add this for avoiding exception. And it works as expected
//Dummy constructor for JPA - Workaround
public Client(String s1, String s2){}
#Column(name="requestid", unique=true,nullable=false)
private String requestId;
#Id
#Column(name="clientid", unique=true, nullable=false, length=128)
private String clientId;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="REQUESTID", nullable=false)
private RoccoRequest roccoRequest;
//bi-directional One-To-Many association to ClientGroup
#OneToMany(mappedBy="client",fetch=FetchType.LAZY)
private List<ClientGroup> clientGroups;
....
,...
...
}
#Entity
#Embeddable
#Table(name="CLIENTGROUP")
#NamedQuery(name="ClientGroup.findAll", query="SELECT c FROM ClientGroup c")
public class ClientGroup implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ClientGroupPK id;
#Column(length=32)
private String type;
#Column(name="clientid", length=128)
private String clientId;
//bi-directional many-to-one association to Client
#ManyToOne(fetch=FetchType.EAGER)
#MapsId("clientid")
#JoinColumn(name="CLIENTID", nullable=true, insertable=false, updatable=false)
private Client client;
..
.
.
.
}
#Entity
#Table(name="ROCCOREQUEST")
#NamedQuery(name="RoccoRequest.CHECK_EXISISTING_CLIENT_DETAILS",
query="SELECT r FROM RoccoRequest r JOIN r.client c WHERE c.crmId = :crmId")
public class RoccoRequest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="requestid", unique=true, nullable=false, length=128)
private String requestId;
#OneToOne(mappedBy="roccoRequest", fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private Client client;
..
..
..
CriteriaQuery<Client> criteriaQuery = criteriaBuilder.createQuery(Client.class);
Root<Client> clientRoot = criteriaQuery.from(Client.class);
//Join the Client table with the RoccoRequest table
final Join<Client, RoccoRequest> clientRoccoJoin = clientRoot.join(Client_.roccoRequest,JoinType.INNER);
final Path<String> _requestStatus = clientRoccoJoin.get(RoccoRequest_.statusCode);
final Path<String> _requestId = clientRoccoJoin.get(RoccoRequest_.requestId);
final Predicate _crmIdPredicate = criteriaBuilder.equal(clientRoot.get(Client_.crmId), CRMId);
criteriaQuery.multiselect(_requestId,_requestStatus);
criteriaQuery.where(_crmIdPredicate);
//Get list of details of existing requests for the client with the request type as ACO
clientDetails = entityManager.createQuery(criteriaQuery).getResultList();
if(null != clientDetails) for(Client clientDetail : clientDetails){
StatusBO statusDetails = new StatusBO();
statusDetails.setCode((clientDetail.getRoccoRequest().getStatusCode()));
PreInitiationBO preinitiateDetails = new PreInitiationBO();
preinitiateDetails.getCaseHeader().setRequestId(requestId);
preinitiateDetails.setStatus(statusDetails);
exisitngRequestInfo.add(preinitiateDetails);
}
I have did some Criteria fetching of the entities. But I'm getting an exception as follows:
Can not find constructor for "class com.xxx.xxx.model.Client" with
argument types "[class java.lang.String, class java.lang.String]" to
fill data.
Why does JPA expect an argument Constructor? It has anything to do with the association? I tried removing the OneToMany relationship but I still get the error.
Please note that I have added a 2 argument constructor that makes no sense to me. But it works if it's given. log root level has Debug enabled. It has very less information on exception.
Please help.
As JBNizet pointed out,
I was making a dumb mistake by adding multiselect with two Strings but was having a CrtieriaQuery of type Client.class.
This can either be solved by removing the multiselect(Not in my case) or by Making the CriteriaQuery and other types with Tuples.class instead of Client.class and loop through the Tuples and get as tuple.get(0) etc.
Problem resolved. Thanks #Neil and #JBNizet