Hibernate HQL. ManyToMany in subquery - java

I have following entities:
#Entity
public class Company {
....
#OneToMany
private List<Product> products = new Arraylist<>();
....
}
#Entity
public class Product {
....
#Column(name="product_key")
private String productKey; // same value as in ProductCategory
....
}
#Entity
public class ProductCategory{
....
#Column(name="product_key")
private String productKey // same value as in Product
#ManyToMany
#JoinTable (...)
private List<Category> categories = new ArrayList<>();
....
}
I want to write query which will return companies with their corresponding categories:
Company - List<Category>
I.e. I want aggregate categories of each Product company have.
Currently I end up with this HQL query, but it doesn't work:
SELECT DISTINCT c,
(SELECT pc.categories
FROM ProductCategories pc
LEFT JOIN c.products products
WHERE pc.productKey IN products.productKey)
FROM Company c
I tried to add virtual List<Categories> field to Company entity using #JoinFormula, but without success (#JoinFormula can not be used with List types, only with single values)

I think Your DB design is making the solution hard.
Since company & product has a one to many relationship, the owner of the relation should be product. Therefore you could keep a reference to the company inside each product.
If I were you, I would go with a design like this.
Company Class
#Entity
public class Company {
#OneToMany(mappedBy="company", cascade = CascadeType.ALL)
private List<Product> products = new ArrayList<Product>();
#Id
private int companyId;
}
Product Class
#Entity
public class Product {
#Id
#Column(name = "product_key")
private String productKey; // same value as in ProductCategory
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "company", nullable = false)
private Company company;
#ManyToMany(cascade = CascadeType.ALL, mappedBy="products")
private List<ProductCategory> categories = new ArrayList<ProductCategory>();
}
ProductCategory Class
#Entity
public class ProductCategory {
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="ProductWiseCategory", joinColumns=#JoinColumn(name="cat_name"),
inverseJoinColumns=#JoinColumn(name="product_key") )
private List<Product> products = new ArrayList<Product>();

Related

Using EntityGraph with SuperClasses and Embedded Attributes

I would like to use the EntityGraph Feature because of the known n+1 Problem. I have the following Entities structure:
#Entity
#Table(name = "customer")
public class Customer extends Person {
#Column(name = "foo")
public String foo;
#Column(name = "bar")
public String bar;
}
#Entity
#Table(name = "person")
public class Person {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "car.id")
public Car car;
#Embedded
public Key key;
}
#Entity
#Table(name = "car")
public class Car {
#Column(name = "a")
public String a;
#Column(name = "b")
public String b;
}
#Embeddable
public class Key
{
#Column(name = "key_id")
public Long keyId;
#Column(name = "key_color")
public String keyColor;
}
Now I want to use a NamedEntityGraph. As far as I understand with "#NamedEntityGraph(name = "getCustomer", includeAllAttributes=true)" it should work but it doesnt.
The NamedEntityGraph call with
em.createQuery(criteriaQuery).setHint("javax.persistence.fetchgraph", em.getEntityGraph("getCustomer")).getResultList()
returns the amount of Customers in the database but all Attributes including car and the Embedded Attribute key is always null.
Do I have to use subgraphs? I tried to declare the NamedEntityGraph on Customer class also on Person class. It makes no difference.
EDIT:
After struggling a long time with this problem, i tried to break down it to the lowest level with these two entities
#Entity
#Table(name = "publication")
#NamedEntityGraph(name = "graph.Publication.articles",
attributeNodes = #NamedAttributeNode("articles"))
public class Publication {
#Id
private String publicationId;
private String name;
private String category;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "publicationId")
private List<Article> articles;
#Entity
#Table(name = "article")
public class Article {
#Id
private String articleId;
private String title;
private String publicationId;
}
If i create a query i can see further more than one query in the postgres log.
EntityGraph<?> entityGraph = em.getEntityGraph("graph.Publication.articles");
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("javax.persistence.fetchgraph", entityGraph).getResultList();
Different queries for all publications
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_1'
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_2'
But I would only have expected one query with a join here.
Finally I found a solution for my problem. I refer to the edited part of my question.
I found this page which describes very well how to use batch query hints to improve performance.
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html?m=1
For my example I don't need the entitygraph anymore. The query should created like this
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("eclipselink.batch", "x.articles").getResultList();

Load lazy collection in ManyToMany relationship

I would like to query all products for a company. The products should be loaded with the list of countries. I managed to write a Spring JPA repository method to query what I want but I wonder why I need a DISTINCT clause.
If I run the following query, I get one product per country. So if a product has 3 countries, the query will return the same 3 rows. Can you explain why?
#EntityGraph(attributePaths = "countries", type = EntityGraph.EntityGraphType.LOAD)
List<Product> findByCompanyIdOrderByIdAsc(Long companyId);
So to fix that issue I added a Distinct clause which return what I want.
#EntityGraph(attributePaths = "countries", type = EntityGraph.EntityGraphType.LOAD)
List<Product> findDistinctByCompanyIdOrderByIdAsc(Long companyId);
I have the same issue if I run a JPQL Select p from Product LEFT JOIN FETCH p.countries WHERE p.company.id = ?1 which is equivalent to findByCompanyIdOrderByIdAsc.
The entities:
public class Product implements Serializable {
#ManyToOne
#JsonIgnore
private Company company;
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "product_country",
joinColumns = #JoinColumn(name="product_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="country_id", referencedColumnName="id"))
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",
resolver = EntityIdResolver.class, scope = Country.class)
#JsonIdentityReference(alwaysAsId = true)
private Set<Country> countries = new HashSet<>();
}
public class Country implements Serializable {
}

Hibernate Joined inheritance: Query records by ID ONLY from child table

I have a hierarchy of classes like next:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="Person")
public class Person implements Serializable{
#Id
#Column(name = "PersonID", unique = true, nullable = false)
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
}
#Entity
#Table(name="Student")
#PrimaryKeyJoinColumn(name="PersonID")
public class Student extends Person{
}
#Entity
#Table(name="Bachelor")
#PrimaryKeyJoinColumn(name="PersonID")
public class Bachelor extends Student{
#OneToMany(mappedBy = "bachelor", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<BachelorExam> exams;
}
#Entity
#Table(name="Exam")
public class Exam implements Serializable {
#Id
#Column(name = "ExamID", unique = true, nullable = false)
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
}
#Entity
#Table(name="BachelorExam")
public class BachelorExam implements Serializable {
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "PersonID_FK", referencedColumnName = "PersonID")
private Bachelor bachelor;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "ExamID_FK", referencedColumnName = "ExamID")
private Exam exam;
}
I want to get users (regular student or bachelor) from appropriate table by ID using generic method like next:
<T extends Person> T getStudentById(Long studentId);
This method can be something like
public <T extends Person> T getUserById(Long personId) {
List<Class<?>> studentTypes = new LinkedList<>();
studentTypes.add(Student.class);
studentTypes.add(Bachelor.class);
for (Class aClass : studenTypes) {
List<T> results = getDatabaseProvider().getDataFromDatabase(String.format("select u %s as u " +
"where u.userId = '%d'", aClass.getName(), userId));
return results.get(0);
}
}
The problem is that when I save a bachelor object in database, hibernate also saves bachelor's id to 'Student' table so when I get data from database going through whole list of inherited classes, query returns record from table Bachelor and also record from table Student, because both contain required student ID.
I've tried to use InheritanceType Table_Per_class but in this case hibernate doesn't create foreign key for bachelor in table BachelorExam.
How can I receive only records from table Bachelor by id?
Thanks!

JPA 2: Order by not working in ManyToMany with extra fields

I have a ManyToMany which I have mapped like this question.
Please note, I have removed boilerplate for simplicity
#Entity
class Person {
#OneToMany(mappedBy = "person")
#OrderBy("sort")
private List<PersonAddress> adresses = new ArrayList<>();
}
#Entity
class PersonAdress {
#EmbeddedId
private PersonAdressId id;
#Column
private int sort;
#ManyToOne
private Person person;
#ManyToOne
private Address address;
}
#Entity
class Address {
#OneToMany(mappedBy = "address")
#OrderBy("sort")
private List<PersonAddress> persons = new ArrayList<>();
}
#Embeddable
public class PersonAdressId implements Serializable {
#Column(name = "person_id")
private long personId;
#Column(name = "address_id")
private long addressId;
}
I am trying to get all the adresses for person, and order by the sort attribute.
But for some reason I get exception or I don't get it sorted.
I have tried the following:
"select p from Person p where p.id=pid join fetch p.address a order by a.sort"
I have also tried:
Person person = entityManager.find(Person.class, personId);
person.getAddress() //<-- This should use the #OrderBy, but I don't get it ordered nor does it print out order by in the output
Can anyone spot why its not working?
Did you try following query:
Select p From Person p Left Join p.adresses a Where p.id = :pid Order By a.sort
See the HQL reference for more information.
To start with, your mapping is false : you want a many-to-many association between Person and Address ? and that's why you have that PersonAddress entity in the middle ?
In class Person, it should be :
#OneToMany(mappedBy = "person")
private List<PersonAddress> personAdresses = new ArrayList<>();
In class Address, it should be :
#OneToMany(mappedBy = "address")
private List<PersonAddress> personAddresses = new ArrayList<>();
Only then, your sort will work, as it is a member of PersonAddress entity

Spring Hibernate product - category relationship

I've read many tutorials about spring-hibernate relationships but I'm a bit confused about how to use them in my case... I've product/category entities defined as follow:
Product
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#Column
private int category;
.
.
.
Category
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
.
.
.
So, I'd like in the product list page, under the voice "category" would appear the category name, and in the product form the category list...
In my case a product fits only one category so if I'm right it should be a #ManyToOne but I don't know how to implement this... in my product database I've the categoryId field, but if I mark the category entity field as #OneToMany it will not be stored to the db...
EDIT
I've changed like this (as suggested):
Product.class
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
#Column
#Size (max = 255)
private String description;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
Category.class
#Entity
#Table(name = "categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
#Column
#Size (max = 255)
private String description;
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.EAGER, mappedBy = "category", cascade = CascadeType.ALL)
private Set<Product> products = new HashSet<Product>();
Controller
#RequestMapping(value = "/add/", method = RequestMethod.POST)
public String addProduct(
#ModelAttribute(value = "product") #Valid Product product,
BindingResult result, ModelMap model, Category category) {
if (result.hasErrors()) {
return "forms/productForm";
}
try {
category.addProduct(product);
product.setCategory(category);
// Add product to db
productService.addProduct(product);
} catch (Exception e) {
log.error("/add/---" + e);
return "redirect:/product/deniedAction/?code=0";
}
return "redirect:/admin/product/";
}
I also added a #initbinder on the product controller to translate the data from the product form string to Category... but now when I save a product it automatically saves a category instead of attach the existing selected one...
As the Product will have only one Category and Category will have a list of Products, you can relate these two by creating a Foreign Key in the Product table to refer to the primary key in the Category table:
Category Table: id, name, other fields...
Product Table: id, category_id (FK), and other fields.
And the mapping can be defined as below:
public class Category {
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.LAZY, mappedBy = "category", cascade = CascadeType.ALL)
private Set<Product> products = new HashSet<Product>();
...
}
public class Product {
//Here JoinColumn states that this entity is the owner of the relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
...
}
The mappedBy attribute tells Hibernate that the collection is a mirror image of the many-to-one association on the other side. Its like telling Hibernate that it should propagate changes made at the Product end of the association to the database, ignoring changes made only to the products collection that you have in the Category. Thus if we only call category.getProducts().add(product), no changes will be made persistent. As the association is bidirectional, you have to create the link on two sides, not just one.
For your convenience, you can add one addProduct method in the Category class to save the association:
public void addProduct(Product product) {
product.setCategory(this);
products.add(product);
}
You appear to have a one-to-many relationship between Category and Product (one category has many products)
In Java (and OO generally) you'd expect the Category class to contain a list of Products, so the Category can be said to 'own' products.
In SQL it's the other way round - you'd expect Product table to hold a foreign key reference to a Category, so here, the Product can be said to 'own' a Category.
Looks like your using JPA, so you could have something like this:
Category class:
#Entity
public class Category {
//other stuff...
#OneToMany(cascade=CascadeType.ALL, mappedBy="category")
private Set<Product> products;
}
Product class:
#Entity
public class Product {
//other stuff...
#ManyToOne
private Category category;
}
so you have this:
Product{
atributtes...
#ManyToOne
Category category; --so every product has a category
}
Category {
attributtes...
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="id_Product")
private List<Product> products;
}
try this, if not we can look another solution..
You are right, you should use #ManyToOne because "...a product fits only one category...".
In Product entity declare a Category field instead of int category and annotate it with #ManyToOne. Also add #JoinColumn to specify the name of product.category_id column in the database.
Product:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
.
.
.

Categories