I'm trying to lazily load the ingredients of a product in a self relationship. A product can have zero or more ingredients. The relationship is stored in the ProductComposition entity.
These are my entities:
Product
#Entity(name = Product.TABLE_NAME)
//#NamedEntityGraph(name = "graph.Product.ingredients", attributeNodes = //#NamedAttributeNode("ingredients"))
public class Product {
public static final String TABLE_NAME = "Product";
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long idProduct;
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE}, mappedBy = "product")
private List<OrderDetail> orders;
#OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL, mappedBy = "ingredient", orphanRemoval=true)
private List<ProductComposition> ingredients;
ProductComposition
#Entity(name = ProductComposition.TABLE_NAME)
#IdClass(ProductCompositionId.class)
public class ProductComposition {
public static final String TABLE_NAME = "ProductComposition";
#Id
#ManyToOne //(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "PrincipalProductID")
private Product principalProduct;
#Id
#ManyToOne //(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "IngredientID")
private Product ingredient;
private int quantity;
ProductCompositionId
class ProductCompositionId implements Serializable{
private long principalProduct;
private long ingredient;
In the method get of my Dao I've tried different things:
Fetching with a CriteriaQuery the ingredients and then set them to the product
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductComposition> q = cb.createQuery(ProductComposition.class);
Root<ProductComposition> product = q.from(ProductComposition.class);
product.fetch("principalProduct", JoinType.LEFT);
q.select(product).where(cb.equal(product.get("principalProduct"), id));
List<ProductComposition> ingredients = entityManager.createQuery(q).getResultList();
Product p = entityManager.find(Product.class, id);
p.setIngredients(ingredients);
Using an Entity Graph
EntityGraph<Product> graph = (EntityGraph<Product>) entityManager.getEntityGraph("graph.Product.ingredients");
Map<String, Object> ingredients = new HashMap<>();
ingredients.put("javax.persistence.fetchgraph", graph);
Product p = entityManager.find(entityClass, id, ingredients);
Calling the method initialize
p = productDao.get(p.getIdProduct()); //the get here just calls entityManager.find(Product.class, id)
Hibernate.initialize(p.getIngredients());
System.out.println("Ingredients size: "+p.getIngredients().size()); //gives 0
After calling those two above lines I get the following two logs, but p still has no ingredients after:
Hibernate: select product0_.idProduct as idProduc1_4_0_, product0_.name as name2_4_0_, orders1_.product_idProduct as product_3_3_1_, orders1_.foodOrder_idFoodOrder as foodOrde2_3_1_, orders1_.foodOrder_idFoodOrder as foodOrde2_3_2_, orders1_.product_idProduct as product_3_3_2_, orders1_.quantity as quantity1_3_2_, foodorder2_.idFoodOrder as idFoodOr1_2_3_, foodorder2_.CustomerID as Customer2_2_3_, foodorder2_.DeliverymanID as Delivery3_2_3_, foodorder2_.RestaurantID as Restaura4_2_3_ from Product product0_ left outer join OrderDetail orders1_ on product0_.idProduct=orders1_.product_idProduct left outer join FoodOrder foodorder2_ on orders1_.foodOrder_idFoodOrder=foodorder2_.idFoodOrder where product0_.idProduct=?
Hibernate: select ingredient0_.ingredient_idProduct as ingredie2_5_0_, ingredient0_.principalProduct_idProduct as principa3_5_0_, ingredient0_.ingredient_idProduct as ingredie2_5_1_, ingredient0_.principalProduct_idProduct as principa3_5_1_, ingredient0_.quantity as quantity1_5_1_, product1_.idProduct as idProduc1_4_2_, product1_.name as name2_4_2_ from ProductComposition ingredient0_ inner join Product product1_ on ingredient0_.principalProduct_idProduct=product1_.idProduct where ingredient0_.ingredient_idProduct=?
However, all tries can't load the ingredients. They just return an empty list.
What am I doing wrong in this methods?
I would prefer to keep the relationship as lazy. Also because otherwise hibernate will return cannot simultaneously fetch multiple bags referring to Product.orders and Product.ingredients
Related
I have multiple entities that i want to fetch in single query, because N+1 queries last too long.
For example SQL join query lasts 5 seconds on DB, but elcipselink persistence fetching lasts 50-80 seconds due to N+1 fetching.
I found out that LEFT JOIN FETCH is not working as soon as #ManyToOne relation is implemented.
Does anyone know solution to LEFT JOIN FETCH for this case?
Please find below simplified entities.
#Entity
#Table(name="SITUATION_DATA")
#NamedQuery(name="SituationData.findAll", query="SELECT s FROM SituationData s")
public class DatexSituationData implements Serializable {
private static final long serialVersionUID = 1L;
//bi-directional many-to-one association to SituationRecord
#OneToMany(mappedBy="datexSituationData", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
private List<SituationRecord> situationRecords;
}
#Entity
#Table(name="SituationRecord")
#NamedQuery(name="SituationRecord.findAll", query="SELECT s FROM SituationRecord s")
public class SituationRecord implements Serializable {
private static final long serialVersionUID = 1L;
#OneToMany(mappedBy="situationRecord", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
private List<SituationRecordComment> situationRecordComment;
#OneToMany(mappedBy="situationRecord", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
private List<SituationRecordTypeElement> situationRecordTypeElements;
//bi-directional many-to-one association to SituationLocation
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
#JoinColumn(name="ID_LOKACIJE")
private SituationLocation situationLocation;
//bi-directional many-to-one association to DatexSituationData
#ManyToOne()
#JoinColumns({
#JoinColumn(name="SITUATION_ID", referencedColumnName="ID", nullable=false),
#JoinColumn(name="SITUATION_VERSION", referencedColumnName="VERSION", nullable=false)
})
private DatexSituationData datexSituationData;
}
#Entity
#Table(name="SITUATION_LOCATIONS")
#NamedQuery(name="SituationLocation.findAll", query="SELECT s FROM SituationLocation s")
public class SituationLocation implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="situation_location_seq")
#SequenceGenerator(name="situation_location_seq", sequenceName="SEQ_SITUATION_LOCATION", allocationSize=1)
#Column(name="ID_LOKACIJE", unique=true, nullable=false)
private long idLokacije;
//bi-directional many-to-one association to SituationRecord
#OneToMany(mappedBy="situationLocation", cascade = CascadeType.PERSIST)
private List<SituationRecord> situationRecords;
}
This is how i fetch it.
I have tried all of the below combinations, but every combinations makes query for each row (object) in SituationData, or in some cases for each joined SituationData join SituationRecord.
String sQuery =
//"select * from SITUATION_DATA t";
//"SELECT * FROM (select t.*, rank() over(partition by t.id order by version desc) rnk from SITUATION_DATA t) where rnk = 1";
"SELECT ds FROM SituationData ds LEFT JOIN FETCH ds.situationRecords sr LEFT JOIN FETCH sr.situationLocation sl LEFT JOIN FETCH sr.situationRecordTypeElements sre LEFT JOIN FETCH sr.situationRecordComment src";
EntityManager em = Emf.getInstance().getFactory().createEntityManager();
//Query q = em.createNativeQuery(sQuery, DatexSituationData.class);
Query q = em.createQuery(sQuery, DatexSituationData.class);
// q.setHint("eclipselink.LEFT_FETCH", "t.situationRecords.situationRecordComment");
q.setHint("eclipselink.LEFT_FETCH", "ds.sr.sl");
q.setHint("eclipselink.LEFT_FETCH", "ds.sr.sre");
q.setHint("eclipselink.LEFT_FETCH", "ds.sr.src");
// q.setHint("eclipselink.JDBC_FETCH_SIZE", "100");
lResult = q.getResultList();
Since you are not explicitly state EclipseLink version being used, I'll just assume version 2.6.
As per the official EclipseLink documentation, eclipselink.LEFT_FETCH is not a supported query hint. You are probably trying to use eclipselink.join-fetch or eclipselink.left-join-fetch as documented here.
The proper way to use this hint is along the lines of:
String sQuery = "SELECT ds FROM DatexSituationData ds";
EntityManager em = emf.getInstance().getFactory().createEntityManager();
TypedQuery q = em.createQuery(sQuery, DatexSituationData.class);
q.setHint("eclipselink.join-fetch", "ds.situationRecords");
q.setHint("eclipselink.join-fetch", "ds.situationLocation");
// ...
lResult = q.getResultList();
Another approach would be batch fetching.
This would work as:
//...
em
.createQuery("SELECT ds FROM DatexSituationData ds")
.setHint("eclipselink.batch", "ds.situationRecords")
//...
.setHint("eclipselink.batch.type", "IN")
.setHint("eclipselink.batch_size", "1000");
//...
You would probably test both approaches and gather some metrics; then decide which one works better (in terms of performance) for your DB schema and usage patterns.
I have two entities, Movie and Genre, with a many to many relationship from genre to movie. Genre being the "parent" of the relationship.
This generates three tables: genre, movie and genre_movie
#Entity
public class Genre {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#ManyToMany(fetch = LAZY)
#JoinTable( name = "genre_movie",
joinColumns = {#JoinColumn(name = "genre_id")},
inverseJoinColumns = {#JoinColumn(name = "movie_id")}
)
private Set<Movie> movies = new HashSet<>();
...
}
#Entity
public class Movie {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
...
}
I it possible perform this query using the criteria query api? Filter movies based on their genre id.
select *
from movie
join genre_movie on movie.id = genre_movie.movie_id
where genre_id = 19;
It is not possible because the Movie entity does not have a reference to Genre as it is not bidirectional.
But you can restructure the query to get the same results:
Select * from genre
inner join genre_movie on genre.id = genre_movie.genre_id
inner join movie on genre_movie.movie_id = movie.id
where genre.genre_id = 19;
The query would be implemented as follows:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Movie> cq = cb.createQuery(Movie.class);
Root<Genre> rootGenre = cq.from(Genre.class);
Join<Genre,Movie> joinMovie = rootGenre.join("movies");
//Join<Genre,Movie> joinMovie = rootGenre.join(Genre_.movies);
cq.select(joinMovie);
cq.where(cb.equals(rootGenre.get("id"), 19));
//cq.where(cb.equals(rootGenre.get(Genre_.id), 19));
return this.em.createQuery(cq).getResultList();
I particularly recommend using metamodels (commented lines) since when accessing the properties by name in String format, errors can occur that with Metamodels not in more complex queries.
I also think it would be interesting to define the relationship as bidirectional to be able to set the Movie class as Root
I have 2 entities:
#Entity
class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Contract> contracts= new HashSet<>();
}
and
#Entity
class Contract {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private Integer numberOfClaims;
}
Now I want to build a javax.persistence.criteria.Predicate to find all Orders with numberOfClaims greater then x.
Try:
select o from Order o where
(select sum(c.numberOfClaims) from o.contracts c) > x
See some correlated subquery examples in the following cmobilecom-jpa developer guide:
https://cmobilecom.com/docs/jpa/latest/devguide/index.html
Using Criteria API:
(refer to the section: "Correlate To Root" of chapter "Subquery")
CriteriaQuery<Order> criteriaQuery = criteriaBuilder.createQuery(Order.class);
Root<Order> root = criteriaQuery.from(Order.class);
criteriaQuery.select(root);
criteriaQuery.distinct(true);
// Subquery: correlate the root of parent query
Subquery<Integer> subquery = criteriaQuery.subquery(Integer.class);
Root<Order> subqueryRoot = subquery.correlate(root);
Join<Order, Contract> contracts = subqueryRoot.join("contracts");
subquery.select(criteriaBuilder.sum(contracts.get("numberOfClaims")));
Predicate restriction = criteriaBuilder.greaterThan(subquery, x);
criteriaQuery.where(restriction);
I use PostgreSQL and I have these tables, product and product_media with relation OneToMany on product with product_media. I want to retrieve a list with product which each of them contains a list of product_media.
And I have two options in my mind in order to retrieve them from DB.
First solution is initially retrieve the list of product and then iterate the retrieved list and execute query in order to retrieve the list of product_media.
Query1:
select * from product as p where p.status=1;
Retrieve List and then iterate this list and execute this query:
select * from product_media as pm where pm.product_id=?
Second is to implement join in query and retrieve all data from my DB.
Query:
select * from product as p Join product_media as pm on (p.id=pm.product_id)
Retrieve a complex list with all data.
The problem of second option is to do not know an elegant way to map this list into an object which has the format below. Do you know how can map automatically the results into this format?
product:[
{
id:1,
name:'Pro1',
medias:[
{
id:1,
uuid:'asdfi-asdf-rg-fgsdf-do'
},
{
id:2,
uuid:'asdfi-asdf-rg-fgsdf-do'
}
]
},
{
id:2,
name:'Pro2',
medias:[
{
id:5,
uuid:'asdfi-asdf-rg-fgsdf-do'
},
{
id:7,
uuid:'asdfi-asdf-rg-fgsdf-do'
}
]
}
]
I think the second variant is the better option. After fetching the object tree from the database you can do something like the following to achieve what you are posted above:
Assuming your entities are defined as follows:
Product.java
public class Product {
private long id;
private String name;
private List<ProductMedia> mediaList;
public Product() {
mediaList = new ArrayList<ProductMedia>();
}
public Product(long id, String name) {
this.id = id;
this.name = name;
mediaList = new ArrayList<ProductMedia>();
}
// getters + setters
}
ProductMedia.java
public class ProductMedia {
private long id;
private String uuid;
public ProductMedia() { }
public ProductMedia(long id, String uuid) {
this.uuid = uuid;
}
// getters + setters
}
Using the Jackson library you can generate output as follows:
public class JsonTest {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Product prod = new Product(1, "p1");
ProductMedia pm = new ProductMedia(1, "uuid1");
ProductMedia pm2 = new ProductMedia(2, "uuid2");
prod.getMediaList().add(pm);
prod.getMediaList().add(pm2);
Product prod1 = new Product(2, "p2");
ProductMedia pm3 = new ProductMedia(3, "uuid3");
ProductMedia pm4 = new ProductMedia(4, "uuid4");
prod1.getMediaList().add(pm3);
prod1.getMediaList().add(pm4);
Product[] pList = {prod, prod1};
mapper.writeValue(System.out, pList);
}
}
In this example, I am writing the output onto the console. But you are not restricted to it; you can write to a file passing in a FileOutputStream.
To be able to run this example you need to add the dependency; if you use Maven you can add the following into your POM:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.4</version>
</dependency>
Otherwise add the jar of the dependency into your project build path.
If your response is not in json format you can try below
There is a many-to-many relationship between Product and Media.
Product_Media is a helper table to maintain many-to-many relationship between Product and Media entities.
Product entity:
#Entity(name = "product")
public class Product {
#Id
#GeneratedValue
private Long product_id;
#Column
private String name;
#ManyToMany(cascade = { CascadeType.MERGE }, fetch = FetchType.EAGER)
#JoinTable(name = "product_media", joinColumns = {
#JoinColumn(name = "product_id", table = "product") }, inverseJoinColumns = {
#JoinColumn(name = "media_id", table = "media") })
List<Media> medias;
}
Media entity
#Entity(name = "media")
public class Media {
#Id
#GeneratedValue
private Long media_id;
#Column
private String name;
}
SQL generated by Hibernate
select
product0_.product_id as product_1_1_0_,
product0_.name as name2_1_0_,
medias1_.product_id as product_1_1_1_,
media2_.media_id as media_id2_2_1_,
media2_.media_id as media_id1_0_2_,
media2_.name as name2_0_2_
from
product product0_
left outer join
product_media medias1_
on product0_.product_id=medias1_.product_id
left outer join
media media2_
on medias1_.media_id=media2_.media_id
where
product0_.product_id=?
If the relationship is one-to-many, change entities like below
Media Entity
#Entity(name = "media")
public class Media {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#ManyToOne
#JoinColumn(name = "product_id", referencedColumnName = "id", nullable = false, updatable = false)
private Product product;
public Media() {
}
}
Product Entity
#Entity(name = "product")
public class Product {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "product")
List<Media> medias;
}
Hibernate generated SQL
select
product0_.id as id1_2_0_,
product0_.name as name2_2_0_,
medias1_.product_id as product_3_2_1_,
medias1_.id as id1_0_1_,
medias1_.id as id1_0_2_,
medias1_.name as name2_0_2_,
medias1_.product_id as product_3_0_2_
from
product product0_
left outer join
media medias1_
on product0_.id=medias1_.product_id
where
product0_.id=?
I am building a messaging system for my web application using Spring MVC with Spring Data JPA and Hibernate as my JPA provider.
I have five entities: Thread, ThreadParticipant, Participant, Account and Company. Each message thread has at least two participants, one of which is associated with a user (Account entity), and the other is associated with a Company. This constraint is enforced by the application. The database is designed like this to support future features. An example of two participants for a given thread in the database looks as follows:
id account_id company_id
1 44 NULL
2 NULL 123
The row with id=1 is the user, and the row with id=2 is the company. What I want to do is to write an HQL query that extracts all Thread objects for a given account, containing both the user/account participant as well as the company participant. I have tried to use different alias for my joins, like this:
select distinct t
from Thread t
inner join fetch t.threadParticipants user_tp
inner join fetch t.threadParticipants company_tp
inner join fetch user_tp.participant user_p
inner join fetch user_p.account a
inner join fetch company_tp.participant receiver_p
inner join fetch receiver_p.company
where a.id = :accountId
I get the exception cannot simultaneously fetch multiple bags due to the two fetches of t.threadParticipants. If I only do a single join here, the generated SQL simply ignores my additional join and only joins to Participant once, which requires a participant to have both an account and a company associated. With raw SQL, I can do like this, and it works fine:
select *
from thread t
inner join thread_participant user_tp on (user_tp.thread_id = t.id)
inner join thread_participant company_tp on (company_tp.thread_id = t.id)
inner join participant user_p on (user_p.id = user_tp.participant_id)
inner join account a on (a.id = user_p.account_id)
inner join participant company_p on (company_p.id = company_tp.participant_id)
inner join company c on (c.id = company_p.company_id)
where a.id = 123;
If I don't use different alias for the same table (see the below query), the query runs fine, but I only get one of the thread participants returned - the one that is associated with the account.
select distinct t
from Thread t
inner join fetch t.threadParticipants tp
inner join fetch tp.participant p
inner join fetch p.account a
left join fetch p.company
where a.id = :accountId
Is there any way that I can do what I am trying to do with HQL, or do I have to go with using native SQL?
My mapping is as follows:
Thread entity
#Entity
#Table(name = "thread")
public class Thread {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Collection<ThreadParticipant> threadParticipants = new HashSet<>();
// Getters and setters
}
Participant entity
#Entity
#Table(name = "participant")
public class Participant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Account.class, cascade = { CascadeType.PERSIST })
#JoinColumn(name = "account_id")
private Account account;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
// Getters and setters
}
ThreadParticipant entity
#Entity
#Table(name = "thread_participant")
#IdClass(ThreadParticipantPK.class)
public class ThreadParticipant implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Participant.class, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "participant_id")
private Participant participant;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Thread.class)
#JoinColumn(name = "thread_id")
private Thread thread;
#Column(name = "last_viewed", nullable = true)
private Date lastViewed;
// Getters and setters
}
ThreadParticipantPK
public class ThreadParticipantPK implements Serializable {
private Thread thread;
private Participant participant;
public ThreadParticipantPK() { }
public ThreadParticipantPK(Thread thread, Participant participant) {
this.thread = thread;
this.participant = participant;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ThreadParticipantPK)) return false;
ThreadParticipantPK that = (ThreadParticipantPK) o;
if (!participant.equals(that.participant)) return false;
if (!thread.equals(that.thread)) return false;
return true;
}
#Override
public int hashCode() {
int result = thread.hashCode();
result = 31 * result + participant.hashCode();
return result;
}
// Getters and setters
}
Thank you in advance!
Try changing the type of the threadParticipants collection to Set instead of a Collection:
private Set<ThreadParticipant> threadParticipants;