JpaRepository Query to DTO problem
Учебный проект. Сервис на Spring web + Lombok + Spring data jpa + Postgresql driver + Liquibase migrations. Есть 2 сущности:
Client:
@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "clients")
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "card_number")
private String cardNumber;
@Column(name = "discount_points")
private int discountPoints;
}
Check:
@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "order_checks")
public class Check {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "client_id")
private Long clientId;
@Column(name = "amount")
private BigDecimal amount = BigDecimal.valueOf(0);
}
Безхотростный репозиторий для клиента. Сервисы для клиента и чеков типа:
public interface ClientService {
void addClient(Client client);
Client getById(Long id);
Client getByCardNumber(String cardNumber);
int getDiscountPoints(Long id);
void deleteClient(Long id);
List<Client> getAll();
}
@Service
public class ClientServiceImpl implements ClientService {
@Autowired
private ClientRepository clientRepository;
@Override
public void addClient(Client client) {
clientRepository.save(client);
}
@Override
public Client getById(Long id) {
return clientRepository.getReferenceById(id);
}
public Client getByCardNumber(String cardNumber) {
return clientRepository.getByCardNumber(cardNumber);
}
@Override
public int getDiscountPoints(Long id) {
return clientRepository.getReferenceById(id).getDiscountPoints();
}
@Override
public void deleteClient(Long id) {
clientRepository.deleteById(id);
}
@Override
public List<Client> getAll() {
return clientRepository.findAll();
}
}
База:
databaseChangeLog:
- changeSet:
id: SET-1
author: user
changes:
- createTable:
tableName: clients
columns:
- column:
name: id
type: serial
constraints:
nullable: false
primaryKey: true
- column:
name: card_number
type: varchar(20)
constraints:
nullable: false
- column:
name: discount_points
type: bigint
constraints:
nullable: false
- createTable:
tableName: order_checks
columns:
- column:
name: id
type: serial
constraints:
nullable: false
primaryKey: true
- column:
name: client_id
type: bigint
constraints:
nullable: false
foreignKeyName: fk_client_id
references: clients(id)
- column:
name: amount
type: decimal
constraints:
nullable: false
В целом все запросы, делающие выборку из одной таблицы, отрабатываю корректно. Но появилась необходимость в выборке из двух таблиц. На этом фоне изменился репозиторий чеков:
@Repository
public interface CheckRepository extends JpaRepository<Check, Long> {
List<Check> getAllByClientId(Long clientId);
@Query(value = "SELECT clients.id, order_checks.id, clients.card_number, order_checks.amount"
+ " FROM clients, order_checks "
+ " WHERE clients.id = order_checks.client_id "
+ " AND clients.card_number = :cardNumber "
, nativeQuery = true)
List<ClientAndCheckDTO> getAllByCardNumber(@Param("cardNumber") String cardNumber);
}
Структура DTO:
@Getter
@Setter
@ToString
@AllArgsConstructor
public class ClientAndCheckDTO {
private Long clientId;
private Long checkId;
private String cardNumber;
private BigDecimal amount;
}
Application.properties:
spring.application.name=PAService
server.port=9966
spring.datasource.url=jdbc:postgresql://localhost:5432/PAService
spring.datasource.username=user
spring.datasource.password=1234
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database=postgresql
#spring.jackson.serialization.fail-on-empty-beans=false
spring.liquibase.change-log=classpath:/liquibase/changelog.yml
spring.liquibase.enabled=true
spring.liquibase.url=jdbc:postgresql://localhost:5432/PAService
spring.liquibase.user=user
spring.liquibase.password=1234
CheckRestController:
@RestController
@RequestMapping("/checks")
public class CheckRestController {
@Autowired
private CheckService checkService;
@GetMapping("/cardnumber/{cardNumber}")
public ResponseEntity<List<ClientAndCheckDTO>> getAllByCardNumber(@PathVariable("cardNumber") String cardNumber) {
if(cardNumber == null) {
return null;
}
List<ClientAndCheckDTO> clientAndCheckDTOs = this.checkService.getAllByCardNumber((cardNumber));
if(clientAndCheckDTOs.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(clientAndCheckDTOs, HttpStatus.OK);
}
}
Get запрос в качестве параметра должен принимать String из 20 символов. В Postman отправляю запрос:
localhost:9966/checks/cardnumber/99998888777766665555
Получаю результат:
2024-05-04T22:12:30.270+07:00 ERROR 9157 --- [PAService] [nio-9966-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [ru.fev.accumulation.dto.ClientAndCheckDTO]] with root cause
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [ru.fev.accumulation.dto.ClientAndCheckDTO]
Если запрос:
localhost:9966/checks/cardnumber/"99998888777766665555"
То получаю ответ: 404 Not found. Соответствующие чеки и клиенты в базу заранее добавлены и запросами getAll выводятся корректно. Что делаю не так? Только учусь. Буду благодарен за любую помощь!
Ответы (1 шт):
Решено. https://www.baeldung.com/spring-data-jpa-query. Пункт 4.2. Получилось, что в nativeQuery нельзя делать выборку определенных столбцов.
@Query(value = "SELECT clients.*, order_checks.* "
+ " FROM clients, order_checks "
+ " WHERE clients.id = order_checks.client_id "
+ " AND clients.card_number = ?1 "
, nativeQuery = true)
List<Map<String, Object>> getAllByCardNumber(String cardNumber);