Two-side Joining in JPA Criteria Building - java

I've three entities, like that:-
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
#Expose
private long id;
#ManyToOne
#JoinColumn(name = "users_id")
#Expose
private User user;
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
#Expose
private long id;
}
#Entity
#Table(name = "users_phone")
public class UserPhone {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
#Expose
private int id;
#ManyToOne
#JoinColumn(name = "users_id")
#Expose
private User user;
}
Now, from I want to get results from product table based on joining the three tables. So I made a Specification so that I can pass it into the repository. Here is the code for Specification.
Specification<ProductPost> productSpecification = new Specification<ProductPost>() {
#Override
public Predicate toPredicate(Root<ProductPost> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
criteriaQuery = criteriaBuilder.createQuery();
if (userName.length() > 0) {
predicates.add(criteriaBuilder.like(root.join("user").<String>get("fullName"), "%" + userName + "%"));
}
if (phoneNumber.length() > 0) {
// Below line isn't working actually as join("UserPhone") - 'user' table has no reference for 'userPhone'. But 'userPhone' has 'user'.
predicates.add(criteriaBuilder.like(root.join("user").join("UserPhone").<String>get("phoneNumber"), "%" + postType + "%"));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
Yes, It will be easy if I have 'userPhone' reference in 'user' table like - product -> user -> userPhone rather than product -> user, userPhone -> user. But my schema is like that. Now I face troubles joining the 3 tables and fetch the results.

If cross-join is appropriated instead of join you can try this solution
if (phoneNumber.length() > 0) {
Root<UserPhone> userPhone = query.from(UserPhone.class);
predicates.add(criteriaBuilder.equal(
root.join("user").get("id"),
userPhone.get("user").get("id")
));
predicates.add(criteriaBuilder.like(
userPhone.get("phoneNumber"),
"%" + postType + "%"
));
}

Related

How to search by prefix using a Spring JPA Specification?

I'm stuck on a JPA Specification task.
I have two entities, prefixes and country codes.
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity(name = "prefixes")
public class PrefixEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String prefix;
#OneToMany(mappedBy = "prefix", cascade = CascadeType.ALL)
private List<CountryCodeEntity> countryCodes;
#Data
#Builder
#Entity(name = "country_codes")
public class CountryCodeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String countryCode;
#ManyToOne
#JoinColumn(name = "prefixes_id")
private PrefixEntity prefix;
}
I want to get country code from "country_codes" table searching by prefix in "prefixes" table. I have already written a native query.
#Query(value = "SELECT cc.country_code FROM prefix_country_codes.prefixes p" +
" JOIN country_codes cc ON p.id = cc.prefixes_id" +
" WHERE p.prefix=?1", nativeQuery = true)
List<String> getCountryCodeByPrefix(String prefix);
How can I do this using Spring JPA Specification?
Something like:
public interface PrefixSpecification<P> {
static Specification<PrefixEntity> joinTest(String prefix) {
return (root, query, criteriaBuilder) -> {
Join<PrefixEntity, CountryCodeEntity> countryCodes = root.joinList("countryCodes");
...
};
}
}
and
phoneNumberRepository.findAll(Specification.where(PrefixSpecification.joinTest("371")));

How to write a spring boot jpa specification joining multiple tables

I want to write below query using spring boot specification.
SELECT o.*
from orders as o
inner join user as u on o.user_id = u.id
inner join user_group as ug on u.user_group_id = ug.id
left join order_product op on o.id = op.order_id
left join mobile_order_product mop on op.id = mop.order_product_id
left join mobile_device as md on mop.mobile_device_id = md.id
left join tablet_order_product top on op.id = top.order_product_id
left join tablet_device as td on top.tablet_device_id = td.id
where ug.id = 1
and (md.imei = 123456789 or td.imei = 123456789)
I try to write specification like below but I couldn't find a way to join order_product table.
public static Specification<Order> filterOrdersByGroupIdAndImei(int userGroupId, int imei) {
return (root, query, cb) -> {
Join<Object, User> user = root.join("user");
Join<Object, UserGroup> userGroup = user.join("userGroup");
// how to join order_product and other join tables
Predicate equalPredicate = cb.equal(userGroup.get("id"), userGroupId);
return cb.and(equalPredicate);
};
}
I am going to put answer in my own question.
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private User user;
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderProduct> orderProducts ;
}
#Entity
#Table(name = "order_product")
public class OrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private Order order;
#OneToMany(mappedBy = "orderProduct", fetch = FetchType.LAZY)
private List<MobileOrderProduct> mobileOrderProducts;
#OneToMany(mappedBy = "orderProduct", fetch = FetchType.LAZY)
private List<TabletOrderProduct> tabletOrderProducts;
}
#Entity
#Table(name = "mobile_order_product")
public class MobileOrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String mobileCode;
private String mobileNumber;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private MobileDevice mobileDevice;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
#JoinColumn(referencedColumnName = "id", nullable = false)
private OrderProduct orderProduct;
}
#Entity
#Table(name = "mobile_device")
public class MobileDevice {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String serialNumber;
private String imei;
#OneToMany(mappedBy = "mobileDevice", fetch = FetchType.LAZY)
#JsonIgnore
private List<MobileOrderProduct> mobileOrderProducts;
}
Here I only included couple of my entity class because then you can understand the table structure correctly
public static Specification<Order> filterOrdersByGroupIdAndImei(int userGroupId, String imei) {
return (root, query, cb) -> {
List<Predicate> list = new ArrayList<Predicate>();
Join<Order, User> user = root.join("user");
Join<User, UserGroup> userGroup = user.join("userGroup");
Join<Order, OrderProduct> orderProduct = root.join("orderProducts", JoinType.INNER);
Join<OrderProduct, MobileDevice> mobileDevice = orderProduct
.join("mobileOrderProducts", JoinType.LEFT)
.join("mobileDevice", JoinType.LEFT);
Join<OrderProduct, TabletDevice> tabletDevice = orderProduct
.join("tabletOrderProducts", JoinType.LEFT)
.join("tabletDevice", JoinType.LEFT);
list.add(cb.equal(userGroup.get("id"), userGroupId));
list.add(cb.or(cb.equal(mobileDevice.get("imei"), imei), cb.equal(tabletDevice.get("imei"), imei)));
Predicate[] p = new Predicate[list.size()];
return cb.and(list.toArray(p));
}

Hibernate - Group two entities

I have this code:
Map<OrderProduct, Product> temp =
session.createQuery("FROM OrderProduct " +
"WHERE orderId = '" + orderId + "'", OrderProduct.class)
.getResultList()
.stream()
.collect(Collectors.toMap(
k -> k,
v -> session.createQuery("FROM Product " +
"WHERE id = '" + v.getProductId() + "'", Product.class)
.getSingleResult()));
Here I take all order products of an specific order(orderId).
After make a stream and collect it as Map.
On key put order products and for value make another query to get product(product info).
Now I do 2 queries for every order product. There is a way to make all of this with just a single query?
Something like this:
SELECT *
FROM order_products OP, products P
WHERE OP.product_id = P.id;
But I don't know how to use in hibernate.
Here are entities:
#Entity
#Table(name = "order_products")
public class OrderProduct{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "order_id")
private Integer orderId;
#Column(name = "product_id")
private Integer productId;
#Column(name = "quantity")
private Integer quantity;
//getters setters
}
#Entity
#Table(name = "products")
public class Product{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
Integer id;
#Column(name = "name")
String name;
//getters setters
}
Here is a short example of how db look:
OrderProduct:
id-----order_id-----product_id-----quantity
1------1------------1--------------2
2------1------------2--------------1
3------2------------1--------------1
Product:
id-----name
1------productOne
2------productTwo
For many to one relation :
Change your entity to this :
#Entity
#Table(name = "products")
public class Product{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
Integer id;
#Column(name = "name")
String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "product", cascade = { CascadeType.ALL })
private List<OrderProduct> orderProducts = new ArrayList<OrderProduct>();
//getters setters
}
#Entity
#Table(name = "order_products")
public class OrderProduct{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#JoinColumn(name = "product_id")
private Product product;
#Column(name = "quantity")
private Integer quantity;
//getters setters
}
Now the code :
int orderId = 1; // put any id
OrderProduct orderProduct = session.get(OrderProduct.class, orderId);
System.out.println(orderProduct.getProduct().getId());
Hope this helps you. :)

OneToMany could not determine type for SpringBoot/SQLite3

I have a problem with the relationship oneToMany. I created tables in SQLite DB, this is my tables:
I created the two models CategoryModel and ProductModel.
ProductModel is:
#Entity
#Table(name = "Product_Category")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class ProductModel {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long product_id;
private Long category_id;
private String name;
private String description;
private int numberOfProduct;
private String image;
private int price;
#JoinColumn(name = "country_id", nullable = false)
private CategoryModel category;
//geter's and seter's
}
My CategoryModel:
#Entity
#Table(name = "Category")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class CategoryModel {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String category_name;
private String category_description;
private String image_path;
#OneToMany( mappedBy = "category")
private Set<ProductModel> category;
//Geter's and Seter's
My Repository:
public interface CategoryRepository extends JpaRepository<CategoryModel, Long> {
#Query("SELECT * "
+ "FROM Product_Category d INNER JOIN d.categoryModel e")
List<ProductModel> fetchEmpDeptDataInnerJoin();
}
I do not understand where I make a mistake. I have this error:
Could not determine type for:
com.dar.darkozmetika.models.CategoryModel, at table: product_category,
for columns: [org.hibernate.mapping.Column(category)]
1) Add #ManyToOne annotation:
#ManyToOne
#JoinColumn(name = "country_id", nullable = false)
private CategoryModel category;
2) Remember that you are using JPQL, not SQL (unless you sent native="true"):
#Query("SELECT p "
+ "FROM ProductModel p INNER JOIN p.category c")

Spring + Hibernate + JPA: remove entity reference in join table

I'm facing a difficulty in developing a server in Spring (+ Hibernate + JPA) for a project.
The structure of the server (the part of interest in this case) is composed of catalogs composed of products that can have some related feedbacks.
Here I share the 3 entities:
Catalog.java
#Entity
#Data
#Table(name = "catalog")
public class Catalog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String description;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "catalog_user_id", nullable = false)
private User user;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "catalog_product",
joinColumns = {#JoinColumn(name = "catalog_id")},
inverseJoinColumns = {#JoinColumn(name = "product_id")}
)
private List<Product> products;
public Catalog() {}
}
Product.java
#Entity
#Data
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String description;
#Column(nullable = false, length = 1)
#MapKeyEnumerated(EnumType.ORDINAL)
private Category category;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "product_user_id", nullable = false)
private User user;
public Product() {}
}
Feedback.java
#Entity
#Data
#Table(name = "feedback")
public class Feedback {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "feedback_user_id", nullable = false)
private User user;
#Column(nullable = false, length = 1)
#MapKeyEnumerated(EnumType.ORDINAL)
private Rating rating;
private String text;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "product_id", nullable = false)
private Product product;
public Feedback() {}
}
The problem occurs when I try to delete some entities. What I want is:
when I delete a catalog also the catalog references in the "catalog_product" join table should be deleted (but the product linked with the catalog should not be deleted);
when I delete a product also the product references in the "catalog_product" join table and the feedbacks related to that product should be deleted;
when I delete a feedback nothing happens.
In the business layer I have this operations:
CatalogServiceImpl.java
#Service
public class CatalogServiceImpl implements CatalogService {
#Autowired
private CatalogDAO catalogDAO;
#Autowired
private ModelMapper mapper;
public CatalogDTO findById(Long id) {
Catalog catalog = catalogDAO.findById(id);
return mapper.map(catalog, CatalogDTO.class);
}
public CatalogDTO findByName(String name) {
Catalog catalog = catalogDAO.findByName(name);
return mapper.map(catalog, CatalogDTO.class);
}
public List<CatalogDTO> findByUserId(Long id) {
List<Catalog> catalogs = catalogDAO.findByUserId(id);
Type listCatalogsType = new TypeToken<List<CatalogDTO>>() {}.getType();
return mapper.map(catalogs, listCatalogsType);
}
public List<CatalogDTO> findAll() {
List<Catalog> catalogs = catalogDAO.findAll();
Type listCatalogsType = new TypeToken<List<CatalogDTO>>() {}.getType();
return mapper.map(catalogs, listCatalogsType);
}
public CatalogDTO createCatalog(CatalogDTO catalogDTO) {
Catalog catalog = mapper.map(catalogDTO, Catalog.class);
Catalog catalogFromDB = catalogDAO.save(catalog);
return mapper.map(catalogFromDB, CatalogDTO.class);
}
public CatalogDTO updateCatalog(CatalogDTO catalogDTO) {
Catalog catalog = mapper.map(catalogDTO, Catalog.class);
Catalog catalogFromDB;
if(catalogDAO.exists(catalog.getId())) {
catalogFromDB = catalogDAO.save(catalog);
} else {
catalogFromDB = null;
}
return mapper.map(catalogFromDB, CatalogDTO.class);
}
public void deleteCatalog(Long id) {
Catalog catalog = catalogDAO.findById(id);
if(catalog != null) {
catalogDAO.delete(catalog.getId());
}
}
}
ProductServiceImpl.java
#Service
public class ProductServiceImpl implements ProductService {
#Autowired
private ProductDAO productDAO;
#Autowired
private ModelMapper mapper;
public ProductDTO findById(Long id) {
Product product = productDAO.findById(id);
return mapper.map(product, ProductDTO.class);
}
public ProductDTO findByName(String name) {
Product product = productDAO.findByName(name);
return mapper.map(product, ProductDTO.class);
}
public ProductDTO findByCategory(Category category) {
Product product = productDAO.findByCategory(category);
return mapper.map(product, ProductDTO.class);
}
public List<ProductDTO> findByUserId(Long id) {
List<Product> products = productDAO.findByUserId(id);
Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
return mapper.map(products, listProductsType);
}
public List<ProductDTO> findAll() {
List<Product> products = productDAO.findAll();
Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
return mapper.map(products, listProductsType);
}
public ProductDTO createProduct(ProductDTO productDTO) {
Product product = mapper.map(productDTO, Product.class);
Product productFromDB = productDAO.save(product);
return mapper.map(productFromDB, ProductDTO.class);
}
public ProductDTO updateProduct(ProductDTO productDTO) {
Product product = mapper.map(productDTO, Product.class);
Product productFromDB;
if(productDAO.exists(product.getId())) {
System.out.println(product.toString());
productFromDB = productDAO.save(product);
} else {
productFromDB = null;
}
return mapper.map(productFromDB, ProductDTO.class);
}
public void deleteProduct(Long id) {
Product product = productDAO.findById(id);
if(product != null) {
productDAO.delete(product.getId());
}
}
}
Now, when I try performing the operations of deletion of catalog or product an error of constraint key fail is triggered. For example trying to delete a product which has a reference in the catalog_product join table:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`e01`.`catalog_product`, CONSTRAINT `FKdx5j7bcx77t7h0hjw6tvoxmp1` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`))
I don't understand if there's a way to set the relations between entities to make what I want in an automatic way with Spring, or if I have to remove records with reference manually before the deletion of the catalog/product.
Thanks a lot in advance to everyone!
Luca

Categories