Skip to content

Commit

Permalink
feat: enable 1-n mapping with caching
Browse files Browse the repository at this point in the history
  • Loading branch information
rajadilipkolli committed Oct 4, 2023
1 parent 75f3054 commit 3213f2c
Show file tree
Hide file tree
Showing 26 changed files with 625 additions and 250 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package com.example.hibernatecache.entities;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -37,4 +34,8 @@ public class Customer {
@Email private String email;

private String phone;

@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<Order> orders = new ArrayList<>();
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,50 @@
package com.example.hibernatecache.entities;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Table(name = "orders")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Cache(region = "orderCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Order {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

@Column(nullable = false)
@NotEmpty(message = "Text cannot be empty")
private String text;

@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<OrderItem> orderItems = new ArrayList<>();

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
package com.example.hibernatecache.entities;

import jakarta.persistence.Cacheable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;

@Entity
@Table(name = "order_items")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Cacheable
public class OrderItem {

@Id
Expand All @@ -30,16 +34,34 @@ public class OrderItem {
@NotEmpty(message = "Text cannot be empty")
private String text;

@ManyToOne
@JoinColumn(name = "order_id")
private Order order;

@Override
public boolean equals(Object o) {
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
if (o == null) return false;
Class<?> oEffectiveClass =
o instanceof HibernateProxy
? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass()
: o.getClass();
Class<?> thisEffectiveClass =
this instanceof HibernateProxy
? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass()
: this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
OrderItem orderItem = (OrderItem) o;
return id != null && Objects.equals(id, orderItem.id);
return getId() != null && Objects.equals(getId(), orderItem.getId());
}

@Override
public int hashCode() {
return getClass().hashCode();
public final int hashCode() {
return this instanceof HibernateProxy
? ((HibernateProxy) this)
.getHibernateLazyInitializer()
.getPersistentClass()
.hashCode()
: getClass().hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.hibernatecache.mapper;

import com.example.hibernatecache.entities.Customer;
import com.example.hibernatecache.entities.Order;
import com.example.hibernatecache.entities.OrderItem;
import com.example.hibernatecache.model.request.OrderRequest;
import com.example.hibernatecache.model.response.CustomerResponse;
import com.example.hibernatecache.model.response.OrderItemResponse;
import com.example.hibernatecache.model.response.OrderResponse;
import java.util.List;
import org.mapstruct.IterableMapping;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValueCheckStrategy;

@org.mapstruct.Mapper(
componentModel = "spring",
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface Mapper {

@Mapping(source = "id", target = "customerId")
CustomerResponse mapToCustomerResponse(Customer customer);

@IterableMapping(elementTargetType = CustomerResponse.class)
List<CustomerResponse> mapToCustomerResponseList(List<Customer> customerList);

void updateCustomerWithRequest(Customer customerRequest, @MappingTarget Customer savedCustomer);

@Mapping(target = "customerId", source = "customer.id")
@Mapping(source = "id", target = "orderId")
OrderResponse orderToOrderResponse(Order order);

@IterableMapping(elementTargetType = OrderResponse.class)
List<OrderResponse> mapToOrderResponseList(List<Order> orderList);

@Mapping(target = "orderItems", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "customer", ignore = true)
void updateOrderWithRequest(OrderRequest orderRequest, @MappingTarget Order savedOrder);

@Mapping(source = "id", target = "orderItemId")
OrderItemResponse orderItemToOrderItemResponse(OrderItem orderItem);

@IterableMapping(elementTargetType = OrderItemResponse.class)
List<OrderItemResponse> orderItemListToOrderItemResponseList(List<OrderItem> list);

@Mapping(target = "orderItems", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "customer.id", source = "customerId")
Order mapToOrder(OrderRequest orderRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.hibernatecache.model.request;

import jakarta.validation.constraints.NotEmpty;

public record OrderRequest(
Long customerId, @NotEmpty(message = "Text cannot be empty") String text) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.hibernatecache.model.response;

import java.util.List;

public record CustomerResponse(
Long customerId,
String firstName,
String lastName,
String email,
String phone,
List<OrderResponse> orders) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.hibernatecache.model.response;

public record OrderItemResponse(Long orderItemId, String text) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.hibernatecache.model.response;

import java.util.List;

public record OrderResponse(
Long customerId, Long orderId, String text, List<OrderItemResponse> orderItems) {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.hibernatecache.entities.Customer;
import jakarta.persistence.QueryHint;
import java.util.Optional;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -13,5 +14,6 @@ public interface CustomerRepository extends JpaRepository<Customer, Long> {

@Transactional(readOnly = true)
@QueryHints(@QueryHint(name = HINT_CACHEABLE, value = "true"))
@EntityGraph(attributePaths = "orders")
Optional<Customer> findByFirstName(String firstName);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.example.hibernatecache.services;

import com.example.hibernatecache.entities.Customer;
import com.example.hibernatecache.mapper.Mapper;
import com.example.hibernatecache.model.response.CustomerResponse;
import com.example.hibernatecache.model.response.PagedResult;
import com.example.hibernatecache.repositories.CustomerRepository;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -14,24 +18,48 @@
public class CustomerService {

private final CustomerRepository customerRepository;
private final Mapper mapper;

public List<Customer> findAllCustomers() {
return customerRepository.findAll();
public PagedResult<CustomerResponse> findAllCustomers(
int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort =
sortDir.equalsIgnoreCase(Sort.Direction.ASC.name())
? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();

// create Pageable instance
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<Customer> customersPage = customerRepository.findAll(pageable);
List<CustomerResponse> customerResponses =
mapper.mapToCustomerResponseList(customersPage.getContent());
PageImpl<CustomerResponse> customerResponsePage =
new PageImpl<>(customerResponses, pageable, customersPage.getTotalElements());
return new PagedResult<>(customerResponsePage);
}

public Optional<Customer> findCustomerById(Long id) {
return customerRepository.findById(id);
public Optional<CustomerResponse> findCustomerById(Long id) {
return findById(id).map(mapper::mapToCustomerResponse);
}

public Customer saveCustomer(Customer customer) {
return customerRepository.save(customer);
public CustomerResponse saveCustomer(Customer customer) {
Customer saved = customerRepository.save(customer);
return mapper.mapToCustomerResponse(saved);
}

public void deleteCustomerById(Long id) {
customerRepository.deleteById(id);
}

public Optional<Customer> findCustomerByFirstName(String firstName) {
return customerRepository.findByFirstName(firstName);
public Optional<CustomerResponse> findCustomerByFirstName(String firstName) {
return customerRepository.findByFirstName(firstName).map(mapper::mapToCustomerResponse);
}

public Optional<Customer> findById(Long id) {
return customerRepository.findById(id);
}

public CustomerResponse updateCustomer(Customer customerRequest, Customer savedCustomer) {
mapper.updateCustomerWithRequest(customerRequest, savedCustomer);
return saveCustomer(savedCustomer);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.example.hibernatecache.services;

import com.example.hibernatecache.entities.OrderItem;
import com.example.hibernatecache.mapper.Mapper;
import com.example.hibernatecache.model.response.OrderItemResponse;
import com.example.hibernatecache.model.response.PagedResult;
import com.example.hibernatecache.repositories.OrderItemRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -17,13 +17,15 @@
public class OrderItemService {

private final OrderItemRepository orderItemRepository;
private final Mapper mapper;

@Autowired
public OrderItemService(OrderItemRepository orderItemRepository) {
public OrderItemService(OrderItemRepository orderItemRepository, Mapper mapper) {
this.orderItemRepository = orderItemRepository;
this.mapper = mapper;
}

public PagedResult<OrderItem> findAllOrderItems(
public PagedResult<OrderItemResponse> findAllOrderItems(
int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort =
sortDir.equalsIgnoreCase(Sort.Direction.ASC.name())
Expand All @@ -33,19 +35,27 @@ public PagedResult<OrderItem> findAllOrderItems(
// create Pageable instance
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<OrderItem> orderItemsPage = orderItemRepository.findAll(pageable);
List<OrderItemResponse> orderItemResponses =
mapper.orderItemListToOrderItemResponseList(orderItemsPage.getContent());

return new PagedResult<>(orderItemsPage);
return new PagedResult<>(
new PageImpl<>(orderItemResponses, pageable, orderItemsPage.getTotalElements()));
}

public Optional<OrderItem> findOrderItemById(Long id) {
return orderItemRepository.findById(id);
public Optional<OrderItemResponse> findOrderItemById(Long id) {
return findById(id).map(mapper::orderItemToOrderItemResponse);
}

public OrderItem saveOrderItem(OrderItem orderItem) {
return orderItemRepository.save(orderItem);
public OrderItemResponse saveOrderItem(OrderItem orderItem) {
OrderItem saved = orderItemRepository.save(orderItem);
return mapper.orderItemToOrderItemResponse(saved);
}

public void deleteOrderItemById(Long id) {
orderItemRepository.deleteById(id);
}

public Optional<OrderItem> findById(Long id) {
return orderItemRepository.findById(id);
}
}
Loading

0 comments on commit 3213f2c

Please sign in to comment.