I need to return a DTO response with the entity's statistics, I can return the statistics using an interface, like this:
Current response:
[
{
"name": "Customer 1",
"id": 1,
"countOrdersPending": 1,
"countOrdersCompleted": 2,
"countOrders": 3,
"totalAmount": 950.0
},
{
"name": "Customer 2",
"id": 2,
"countOrdersPending": 2,
"countOrdersCompleted": 3,
"countOrders": 5,
"totalAmount": 1867.5
}
]
I return statistics attributes, but I need to return a customerStats object like this:
Expected response:
[
{
"id": 1,
"name": "Customer 1",
"customerStats": {
"countOrdersPending": 1,
"countOrdersCompleted": 2,
"countOrders": 3,
"totalAmount": 950.0
}
},
{
"id": 2,
"name": "Customer 2",
"customerStats": {
"countOrdersPending": 2,
"countOrdersCompleted": 3,
"countOrders": 5,
"totalAmount": 1867.5
}
}
]
Can someone give me a tip on how to implement this?
Here are the classes:
Customer Entity
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "customers")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Order Entity
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "orders")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false, foreignKey = @ForeignKey(name = "fk_order_customer1"))
private Customer customer;
private Double amount;
@Enumerated(value = EnumType.STRING)
private OrderStatusType orderStatus;
}
OrderStatusType Enum
public enum OrderStatusType {
PENDING,
COMPLETED
}
CustomerStatsResponse Interface
public interface CustomerStatsResponse {
Long getId();
String getName();
Double getTotalAmount();
Long getCountOrders();
Long getCountOrdersPending();
Long getCountOrdersCompleted();
}
Customer Repository
import com.example.demo.dto.CustomerStatsResponse;
import com.example.demo.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface CustomerRep extends JpaRepository<Customer, Long> {
@Query(value = "SELECT c.id, c.name, "
"SUM(o.amount) AS totalAmount, "
"COUNT(o.id) AS countOrders, "
"(SELECT COUNT(ord.id) FROM orders ord "
"WHERE ord.customer_id = c.id AND ord.order_status = 'PENDING') as countOrdersPending, "
"(SELECT COUNT(ord.id) FROM orders ord "
"WHERE ord.customer_id = c.id AND ord.order_status = 'COMPLETED') as countOrdersCompleted "
"FROM customers c "
"INNER JOIN orders o ON c.id = o.customer_id "
"GROUP BY c.id, c.name"
, nativeQuery = true)
List<CustomerStatsResponse> customerStats();
}
Order Repository
import com.example.demo.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRep extends JpaRepository<Order, Long> {
}
Customer Service
import com.example.demo.dto.CustomerStatsResponse;
import com.example.demo.model.Customer;
import com.example.demo.repository.CustomerRep;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class CustomerService {
private final CustomerRep customerRep;
public List<Customer> findAll() {
return customerRep.findAll();
}
public List<CustomerStatsResponse> customerStats() {
return customerRep.customerStats();
}
}
Order Service
import com.example.demo.model.Order;
import com.example.demo.repository.OrderRep;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRep orderRep;
public List<Order> findAll() {
return orderRep.findAll();
}
}
Customer Controller
import com.example.demo.dto.CustomerStatsResponse;
import com.example.demo.model.Customer;
import com.example.demo.service.CustomerService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("api/v1/customers")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService customerService;
@GetMapping
public ResponseEntity<List<Customer>> findAll() {
return ResponseEntity.ok(customerService.findAll());
}
@GetMapping("/stats")
public ResponseEntity<List<CustomerStatsResponse>> customerStats() {
return ResponseEntity.ok(customerService.customerStats());
}
}
Order Controller
import com.example.demo.model.Order;
import com.example.demo.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("api/v1/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping
public ResponseEntity<List<Order>> findAll() {
return ResponseEntity.ok(orderService.findAll());
}
}
CodePudding user response:
Just create a CustomerStatsResponseDto
that match your wanted JSON and that accepts a CustomerStatsResponse
in its constructor:
public class CustomerStatsResponseDto {
private Long id;
private String name;
private CustomerDto customerStats;
public CustomerStatsResponseDto(CustomerStatsResponse customerStatsResponse) {
this.id = customerStatsResponse.getId();
this.name = customerStatsResponse.getName();
this.customerStats = new CustomerStatsDto(customerStatsResponse.getTotalAmount(), customerStatsResponse.getCountOrders(),
customerStatsResponse.getCountOrdersPending(), customerStatsResponse.getCountOrdersCompleted());
}
}
public class CustomerStatsDto {
private Double totalAmount;
private Long countOrders;
private Long countOrdersPending;
private Long countOrdersCompleted;
public CustomerStatsDto(Double totalAmount, Long countOrders, Long countOrdersPending, Long countOrdersCompleted) {
this.totalAmount = totalAmount;
this.countOrders = countOrders;
this.countOrdersPending = countOrdersPending;
this.countOrdersCompleted = countOrdersCompleted;
}
}
Now in your CustomerController
you just need to convert from CustomerStatsResponse
to CustomerStatsResponseDto
as follows:
@RestController
@RequestMapping("api/v1/customers")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerService customerService;
@GetMapping
public ResponseEntity<List<Customer>> findAll() {
return ResponseEntity.ok(customerService.findAll());
}
@GetMapping("/stats")
public ResponseEntity<List<CustomerStatsResponseDto>> customerStats() {
return ResponseEntity.ok(customerService.customerStats().stream()
.map(customerStatsResponse -> new CustomerStatsResponseDto(customerStatsResponse))
.collect(Collectors.toList()));
}
}