Hi everyone,
I am new to hibernate JPA. Below is the relationship I defined between Entities A and B.
Here's the code for class A
class A{
#Id
#GeneratedValue
private Long id;
#Column(name = "col_1")
private Long col1;
#Column(name = "col_2")
private Long col2;
#OneToMany(fetch = FetchType.EAGER,mappedBy = "a")
private List<B> bList= new LinkedList<B>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<B> getBList() {
return bList;
}
public void setBList(List<B> bList) {
this.bList = bList;
}
}
And here's the code for class B
class B{
#Id
#GeneratedValue
private Long id;
#NotNull
#ManyToOne(optional=false)
#JoinColumns(value = { #JoinColumn(name = "col_1", referencedColumnName="col_1"),
#JoinColumn(name = "col_1", referencedColumnName="col_2") })
private A a;
#Column(name = "col_1")
private Long col1;
#Column(name = "col_2")
private Long col2;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
I have a crudrepository interface for A. When I run a crud method which loads A entity, I see one left outer join query for each record present in B mapped by col_1 and col_2 columns of A. All these queries are redundant. I am expecting only one left outer join query to be executed. This is causing timeouts in my application. Thanks for you help :)
Related
I am using Hibernate 5.3.10 as my ORM in the Spring boot based project. Suppose that we have the following entities:
#Entity
#Table(name = "parent")
#Inheritance(strategy = InheritanceType.JOINED)
public class Parent {
#EmbeddedId
private EmbId id;
public Id getId() { return id; }
public void setId(Id id) { this.id = id; }
}
#Entity
#Table(name = "child")
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "id"),
#PrimaryKeyJoinColumn(name = "date_time")
})
public class Child {
#EmbeddedId
private EmbId id;
private String name;
public Id getId() { return this.id; }
public void setId(Id id) { this.id = id; }
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
}
and the embeddable key as follow:
#Embeddable
public class EmbId {
#Column(name = "id")
private Long id;
#Column(name = "date_time")
private Date dateTime;
public Long getId() { return this.id; }
public void setId(Long id) { this.id = id; }
public Date getDateTime() { return this.dateTime; }
public void setDateTime(Date dateTime) { this.dateTime = dateTime; }
}
When I want to polymorphic query on Child entity, the JPA faces with ORA-00932 Inconsistent datatypes: expected TIMESTAMP got NUMBER and the following sql have seen in the console:
select child0_.id as date_time1_25_0, child0_.date_time as id2_25_0, child0_1_.name as name3_3_0 from my_schema.child child0_ inner join my_schema.parent child0_1_ on child0_.id = child0_1_.date_time and child0_.date_time = child0_1_.id
It seems that the equality of IDs is displaced. What happened and what should I do to resolve that?
Thanks in advance.
Fortunately, I found that adding referencedColumnName attribute to #PrimaryKeyJoinColumn could be guide hibernate to use the IDs in the right place.
So, the Child class should be as follow:
#Entity
#Table(name = "child")
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "id"),
#PrimaryKeyJoinColumn(name = "date_time")
})
public class Child {
#EmbeddedId
private EmbId id;
private String name;
public Id getId() { return this.id; }
public void setId(Id id) { this.id = id; }
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
}
I have 3 entity classes as follows (Example taken from https://hellokoding.com/jpa-many-to-many-extra-columns-relationship-mapping-example-with-spring-boot-maven-and-mysql/)
Book class
#Entity
public class Book{
private int id;
private String name;
private Set<BookPublisher> bookPublishers;
public Book() {
}
public Book(String name) {
this.name = name;
bookPublishers = new HashSet<>();
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<BookPublisher> getBookPublishers() {
return bookPublishers;
}
public void setBookPublishers(Set<BookPublisher> bookPublishers) {
this.bookPublishers = bookPublishers;
}
}
Publisher class
#Entity
public class Publisher {
private int id;
private String name;
private Set<BookPublisher> bookPublishers;
public Publisher(){
}
public Publisher(String name){
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "publisher")
public Set<BookPublisher> getBookPublishers() {
return bookPublishers;
}
public void setBookPublishers(Set<BookPublisher> bookPublishers) {
this.bookPublishers = bookPublishers;
}
}
Intersection Table
#Entity
#Table(name = "book_publisher")
public class BookPublisher implements Serializable{
private Book book;
private Publisher publisher;
private Date publishedDate;
#Id
#ManyToOne
#JoinColumn(name = "book_id")
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
#Id
#ManyToOne
#JoinColumn(name = "publisher_id")
public Publisher getPublisher() {
return publisher;
}
public void setPublisher(Publisher publisher) {
this.publisher = publisher;
}
#Column(name = "published_date")
public Date getPublishedDate() {
return publishedDate;
}
public void setPublishedDate(Date publishedDate) {
this.publishedDate = publishedDate;
}
}
I want to query 2 things,
Get list of books belonging to a particular publisher e.g. get all books associated with publisher 100
Get list of books not associated with a particular publisher e.g. get all books not associated with publisher 100
I want to achieve this using a simple JPARepository query if possible like findByXYZIn(...) etc.
Please let me know if querying a many to many relation is possible using JPA repository queries and if yes, whether I can do it directly or would it require any changes in the entity classes
In BookRepository
Get publisher's books
findBooksByBookPublishersPublisherId(Long publisherId)
Get books not published by publisher
findBooksByBookPublishersPublisherIdNot(Long publisherId)
IMHO Publication is much more apropriate name then BookPublisher in your case as Publisher by itself could be BookPublisher (a published that publishing books)
I'm not sure if you can make it just by method name. But you definitely can use JPA query. Something like this: "SELECT b FROM Book b JOIN b.bookPublishers bp JOIN bp.publisher p WHERE p.id = ?1". and with not equal for the second case
Well you can use named Queries to fulfill your requirements:
#Query("select b from Book b where b.publisher.idd = ?1")
Book findByPublisherId(int id);
#Query("select b from Book b where b.publisher.idd <> ?1")
Book findByDifferentPublisherId(int id);
Take a look at Using #Query Spring docs for further details.
I have a spring rest backend with two entities with a bidirectional relationshop (one-to-many, many to one). To overcome nested fetching issues, #JsonManagedReference/#JsonBackReference has been used for a perent/child relationship between entities.
The entites look as this:
#Entity
#Table(name = "Parent")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Parent implements java.io.Serializable {
private Integer id;
private List<Child> childList;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
#JsonManagedReference
public List<Child> getChildList() {
return childList;
}
public void setChildListe(List<Child> childListe) {
this.childList = childList;
}
}
#Entity
#Table(name = "Child")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Child implements java.io.Serializable {
private Integer id;
private Parent parent;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ParentID")
#JsonBackReference
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
This works fine when fetching the Parent element, the childset is then fetched alongside and displayed as an json-array.
However, there is no reference to parent in the child element due to the usage of jsonbackreferance.
How can solve this issue ? I need parent reference when fetching child
That would lead to an infinite loop when serializing to JSON. That's the whole reason we don't do bi-direction JSON relationships.
What I would do is add an additional column to the child entity if you need the ID alone.
private Integer parentId;
#Column(name = "ParentID", insertable=false, updateable=false)
public Integer getParentId() {
return parentId;
}
I'm using hibernate and spring data to save entities in my database. I'm attaching child elements to the parent and vice versa. Everything is saved correctly in the database, as well the child instances.
After calling save method again, the child elements get saved twice. I found out, that the reason is, that the framework does not update the childrens Id after persisting. It is always zero. Parent ID got updated correctly.
Any idea how to solve that?
Receipt.java:
#Entity
#javax.persistence.Table(name = "receipts")
public class Receipt {
private int id;
/*
* [...]
*/
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumn(name = "receipt_id",referencedColumnName = "id")
private List<ReceiptItem> getReceiptItemList() {
return receiptItemList;
}
private void setReceiptItemList(List<ReceiptItem> receiptItemList) {
this.receiptItemList = receiptItemList;
}
/*
* [...]
*/
}
ReceiptItem.java:
#Entity
#Table(name = "receipt_items")
public class ReceiptItem {
private int id;
/*
* [...]
*/
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "receipt_id")
public Receipt getReceipt() {
return receipt;
}
public void setReceipt(Receipt receipt) {
this.receipt = receipt;
}
}
Saving:
item = new ReceiptItem();
item.setReceipt(receipt);
receipt.getReceiptItemList().add(item);
// this should create the new ReceiptItem instance
receiptService.save(receipt);
System.out.println("ItemId: "+item.getId()); //but the id is still 0
// calling save method twice results in a second entry in the database
// instead of updating the previously inserted one
receiptService.save(receipt);
System.out.println("ItemId: "+item.getId());
Output:
ItemId: 0
ItemId: 0
I have the following database model:
A
aId
AB
aId
bId
B
bId
status
In a Spring data Specification, I want to return the instances of A when B.status is 'X'.
The JPQL code is the following:
select a from A a where a in
(select ab.id.a from AB ab where ab.id.b.status= :status)
These are the model classes:
#Entity
public class A {
private Long aId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.a")
private Set<AB> ab;
}
#Entity
public class B {
private Long bId;
private String Status;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.b")
private Set<AB> ab;
}
#Entity
public class AB {
private ABPK id;
}
public class ABPK {
#ManyToOne
#JoinColumn(name="aId")
private A a;
#ManyToOne
#JoinColumn(name="bId")
private B b;
}
How would be the JPA Criteria in the Spring Specification?
public class ASpecifications {
public static Specification<A> test(final String status) {
return new Specification<Party>() {
#Override
public Predicate toPredicate(Root<A> a, CriteriaQuery<?> query, CriteriaBuilder cb) {
return null;
}
};
}
}
The Specification that returns instances of A using Criteria API is the following:
public class ASpecifications {
public static Specification<A> test(final String status) {
return new Specification<Party>() {
#Override
public Predicate toPredicate(Root<A> a, CriteriaQuery<?> query, CriteriaBuilder cb) {
Subquery<A> sq = query.subquery(A.class);
Root<AB> ab = sq.from(AB.class);
sq.select(ab.get(AB_.id).get(ABPK_.a));
sq.where(cb.equal(ab.get(AB_.id).get(ABPK_.b).get(B_.status), status));
Predicate p = cb.in(a).value(sq);
return cb.and(p);
}
};
}
}
There are some good examples included on a previous post that address exactly what you're trying to accomplish here: jpa-2-0-criteria-api-subqueries-in-expressions.
I suppose you wanted to select "A entities from AB entities where B is of provided status":
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery(A.class);
Root<AB> ab = cq.from(AB.class);
cq.select(ab.get("id").get("a"));
cq.where(cb.equal(ab.get("id").get("b.status"), status));
Select In - is an option but you could achieve same result with double join. And its easier to make such a jpa specification:
SELECT A.ID FROM A LEFT JOIN AB ON A.ID = AB.A_ID LEFT JOIN B ON AB.B_ID = B.ID WHERE B.STATUS = 'STATUS'
Method would look like this:
public static Specification<A> findB(String input) {
return new Specification<A>() {
#Override
public Predicate toPredicate(Root<A> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Join<A,AB> AjoinAB = root.joinList(A_.AB_LIST,JoinType.LEFT);
Join<AB,B> ABjoinB = AjoinAB.join(AB_.B,JoinType.LEFT);
return cb.equal(ABjoinB.get(B_.NAME),input);
}
};
}
Or shorter
public static Specification<A> findB(String input) {
return (Specification<A>) (root, cq, cb) -> {
Join<A,AB> AjoinAB = root.joinList(A_.AB_LIST,JoinType.LEFT);
Join<AB,B> ABjoinB = AjoinAB.join(AB_.B,JoinType.LEFT);
return cb.equal(ABjoinB.get(B_.NAME),input);
};
}
I know it's been a long time since this question arose, but I came across it when I tried to do the same. This is my solution that works and I hope it helps someone.
Entities below
#Entity
public class A {
#Id
private Long id;
private String name;
#OneToMany(mappedBy = "a")
private List<AB> abList;
}
#Entity
public class B {
#Id
private Long id;
private String status;
#OneToMany(mappedBy = "b")
private List<AB> abList;
}
#Entity
public class AB {
#Id
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "a_id")
private A a;
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}