Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat : use vlads BaseJpaRepository and polish #1592

Merged
merged 3 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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,8 +41,18 @@
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;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
Customer customer = (Customer) o;
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 @@
private String id;

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

@ManyToOne(fetch = FetchType.LAZY)
Expand All @@ -40,11 +38,12 @@
private Customer customer;

@Override
public boolean equals(Object o) {

Check warning on line 41 in jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Order.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;
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());
Comment on lines +45 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent potential NullPointerException in equals method

The current implementation could throw NullPointerException if the customer field is null. Additionally, comparing by text and customer ID instead of entity ID is unusual for JPA entities and could lead to unexpected behavior.

Consider this safer implementation:

-        return Objects.equals(text, order.text)
-                && Objects.equals(customer.getId(), order.customer.getId());
+        return Objects.equals(text, order.text)
+                && Objects.equals(id, order.id);

If you really need to compare by text and customer, add null checks:

-        return Objects.equals(text, order.text)
-                && Objects.equals(customer.getId(), order.customer.getId());
+        return Objects.equals(text, order.text)
+                && Objects.equals(customer, order.customer);

Committable suggestion skipped: line range outside the PR's diff.

}

@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) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai, please write integrationTests in CustomerControllerIT to showcase all 3 scenarions as per the code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rajadilipkolli Below is an example of how you might write three integration tests in CustomerControllerIT to cover typical scenarios—creation, update, and validation error. Adjust field names and URLs as needed to match your actual setup:


Java

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import com.example.custom.sequence.model.request.CustomerRequest;
import com.example.custom.sequence.model.response.CustomerResponse;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

@AutoConfigureMockMvc
@transactional
class CustomerControllerIT extends BaseIT {

@Autowired
private MockMvc mockMvc;

private JacksonTester<CustomerRequest> jsonRequest;
private JacksonTester<CustomerResponse> jsonResponse;

@BeforeEach
void setUp() {
    // If needed, initialize test data here
}

@Test
void shouldCreateNewCustomerSuccessfully() throws Exception {
    // Given
    CustomerRequest request = new CustomerRequest(
        "Sample Customer", // text 
        List.of()          // empty order list for simplicity
    );

    // When & Then
    mockMvc.perform(post("/api/customers")
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonRequest.write(request).getJson()))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.id").exists())
        .andExpect(jsonPath("$.text").value("Sample Customer"))
        .andExpect(jsonPath("$.orderResponses").isEmpty());
}

@Test
void shouldUpdateExistingCustomer() throws Exception {
    // First, create a new customer. You can also use a DB seeding approach if you prefer.
    CustomerRequest initialRequest = new CustomerRequest("Initial Name", List.of());
    String createdCustomerResponse = mockMvc.perform(post("/api/customers")
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonRequest.write(initialRequest).getJson()))
        .andExpect(status().isCreated())
        .andReturn()
        .getResponse()
        .getContentAsString();
    CustomerResponse createdCustomer = jsonResponse.parse(createdCustomerResponse).getObject();

    // Given
    CustomerRequest updateRequest = new CustomerRequest("Updated Name", List.of());

    // When & Then
    mockMvc.perform(put("/api/customers/{id}", createdCustomer.id())
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonRequest.write(updateRequest).getJson()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(createdCustomer.id()))
        .andExpect(jsonPath("$.text").value("Updated Name"));
}

@Test
void shouldFailToCreateCustomerWhenTextIsEmpty() throws Exception {
    // Given
    CustomerRequest request = new CustomerRequest(
        "",    // invalid text
        List.of()
    );

    // When & Then
    mockMvc.perform(post("/api/customers")
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonRequest.write(request).getJson()))
        .andExpect(status().isBadRequest())
        .andExpect(jsonPath("$.errors").isNotEmpty()); // Adjust to match your error handling
}

@Test
void shouldDeleteCustomer() throws Exception {
    // First, create a new customer
    CustomerRequest request = new CustomerRequest("Somebody to Delete", List.of());
    String createdCustomerResponse = mockMvc.perform(post("/api/customers")
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonRequest.write(request).getJson()))
        .andExpect(status().isCreated())
        .andReturn()
        .getResponse()
        .getContentAsString();
    CustomerResponse createdCustomer = jsonResponse.parse(createdCustomerResponse).getObject();

    // When & Then
    mockMvc.perform(delete("/api/customers/{id}", createdCustomer.id()))
        .andExpect(status().isNoContent());

    // Verify it's gone
    mockMvc.perform(get("/api/customers/{id}", createdCustomer.id()))
        .andExpect(status().isNotFound());
}

}


✏️ Learnings added
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1592
File: jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/CustomerMapper.java:51-51
Timestamp: 2024-12-22T12:22:30.495Z
Learning: Integration tests for CustomerController can showcase creation, update, and validation scenarios using "CustomerRequest" and verifying JSON fields in the response.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai, In above examples include various orderRequests like (newer one, updated one, delete ones )as of now all are empty

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rajadilipkolli Below is an illustrative example in CustomerControllerIT showing how you might handle various order requests (new, updated, and deleted) within a single update scenario. Feel free to adapt field names, request body details, and endpoints as needed to align with your code:


Java

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
;

import com.example.custom.sequence.model.request.CustomerRequest;
import com.example.custom.sequence.model.request.OrderRequest;
import com.example.custom.sequence.model.response.CustomerResponse;
import com.example.custom.sequence.model.response.OrderResponseWithOutCustomer;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

@AutoConfigureMockMvc
@transactional
class CustomerControllerIT extends BaseIT {

@Autowired
private MockMvc mockMvc;

private JacksonTester<CustomerRequest> jsonCustomerRequest;
private JacksonTester<OrderRequest> jsonOrderRequest;
private JacksonTester<CustomerResponse> jsonCustomerResponse;

@BeforeEach
void setUp() {
    // Initialize JacksonTesters if needed, for example:
    // JacksonTester.initFields(this, new ObjectMapper());
}

@Test
void shouldHandleMultipleOrdersInUpdateScenario() throws Exception {
    // 1) Create a new Customer with two initial orders
    CustomerRequest createRequest = new CustomerRequest(
        "Original Customer",
        List.of(
            new OrderRequest("Order_One", "dummyCustomerId"), 
            new OrderRequest("Order_Two", "dummyCustomerId")
        )
    );

    String createResponse = mockMvc.perform(post("/api/customers")
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonCustomerRequest.write(createRequest).getJson()))
        .andExpect(status().isCreated())
        .andReturn()
        .getResponse()
        .getContentAsString();

    CustomerResponse createdCustomer = jsonCustomerResponse.parse(createResponse).getObject();
    String createdCustomerId = createdCustomer.id();
    assertThat(createdCustomerId).isNotNull();

    // 2) Prepare an update with:
    //    - One of the original orders changed (Order_One -> "Order_One_Updated")
    //    - A new order added ("Order_Three_New")
    //    - The other original order ("Order_Two") removed from the list
    CustomerRequest updateRequest = new CustomerRequest(
        "Updated Customer Name",
        List.of(
            // updated text for the order that previously had "Order_One"
            new OrderRequest("Order_One_Updated", "dummyCustomerId"),
            // brand-new order
            new OrderRequest("Order_Three_New", "dummyCustomerId")
            // "Order_Two" is omitted here, so it should be removed
        )
    );

    String updatedResponse = mockMvc.perform(put("/api/customers/{id}", createdCustomerId)
            .contentType(MediaType.APPLICATION_JSON)
            .content(jsonCustomerRequest.write(updateRequest).getJson()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(createdCustomerId))
        .andExpect(jsonPath("$.text").value("Updated Customer Name"))
        .andReturn()
        .getResponse()
        .getContentAsString();

    CustomerResponse updatedCustomer = jsonCustomerResponse.parse(updatedResponse).getObject();
    assertThat(updatedCustomer.orderResponses()).hasSize(2); 
    // Should contain only the updated and the new order now

    // 3) Verify that the orders have the correct text values
    //    and that the removed order doesn't appear
    List<OrderResponseWithOutCustomer> updatedOrders = updatedCustomer.orderResponses();
    assertThat(updatedOrders).extracting("orderDescription")
        .containsExactlyInAnyOrder("Order_One_Updated", "Order_Three_New");
}

}


✏️ Learnings added
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1592
File: jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/CustomerMapper.java:51-51
Timestamp: 2024-12-22T12:38:14.323Z
Learning: Integration tests in CustomerControllerIT can showcase handling multiple OrderRequests (new, updated, removed) during customer creation or update scenarios.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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;
Comment on lines +23 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation for robustness

The mapping methods are well-structured and follow good practices, but could benefit from additional validation:

Consider adding these safety checks:

 public Order mapToEntity(OrderRequest orderRequest) {
+    if (orderRequest == null) {
+        throw new IllegalArgumentException("OrderRequest cannot be null");
+    }
+    if (orderRequest.text() == null || orderRequest.text().trim().isEmpty()) {
+        throw new IllegalArgumentException("Order text cannot be null or empty");
+    }
     Order order = new Order();
     order.setText(orderRequest.text());
     return order;
 }

 public List<OrderResponse> mapToResponseList(List<Order> orders) {
+    if (orders == null) {
+        throw new IllegalArgumentException("Orders list cannot be null");
+    }
     return orders.stream().map(this::getOrderResponse).toList();
 }

 public Order mapToEntityWithCustomer(OrderRequest orderRequest, Customer foundCustomer) {
+    if (foundCustomer == null) {
+        throw new IllegalArgumentException("Customer cannot be null");
+    }
     Order order = mapToEntity(orderRequest);
     order.setCustomer(foundCustomer);
     return order;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
public Order mapToEntity(OrderRequest orderRequest) {
if (orderRequest == null) {
throw new IllegalArgumentException("OrderRequest cannot be null");
}
if (orderRequest.text() == null || orderRequest.text().trim().isEmpty()) {
throw new IllegalArgumentException("Order text cannot be null or empty");
}
Order order = new Order();
order.setText(orderRequest.text());
return order;
}
public List<OrderResponse> mapToResponseList(List<Order> orders) {
if (orders == null) {
throw new IllegalArgumentException("Orders list cannot be null");
}
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) {
if (foundCustomer == null) {
throw new IllegalArgumentException("Customer cannot be null");
}
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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Exercise caution with deleteAllInBatch operation - potential data integrity risk

The deleteAllInBatch() method in OrderRepository is unsafe to use because:

  • Orders are part of a bidirectional relationship with Customer entity
  • Customer has @OneToMany relationship with cascade = CascadeType.ALL and orphanRemoval = true
  • Using deleteAllInBatch() bypasses these JPA cascade operations and orphan removal, which could lead to:
    • Orphaned references in the Customer.orders collection
    • Inconsistent state between Customer and Order entities
    • Potential constraint violations

Instead, use the standard deleteAll() method which respects JPA cascade operations and entity lifecycle events.

🔗 Analysis chain

Exercise caution with deleteAllInBatch operation

The deleteAllInBatch() method performs a bulk delete operation which:

  1. Bypasses entity callbacks and cascades
  2. Could violate referential integrity if not used carefully

Let me gather more information about potential foreign key relationships by checking the Order entity and related entities.


Let me check one more specific detail about the Order entity and its relationships.


Let me check one final detail about the Customer entity to understand the complete relationship structure.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for foreign key relationships to Order entity
ast-grep --pattern 'class $_ {
  $$$
  @ManyToOne
  $$$
  Order $_;
  $$$
}'

# Check for usage of deleteAllInBatch to ensure it's used in appropriate contexts
rg "deleteAllInBatch" --type java

Length of output: 3869


Script:

#!/bin/bash
# Check Order entity definition
rg -A 10 "class Order" --type java

# Check for @OneToMany relationships that might reference Order
ast-grep --pattern 'class $_ {
  $$$
  @OneToMany
  $$$
  $_ Order
  $$$
}'

# Check for any JPA cascade configurations related to Order
rg "@OneToMany.*Order|@ManyToOne.*Order" --type java

Length of output: 34985


Script:

#!/bin/bash
# Check the Order entity in the custom sequence project for its relationships
rg -A 15 "class Order" jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Order.java

# Check if there are any entities with relationships to Order
rg "@ManyToOne.*Order|@OneToMany.*Order" jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/

Length of output: 643


Script:

#!/bin/bash
# Check the Customer entity for any relationships with Order
rg -A 15 "class Customer" jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Customer.java

# Check for any cascade configurations in the Customer-Order relationship
rg "cascade.*ALL" jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/

Length of output: 877


@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
Loading