Skip to content

Commit

Permalink
feat : use vlads BaseJpaRepository and polish (#1592)
Browse files Browse the repository at this point in the history
* feat : use vlads BaseJpaRepository and polish

* implement code review comments

* adds test case
  • Loading branch information
rajadilipkolli authored Dec 22, 2024
1 parent 867e94e commit 6c7af2e
Show file tree
Hide file tree
Showing 29 changed files with 455 additions and 134 deletions.
2 changes: 1 addition & 1 deletion jpa/boot-data-customsequence/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
<configuration>
<java>
<googleJavaFormat>
<version>1.24.0</version>
<version>1.25.2</version>
<style>AOSP</style>
</googleJavaFormat>
</java>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.custom.sequence.config;

import io.hypersistence.utils.spring.repository.BaseJpaRepositoryImpl;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories(
repositoryBaseClass = BaseJpaRepositoryImpl.class,
basePackages = "com.example.custom.sequence.repositories")
public class JpaConfig {}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
@OpenAPIDefinition(
info = @Info(title = "boot-jpa-customsequence", version = "v1"),
servers = @Server(url = "/"))
public class SwaggerConfig {}
class SwaggerConfig {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

private final ApplicationProperties properties;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import jakarta.persistence.Id;
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;
Expand All @@ -33,7 +32,6 @@ public class Customer {
private String id;

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

@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
Expand All @@ -43,6 +41,16 @@ public Customer(String text) {
this.text = text;
}

public void addOrder(Order order) {
orders.add(order);
order.setCustomer(this);
}

public void removeOrder(Order removedOrder) {
orders.remove(removedOrder);
removedOrder.setCustomer(null);
}

@Override
public boolean equals(Object o) {

Check warning on line 55 in jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Customer.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

'equals()' method which does not check class of parameter

`equals()` should check the class of its parameter
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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;
Expand All @@ -32,7 +31,6 @@ public class Order {
private String id;

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

@ManyToOne(fetch = FetchType.LAZY)
Expand All @@ -44,7 +42,8 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
Order order = (Order) o;
return id != null && Objects.equals(id, order.id);
return Objects.equals(text, order.text)
&& Objects.equals(customer.getId(), order.customer.getId());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,80 @@
package com.example.custom.sequence.mapper;

import com.example.custom.sequence.entities.Customer;
import com.example.custom.sequence.entities.Order;
import com.example.custom.sequence.model.request.CustomerRequest;
import com.example.custom.sequence.model.response.CustomerResponse;
import com.example.custom.sequence.repositories.OrderRepository;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;

@Service
public class CustomerMapper {

private final OrderMapper orderMapper;
private final OrderRepository orderRepository;

public CustomerMapper(OrderMapper orderMapper, OrderRepository orderRepository) {
this.orderMapper = orderMapper;
this.orderRepository = orderRepository;
}

public CustomerResponse mapToResponse(Customer saved) {
return new CustomerResponse(saved.getId(), saved.getText());
return new CustomerResponse(
saved.getId(),
saved.getText(),
orderMapper.mapToResponseListWithOutCustomer(saved.getOrders()));
}

public Customer mapToEntity(CustomerRequest customerRequest) {
Customer customer = new Customer(customerRequest.text());
customerRequest
.orders()
.forEach(orderRequest -> customer.addOrder(orderMapper.mapToEntity(orderRequest)));
return customer;
}

public void updateCustomerFromRequest(CustomerRequest customerRequest, Customer foundCustomer) {
foundCustomer.setText(customerRequest.text());
List<Order> removedOrders = new ArrayList<>(foundCustomer.getOrders());
List<Order> ordersFromRequest =
customerRequest.orders().stream()
.map(
orderRequest ->
orderMapper.mapToEntityWithCustomer(
orderRequest, foundCustomer))
.collect(Collectors.toList());
removedOrders.removeAll(ordersFromRequest);

for (Order removedOrder : removedOrders) {
foundCustomer.removeOrder(removedOrder);
}

List<Order> newOrders = new ArrayList<>(ordersFromRequest);
newOrders.removeAll(foundCustomer.getOrders());

ordersFromRequest.removeAll(newOrders);

for (Order existingOrder : ordersFromRequest) {
existingOrder.setCustomer(foundCustomer);
// manually set the id of the existing order to avoid creating a new order instead of
// updating the existing one
for (Order foundOrder : foundCustomer.getOrders()) {
if (foundOrder.getText().equals(existingOrder.getText())) {
existingOrder.setId(foundOrder.getId());
break;
}
}
Order mergedOrder = orderRepository.merge(existingOrder);
foundCustomer
.getOrders()
.set(foundCustomer.getOrders().indexOf(mergedOrder), mergedOrder);
}

for (Order newOrder : newOrders) {
foundCustomer.addOrder(newOrder);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import com.example.custom.sequence.entities.Customer;
import com.example.custom.sequence.entities.Order;
import com.example.custom.sequence.model.response.CustomerResponse;
import com.example.custom.sequence.model.request.OrderRequest;
import com.example.custom.sequence.model.response.CustomerResponseWithOutOrder;
import com.example.custom.sequence.model.response.OrderResponse;
import com.example.custom.sequence.model.response.OrderResponseWithOutCustomer;
import java.util.List;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -14,6 +17,28 @@ public OrderResponse getOrderResponse(Order order) {
return new OrderResponse(
order.getId(),
order.getText(),
new CustomerResponse(customer.getId(), customer.getText()));
new CustomerResponseWithOutOrder(customer.getId(), customer.getText()));
}

public Order mapToEntity(OrderRequest orderRequest) {
Order order = new Order();
order.setText(orderRequest.text());
return order;
}

public List<OrderResponse> mapToResponseList(List<Order> orders) {
return orders.stream().map(this::getOrderResponse).toList();
}

public List<OrderResponseWithOutCustomer> mapToResponseListWithOutCustomer(List<Order> orders) {
return orders.stream()
.map(order -> new OrderResponseWithOutCustomer(order.getId(), order.getText()))
.toList();
}

public Order mapToEntityWithCustomer(OrderRequest orderRequest, Customer foundCustomer) {
Order order = mapToEntity(orderRequest);
order.setCustomer(foundCustomer);
return order;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.custom.sequence.model.request;

import jakarta.validation.constraints.NotBlank;
import java.util.List;

public record CustomerRequest(
@NotBlank(message = "Text cannot be empty") String text, List<OrderRequest> orders) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.custom.sequence.model.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;

public record OrderRequest(
@NotEmpty(message = "Text cannot be empty") String text,
@NotBlank(message = "CustomerId cannot be blank") String customerId) {}
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
package com.example.custom.sequence.model.response;

public record CustomerResponse(String id, String text) {}
import java.util.List;

/**
* Response DTO representing customer information with their associated orders.
*
* @param id Customer's unique identifier
* @param text Customer's descriptive text
* @param orderResponses List of associated orders, never null but may be empty
*/
public record CustomerResponse(
String id, String text, List<OrderResponseWithOutCustomer> orderResponses) {
public CustomerResponse {
orderResponses = orderResponses == null ? List.of() : orderResponses;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.custom.sequence.model.response;

public record CustomerResponseWithOutOrder(String id, String text) {}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package com.example.custom.sequence.model.response;

public record OrderResponse(String id, String text, CustomerResponse customer) {}
public record OrderResponse(String id, String text, CustomerResponseWithOutOrder customer) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.custom.sequence.model.response;

/**
* Response model representing an order without its associated customer details. Used in scenarios
* where customer information is not needed in the response.
*
* @param id The unique identifier of the order
* @param orderDescription The description or details of the order
*/
public record OrderResponseWithOutCustomer(String id, String orderDescription) {}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.example.custom.sequence.repositories;

import com.example.custom.sequence.entities.Customer;
import io.hypersistence.utils.spring.repository.BaseJpaRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface CustomerRepository extends JpaRepository<Customer, String> {
public interface CustomerRepository extends BaseJpaRepository<Customer, String> {

@EntityGraph(attributePaths = "orders")
Optional<Customer> findById(String id);
Expand All @@ -26,4 +26,6 @@ public interface CustomerRepository extends JpaRepository<Customer, String> {

@Query(value = "select c.id from Customer c ", countQuery = "select count(c) from Customer c")
Page<String> findAllCustomerIds(Pageable pageable);

void deleteAllInBatch();
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package com.example.custom.sequence.repositories;

import com.example.custom.sequence.entities.Order;
import io.hypersistence.utils.spring.repository.BaseJpaRepository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.ListPagingAndSortingRepository;
import org.springframework.data.repository.query.Param;

public interface OrderRepository extends JpaRepository<Order, String> {
public interface OrderRepository
extends BaseJpaRepository<Order, String>, ListPagingAndSortingRepository<Order, String> {

@Query("select o from Order o join fetch o.customer where o.id = :id")
Optional<Order> findById(@Param("id") String id);

void deleteAllInBatch();

@EntityGraph(attributePaths = "customer")
Optional<Order> findByIdAndCustomer_Id(String id, String customerId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import com.example.custom.sequence.entities.Customer;
import com.example.custom.sequence.mapper.CustomerMapper;
import com.example.custom.sequence.model.request.CustomerRequest;
import com.example.custom.sequence.model.response.CustomerResponse;
import com.example.custom.sequence.model.response.PagedResult;
import com.example.custom.sequence.repositories.CustomerRepository;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand All @@ -16,14 +16,17 @@
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CustomerService {

private final CustomerRepository customerRepository;
private final CustomerMapper customerMapper;

@Transactional(readOnly = true)
public CustomerService(CustomerRepository customerRepository, CustomerMapper customerMapper) {
this.customerRepository = customerRepository;
this.customerMapper = customerMapper;
}

public PagedResult<Customer> findAllCustomers(
int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort =
Expand Down Expand Up @@ -52,12 +55,33 @@ public Optional<CustomerResponse> findCustomerById(String id) {
return customerRepository.findById(id).map(customerMapper::mapToResponse);
}

public CustomerResponse saveCustomer(Customer customer) {
Customer saved = customerRepository.save(customer);
@Transactional
public CustomerResponse saveCustomer(CustomerRequest customerRequest) {
Customer customer = customerMapper.mapToEntity(customerRequest);
Customer saved = customerRepository.persist(customer);
return customerMapper.mapToResponse(saved);
}

@Transactional
public Optional<CustomerResponse> updateCustomerById(
String id, CustomerRequest customerRequest) {
return customerRepository
.findById(id)
.map(
foundCustomer -> {
customerMapper.updateCustomerFromRequest(
customerRequest, foundCustomer);
return customerMapper.mapToResponse(
customerRepository.merge(foundCustomer));
});
}

@Transactional
public void deleteCustomerById(String id) {
customerRepository.deleteById(id);
}

public Optional<Customer> findById(String customerId) {
return customerRepository.findById(customerId);
}
}
Loading

0 comments on commit 6c7af2e

Please sign in to comment.