Load lazy collection in ManyToMany relationship - java

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 {
}

Related

Spring Boot entity many-to-many native query

I have category class as list in entity. How can I fill this entity with a native query?
Product.java
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer pid;
private String name;
#ManyToMany
#JoinTable(
name = "product_category ",
joinColumns = #JoinColumn(
name = "pro_id", referencedColumnName = "pid"),
inverseJoinColumns = #JoinColumn(
name = "cat_id", referencedColumnName = "cid"))
private List<Category> Categories;
}
Category.java
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long cid;
private String catname;
private String desc;
#ManyToMany(mappedBy = "categories")
private List<User> users;
}
How should I write a query? How can I fill the Categories list
#Query(value = "*****", nativeQuery = true)
List<Product> productList();
Instead of native query you can use hibernate queries like the below to find something useful.
Find all
ProductRepository.findAll();
Hibernate query
#Query(select product from Product product join product.categories categories)
List<Product> getAllProducts();
you can also sort the above queries using Pageable Object.
Could you please elaborate your question. As you can get the collection list if it is mapped just by find all function of Product Repository.
However, if you are looking to insert collection you can use cascade type in Categories field of Product Repository. This will automatically insert the categories entity once you save the Product entity to database by setting categories list in Product entity.

Java: Optimally handling many-to-many relation with huge data sets

here is my issue.
i have a huge amount of data in many-to-many tables that i have to update quite often and also request them with dynamic filters, order and request by location (postgis-db)...
shop entity:
public class Shop extends BaseEntity {
....
#ManyToOne
#JsonIdentityReference(alwaysAsId = true)
#JsonSerialize(using = IdSerializer.class)
#JoinColumn(name = "parent_id")
private Shop parent;
#ManyToMany(mappedBy = "shops")
#JsonIdentityReference(alwaysAsId = true)
#JsonIgnore
private List<Product> products = new ArrayList<>();
...
}
product entity:
public class Product extends BaseEntity {
...
#ManyToMany
#JsonIdentityReference(alwaysAsId = true)
#JoinTable(name = "product_shop",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "shop_id"))
private List<Shop> shops = new ArrayList<>();
the product table consists of about 1 million rows and the shops of about 8000. hence, the many-to-many table is 8*10^9 rows long in the worst case which results in really long request times.
any idea to solve this performantly with keeping in mind, that i want to sort the shops according to a location field which.
current tech stack:
java 8
spring boot
hibernate / jpa
postgresql / postgis

Self-referencing ManyToMany relations in Hibernate

I am looking for a way to model a relation between two or more objects of the same class in Hibernate. For example: I want to create a situation where I want to model relations between persons. In the database, I have two tables:
Person:
Id
Name
Relation:
Parent Id
Child Id
I tried to model this in Hibernate as a Person have two ManyToMany relations (Getter/Setter annotations are Lombok):
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "persons")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id
#Column(name="name")
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "relations",
joinColumns = {#JoinColumn(name = "parent_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "child_id", nullable = false)}
)
private Set<Person> children = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "relations",
joinColumns = {#JoinColumn(name = "child_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "parent_id", nullable = false)}
)
private Set<Person> parents = new HashSet<>();
}
This gave me the following problems:
With fetch type "LAZY" Hibernate complains about not having a Session when calling person.getChildren() or person.getParents()
With fetch type "EAGER" Hibernate returns null for the sets, which causes nullpointers when trying to add children or parents. The other thing I am worried about is that possible endless recursive eager fetching.
To get around this, I've tried to model the Relation as a class, so that I can use JPA queries in the PersonRepository to find Children and Parents without having to mess with the intricacies of ManyToMany :
public interface PersonRepository extends JpaRepository<Person, Long> {
#Query(
"select p from Person p join Relation r on p.id = r.childId where r.childId = :childId"
)
List<Person> findParents(Long childId);
}
This caused Hibernate to complain that Relation does not have an #Id field. I do not want to add that because the relation table in the database should model the relation, and not have an id of its own.
When trying to search online for similar structures in Hibernate I usually find classic many-to-many examples in the documentation and questions like this one where there is a relation between two different classes instead of a relation between two objects of the same class.
I'm looking for a way to model a relation between two or more objects of the same class in Hibernate. I want to be able to fetch relations lazily, or fetch them afterwards for performance reasons.
It would be nice if the relation table could have an extra field "type" which indicates the type of relation between Persons (child, parent, nephew, friend) so that there is room for new relation types without too much changes to database and code.
Not sure I understand you correctly, but I had a similar case once.
What I did was to persist the child/parent without its relations and updated them with their relations afterwards (still in the same transaction).
private void insertEntity(final AbstractEntity entity) {
insertedEntities.add(entity.getId());
final List<AbstractEntity> relations = entity.onBeforeInsertion();
for (final AbstractEntity relation : relations) {
if (!insertedEntities.contains(relation.getId())) {
insertEntity(relation);
}
}
final RelationBundle relationBundle = new RelationBundle();
entity.onInsertion(relationBundle);
immediatelySaveNewEntityThroughProxy(entity);
for (final AbstractEntity relation : relations) {
entity.onRelationInsertion(relation, relationBundle);
}
dao.saveOrUpdate(entity);
}
private void immediatelySaveNewEntityThroughProxy(final DocumentEntity entity) {
proxiedAccess().immediatelySaveNewEntity(entity);
}
private MyConsumer proxiedAccess() {
return applicationContext.getBean(getClass());
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void immediatelySaveNewEntity(final DocumentEntity entity) {
try {
if (!dao.entityExistsFromId((int) entity.getId())) {
dao.save(entity);
}
} catch (final Exception e) {
LOGGER.error("Error saving entity: {}", entity.getId(), e);
}
}

Hibernate HQL. ManyToMany in subquery

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>();

JPA Criteria - where clause

I have a Entity class which contain some primitive data type and contain some collection of object which have OneToMany relation and ManyToOne relation.
And when I am Fetching data by using JPA Criteria then I am getting all the data but I want to filter the result by using JPA Criteria where clause , I can apply in its primitive data type and its work fine , I have to apply where clause in collection of object which have OneToMany relation some have ManyToOne relation but how to apply where clause in these case , can you tell me ?
Use a .join(). Here is an example from the Criteria API documentation.
For queries that navigate to related entity classes, the query must define a join to the related entity by calling one of the From.join methods on the query root object or another join object. The join methods are similar to the JOIN keyword in JPQL.
The target of the join uses the Metamodel class of type EntityType to specify the persistent field or property of the joined entity.
The join methods return an object of type Join<X, Y>, where X is the source entity and Y is the target of the join. In the following code snippet, Pet is the source entity, Owner is the target, and Pet_ is a statically generated metamodel class:
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Pet, Owner> owner = pet.join(Pet_.owners);
Joins can be chained together to navigate to related entities of the target entity without having to create a Join instance for each join:
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Owner, Address> address = cq.join(Pet_.owners).join(Owner_.addresses);
I did that on this way:
Repository
#Query(value = "SELECT e FROM Employee e JOIN e.user u JOIN u.role r JOIN r.grants g where g = :grant")
List<Employee> findByGrant(#Param("grant") Grant grant);
Grant is related ManyToMany with Role wich is related OneToOne with employee.
So you just need to Join the Object and filter with an object setting the PKEY (ID).
Entities
#Entity
public class Employee {
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
#Entity
public class User implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 7854391295707311278L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
#JoinColumn(name = "role_id")
private Role role;
}
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.MERGE)
#JoinTable(
name = "role_has_grant",
joinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "id")
},
inverseJoinColumns = {
#JoinColumn(name = "grant_id", referencedColumnName = "id")
})
#Fetch(FetchMode.SELECT)
private List<Grant> grants;
}
#Entity
public class Grant implements GrantedAuthority {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
Another relation
#Query(value = "SELECT a FROM Activity a JOIN a.task t WHERE t.project = :project AND a.start BETWEEN :start AND :end")
#Entity
public class Task {
#ManyToOne
#JoinColumn(name = "project_id")
private Project project;
#OneToMany(mappedBy = "task")
private List<Activity> activities;
}

Categories