Skip to content

Commit 7b23c23

Browse files
feat : adds cache invalidation at bi-directional mapping (#1834)
* feat : add more tests related to 2nd level cache * fix issue with assertions * feat : Handing cache invalidation at bi-directional mapping * fix test failures * attempt 1 to fix the issue * adds more testcases
1 parent 0a73d18 commit 7b23c23

File tree

19 files changed

+562
-58
lines changed

19 files changed

+562
-58
lines changed

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/entities/Customer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.hibernatecache.entities;
22

3+
import jakarta.persistence.Cacheable;
34
import jakarta.persistence.CascadeType;
45
import jakarta.persistence.Column;
56
import jakarta.persistence.Entity;
@@ -22,6 +23,7 @@
2223
name = "uc_customer_email",
2324
columnNames = {"email"})
2425
})
26+
@Cacheable
2527
@Cache(region = "customerCache", usage = CacheConcurrencyStrategy.READ_WRITE)
2628
public class Customer {
2729

@@ -40,7 +42,7 @@ public class Customer {
4042
private String phone;
4143

4244
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
43-
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
45+
@Cache(region = "customerOrdersCache", usage = CacheConcurrencyStrategy.READ_WRITE)
4446
private List<Order> orders = new ArrayList<>();
4547

4648
public Long getId() {

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/entities/Order.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ public class Order {
3737

3838
@ManyToOne
3939
@JoinColumn(name = "customer_id")
40+
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
4041
private Customer customer;
4142

4243
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
43-
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
44+
@Cache(region = "orderItemsCache", usage = CacheConcurrencyStrategy.READ_WRITE)
4445
private List<OrderItem> orderItems = new ArrayList<>();
4546

4647
public Long getId() {

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/entities/OrderItem.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class OrderItem {
3636

3737
@ManyToOne
3838
@JoinColumn(name = "order_id")
39+
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
3940
private Order order;
4041

4142
public Long getId() {

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/repositories/CustomerRepository.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.example.hibernatecache.repositories;
22

33
import static org.hibernate.jpa.AvailableHints.HINT_CACHEABLE;
4+
import static org.hibernate.jpa.HibernateHints.HINT_JDBC_BATCH_SIZE;
45

56
import com.example.hibernatecache.entities.Customer;
67
import io.hypersistence.utils.spring.repository.BaseJpaRepository;
@@ -20,8 +21,14 @@ public interface CustomerRepository
2021
@EntityGraph(attributePaths = {"orders"})
2122
Optional<Customer> findByFirstName(String firstName);
2223

24+
@Override
25+
@QueryHints(@QueryHint(name = HINT_CACHEABLE, value = "true"))
26+
@EntityGraph(attributePaths = {"orders"})
27+
Optional<Customer> findById(Long aLong);
28+
29+
@QueryHints(@QueryHint(name = HINT_JDBC_BATCH_SIZE, value = "25"))
30+
@Query("delete from Customer ")
2331
@Transactional
2432
@Modifying
25-
@Query("delete from Customer c")
26-
void deleteAll();
33+
void deleteAllInBatch();
2734
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
package com.example.hibernatecache.repositories;
22

3+
import static org.hibernate.jpa.AvailableHints.HINT_CACHEABLE;
4+
import static org.hibernate.jpa.HibernateHints.HINT_JDBC_BATCH_SIZE;
5+
36
import com.example.hibernatecache.entities.OrderItem;
47
import io.hypersistence.utils.spring.repository.BaseJpaRepository;
8+
import jakarta.persistence.QueryHint;
9+
import java.util.List;
10+
import org.springframework.data.jpa.repository.Modifying;
11+
import org.springframework.data.jpa.repository.Query;
12+
import org.springframework.data.jpa.repository.QueryHints;
513
import org.springframework.data.repository.PagingAndSortingRepository;
14+
import org.springframework.data.repository.query.Param;
15+
import org.springframework.transaction.annotation.Transactional;
616

717
public interface OrderItemRepository
818
extends BaseJpaRepository<OrderItem, Long>, PagingAndSortingRepository<OrderItem, Long> {
919

10-
void deleteAll();
20+
@Query("select o from OrderItem o where o.order.id = :orderId")
21+
@QueryHints(@QueryHint(name = HINT_CACHEABLE, value = "true"))
22+
List<OrderItem> findByOrder_Id(@Param("orderId") Long orderId);
23+
24+
@QueryHints(@QueryHint(name = HINT_JDBC_BATCH_SIZE, value = "25"))
25+
@Query("delete from OrderItem ")
26+
@Transactional
27+
@Modifying
28+
void deleteAllInBatch();
1129
}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
11
package com.example.hibernatecache.repositories;
22

33
import static org.hibernate.jpa.AvailableHints.HINT_CACHEABLE;
4+
import static org.hibernate.jpa.HibernateHints.HINT_JDBC_BATCH_SIZE;
45

56
import com.example.hibernatecache.entities.Order;
67
import io.hypersistence.utils.spring.repository.BaseJpaRepository;
78
import jakarta.persistence.QueryHint;
89
import java.util.List;
910
import java.util.Optional;
11+
import org.springframework.data.jpa.repository.Modifying;
1012
import org.springframework.data.jpa.repository.Query;
1113
import org.springframework.data.jpa.repository.QueryHints;
1214
import org.springframework.data.repository.PagingAndSortingRepository;
1315
import org.springframework.data.repository.query.Param;
16+
import org.springframework.transaction.annotation.Transactional;
1417

1518
public interface OrderRepository extends BaseJpaRepository<Order, Long>, PagingAndSortingRepository<Order, Long> {
1619

17-
void deleteAll();
18-
1920
@QueryHints(@QueryHint(name = HINT_CACHEABLE, value = "true"))
2021
@Query("SELECT o FROM Order o join fetch o.orderItems oi WHERE o.id = :id ORDER BY oi.itemCode")
2122
Optional<Order> findById(@Param("id") Long id);
2223

2324
@QueryHints(@QueryHint(name = HINT_CACHEABLE, value = "true"))
2425
@Query("SELECT o FROM Order o join fetch o.orderItems oi WHERE o.customer.id = :customerId ORDER BY oi.itemCode")
2526
List<Order> findByCustomerId(@Param("customerId") Long customerId);
27+
28+
@Override
29+
@Transactional
30+
@Modifying
31+
@Query("delete from Order o where o.id = :id")
32+
void deleteById(@Param("id") Long id);
33+
34+
@QueryHints(@QueryHint(name = HINT_JDBC_BATCH_SIZE, value = "25"))
35+
@Query("delete from Order")
36+
@Transactional
37+
@Modifying
38+
void deleteAllInBatch();
2639
}

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/services/CustomerService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ private Pageable createPageable(FindCustomersQuery findCustomersQuery) {
5050
return PageRequest.of(pageNo, findCustomersQuery.pageSize(), sort);
5151
}
5252

53+
public boolean existsById(Long id) {
54+
return customerRepository.existsById(id);
55+
}
56+
5357
public Optional<CustomerResponse> findCustomerById(Long id) {
5458
return customerRepository.findById(id).map(customerMapper::toResponse);
5559
}

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/services/OrderItemService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,9 @@ public OrderItemResponse updateOrderItem(Long id, OrderItemRequest orderItemRequ
7171
public void deleteOrderItemById(Long id) {
7272
orderItemRepository.deleteById(id);
7373
}
74+
75+
public List<OrderItemResponse> findOrderItemsByOrderId(Long orderId) {
76+
List<OrderItem> byOrderId = orderItemRepository.findByOrder_Id(orderId);
77+
return orderItemMapper.toResponseList(byOrderId);
78+
}
7479
}

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/services/OrderService.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.hibernatecache.services;
22

3+
import com.example.hibernatecache.entities.Customer;
34
import com.example.hibernatecache.entities.Order;
45
import com.example.hibernatecache.exception.OrderNotFoundException;
56
import com.example.hibernatecache.mapper.OrderMapper;
@@ -8,6 +9,7 @@
89
import com.example.hibernatecache.model.response.OrderResponse;
910
import com.example.hibernatecache.model.response.PagedResult;
1011
import com.example.hibernatecache.repositories.OrderRepository;
12+
import jakarta.persistence.EntityManager;
1113
import java.util.List;
1214
import java.util.Optional;
1315
import org.springframework.data.domain.Page;
@@ -24,9 +26,12 @@ public class OrderService {
2426
private final OrderRepository orderRepository;
2527
private final OrderMapper orderMapper;
2628

27-
public OrderService(OrderRepository orderRepository, OrderMapper orderMapper) {
29+
private final EntityManager entityManager;
30+
31+
public OrderService(OrderRepository orderRepository, OrderMapper orderMapper, EntityManager entityManager) {
2832
this.orderRepository = orderRepository;
2933
this.orderMapper = orderMapper;
34+
this.entityManager = entityManager;
3035
}
3136

3237
public PagedResult<OrderResponse> findAllOrders(FindOrdersQuery findOrdersQuery) {
@@ -50,6 +55,11 @@ private Pageable createPageable(FindOrdersQuery findOrdersQuery) {
5055
return PageRequest.of(pageNo, findOrdersQuery.pageSize(), sort);
5156
}
5257

58+
public List<OrderResponse> findOrdersByCustomerId(Long customerId) {
59+
List<Order> orders = orderRepository.findByCustomerId(customerId);
60+
return orderMapper.mapToOrderResponseList(orders);
61+
}
62+
5363
public Optional<OrderResponse> findOrderById(Long id) {
5464
return orderRepository.findById(id).map(orderMapper::toResponse);
5565
}
@@ -76,6 +86,15 @@ public OrderResponse updateOrder(Long id, OrderRequest orderRequest) {
7686

7787
@Transactional
7888
public void deleteOrderById(Long id) {
89+
Order order = orderRepository.findById(id).orElseThrow(() -> new OrderNotFoundException(id));
90+
91+
// Delete order
7992
orderRepository.deleteById(id);
93+
94+
// Force cache eviction for the customer's orders collection
95+
entityManager
96+
.getEntityManagerFactory()
97+
.getCache()
98+
.evict(Customer.class, order.getCustomer().getId());
8099
}
81100
}

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/web/controllers/CustomerController.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
import com.example.hibernatecache.model.query.FindCustomersQuery;
55
import com.example.hibernatecache.model.request.CustomerRequest;
66
import com.example.hibernatecache.model.response.CustomerResponse;
7+
import com.example.hibernatecache.model.response.OrderResponse;
78
import com.example.hibernatecache.model.response.PagedResult;
89
import com.example.hibernatecache.services.CustomerService;
10+
import com.example.hibernatecache.services.OrderService;
911
import com.example.hibernatecache.utils.AppConstants;
1012
import jakarta.validation.Valid;
1113
import java.net.URI;
14+
import java.util.List;
1215
import org.springframework.http.ResponseEntity;
1316
import org.springframework.validation.annotation.Validated;
1417
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -27,9 +30,11 @@
2730
class CustomerController {
2831

2932
private final CustomerService customerService;
33+
private final OrderService orderService;
3034

31-
CustomerController(CustomerService customerService) {
35+
CustomerController(CustomerService customerService, OrderService orderService) {
3236
this.customerService = customerService;
37+
this.orderService = orderService;
3338
}
3439

3540
@GetMapping
@@ -50,6 +55,14 @@ ResponseEntity<CustomerResponse> getCustomerById(@PathVariable Long id) {
5055
.orElseThrow(() -> new CustomerNotFoundException(id));
5156
}
5257

58+
@GetMapping("/{id}/orders")
59+
ResponseEntity<List<OrderResponse>> getCustomerOrders(@PathVariable Long id) {
60+
if (!customerService.existsById(id)) {
61+
throw new CustomerNotFoundException(id);
62+
}
63+
return ResponseEntity.ok(orderService.findOrdersByCustomerId(id));
64+
}
65+
5366
@GetMapping("/search")
5467
public ResponseEntity<CustomerResponse> searchCustomer(@RequestParam String firstName) {
5568
return customerService

jpa/boot-hibernate2ndlevelcache-sample/src/main/java/com/example/hibernatecache/web/controllers/OrderController.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
import com.example.hibernatecache.exception.OrderNotFoundException;
44
import com.example.hibernatecache.model.query.FindOrdersQuery;
55
import com.example.hibernatecache.model.request.OrderRequest;
6+
import com.example.hibernatecache.model.response.OrderItemResponse;
67
import com.example.hibernatecache.model.response.OrderResponse;
78
import com.example.hibernatecache.model.response.PagedResult;
9+
import com.example.hibernatecache.services.OrderItemService;
810
import com.example.hibernatecache.services.OrderService;
911
import com.example.hibernatecache.utils.AppConstants;
1012
import jakarta.validation.Valid;
1113
import java.net.URI;
14+
import java.util.List;
1215
import org.springframework.http.ResponseEntity;
1316
import org.springframework.validation.annotation.Validated;
1417
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -27,9 +30,11 @@
2730
class OrderController {
2831

2932
private final OrderService orderService;
33+
private final OrderItemService orderItemService;
3034

31-
OrderController(OrderService orderService) {
35+
OrderController(OrderService orderService, OrderItemService orderItemService) {
3236
this.orderService = orderService;
37+
this.orderItemService = orderItemService;
3338
}
3439

3540
@GetMapping
@@ -47,6 +52,16 @@ ResponseEntity<OrderResponse> getOrderById(@PathVariable Long id) {
4752
return orderService.findOrderById(id).map(ResponseEntity::ok).orElseThrow(() -> new OrderNotFoundException(id));
4853
}
4954

55+
@GetMapping("/{id}/items")
56+
ResponseEntity<List<OrderItemResponse>> getOrderItems(@PathVariable Long id) {
57+
List<OrderItemResponse> orderItemsByOrderId = orderItemService.findOrderItemsByOrderId(id);
58+
if (orderItemsByOrderId.isEmpty()) {
59+
throw new OrderNotFoundException(id);
60+
} else {
61+
return ResponseEntity.ok(orderItemsByOrderId);
62+
}
63+
}
64+
5065
@PostMapping
5166
ResponseEntity<OrderResponse> createOrder(@RequestBody @Validated OrderRequest orderRequest) {
5267
OrderResponse response = orderService.saveOrder(orderRequest);
@@ -67,8 +82,8 @@ ResponseEntity<OrderResponse> deleteOrder(@PathVariable Long id) {
6782
return orderService
6883
.findOrderById(id)
6984
.map(order -> {
70-
orderService.deleteOrderById(id);
71-
return ResponseEntity.ok(order);
85+
orderService.deleteOrderById(order.orderId());
86+
return ResponseEntity.accepted().body(order);
7287
})
7388
.orElseThrow(() -> new OrderNotFoundException(id));
7489
}

0 commit comments

Comments
 (0)