如何为枚举编写 DTO 投影查询并将其映射到 springboot JPA 中的字符串

问题描述 投票:0回答:1

我有一个带有枚举的实体类,如下所示

package com.expensetracker.api.domain;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Entity
@Table(name = "transaction")
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ExpenseTransaction {

public enum TransactionType {
    INCOME,
    EXPENSE,
    INVESTMENT
}

public enum Category {
    FOOD,
    RENT,
    TRANSPORTATION,
    ENTERTAINMENT,
    GROCERIES,
    SHOPPING,
    MUTUAL_FUNDS,
    STOCKS,
    OTHER_INVESTMENTS,
    LOAN,
    SALARY,
    OTHER
}

@Id
@SequenceGenerator(name = "ex_trn_seq_gen",sequenceName = "ex_trn_seq")
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "ex_trn_seq_gen")
private Long id;

@Column(nullable = false)
private BigDecimal amount;

@Column(name = "transaction_date",nullable = false)
private LocalDate transactionDate;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TransactionType type;

@Enumerated(EnumType.STRING)
private Category category;

@Column(nullable = false)
private String description;

@CreatedDate
@Column(nullable = false)
private LocalDateTime createdAt;

}

我的DTO课程如下

package com.expensetracker.api.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ExpenseTransactionDTO {
    private Long id;
    private BigDecimal amount;
    private LocalDate transactionDate;
    private ExpenseTransaction.TransactionType type;
    private ExpenseTransaction.Category category;
    private String description;
    private LocalDateTime createdAt;
}

我正在尝试在 Spring JPARepository 中使用简单的 JPA 查询编写 DTO 投影选择查询,如下所示:

@Query("SELECT new com.expensetracker.api.domain.ExpenseTransactionDTO(e.id,e.amount,e.transactionDate,e.type,e.category,e.description,e.createdAt) from ExpenseTransaction e")
Page<ExpenseTransactionDTO> findExpenseTransactions(Pageable pageable);

当我运行此方法时,出现以下异常

2024-03-24T22:43:03.382+05:30 ERROR 25232 --- [expensetracker-api] [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessApiUsageException: Cannot instantiate class 'com.expensetracker.api.domain.ExpenseTransactionDTO' (it has no constructor with signature [java.lang.Long, java.math.BigDecimal, java.time.LocalDate, com.expensetracker.api.domain.ExpenseTransaction$TransactionType, com.expensetracker.api.domain.ExpenseTransaction$Category, java.lang.String, java.time.LocalDateTime], and not every argument has an alias)] with root cause

java.lang.IllegalStateException: Cannot instantiate class 'com.expensetracker.api.domain.ExpenseTransactionDTO' (it has no constructor with signature [java.lang.Long, java.math.BigDecimal, java.time.LocalDate, com.expensetracker.api.domain.ExpenseTransaction$TransactionType, com.expensetracker.api.domain.ExpenseTransaction$Category, java.lang.String, java.time.LocalDateTime], and not every argument has an alias)
at org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiationResultImpl.resolveAssembler(DynamicInstantiationResultImpl.java:201) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiationResultImpl.createResultAssembler(DynamicInstantiationResultImpl.java:107) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.results.jdbc.internal.StandardJdbcValuesMapping.resolveAssemblers(StandardJdbcValuesMapping.java:53) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.results.internal.ResultsHelper.createRowReader(ResultsHelper.java:77) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.results.internal.ResultsHelper.createRowReader(ResultsHelper.java:62) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:188) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:83) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:76) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:65) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$2(ConcreteSqmSelectQueryPlan.java:137) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:362) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:303) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:509) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:427) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.hibernate.query.Query.getResultList(Query.java:120) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.doExecute(JpaQueryExecution.java:204) ~[spring-data-jpa-3.2.4.jar:3.2.4]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:92) ~[spring-data-jpa-3.2.4.jar:3.2.4]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:149) ~[spring-data-jpa-3.2.4.jar:3.2.4]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:137) ~[spring-data-jpa-3.2.4.jar:3.2.4]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.2.4.jar:3.2.4]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.2.4.jar:3.2.4]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164) ~[spring-data-commons-3.2.4.jar:3.2.4]

这是因为我的 DTO 类已将枚举属性定义为字符串数据类型而不是枚举。我想在我的 DTO 中使用 String。我应该如何编写 JPA 查询,以便它映射到字符串而不是枚举?

spring-boot hibernate spring-data-jpa
1个回答
0
投票
  • 虽然这不是首选,但是可以使用nativeQuery来解决问题。
    • 但是,您无法直接通过DTO进行搜索。
  • 我将在下面展示代码。
@Repository
public interface ExpenseTransactionRepository extends JpaRepository<ExpenseTransaction, Long> {
    @Query(nativeQuery = true,
            value = "SELECT t.id, t.amount, t.transaction_date, t.type, t.category, t.description, t.created_at FROM transaction AS t")
    List<TestInterface> findExpenseTransactionsNative();
}
public interface TestInterface {
    Long getId();

    BigDecimal getAmount();

    LocalDate getTransactionDate();

    String getType();

    String getCategory();

    String getDescription();

    LocalDateTime getCreatedAt();
}
@SpringBootTest
public class QueryTest {

    @Autowired
    ExpenseTransactionRepository expenseTransactionRepository;

    @Test
    void test() {
        ExpenseTransaction example = new ExpenseTransaction(1L, BigDecimal.ZERO, LocalDate.now(),
                ExpenseTransaction.TransactionType.EXPENSE, ExpenseTransaction.Category.RENT, "example",
                LocalDateTime.now());
        expenseTransactionRepository.save(example);

        List<TestInterface> expenseTransactionsNative = expenseTransactionRepository.findExpenseTransactionsNative();
        TestInterface first = expenseTransactionsNative.getFirst();
        
        Assertions.assertThat(first.getType()).isEqualTo("EXPENSE");
    }
}
  • 代码结果经过测试代码验证。
    • 但是需要调用接口方法并将结果转换为DTO。
  • 我认为查询 DTO 并在应用程序内存中转换响应更干净,如下所示。
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ExpenseTransactionDTO {
    private Long id;
    private BigDecimal amount;
    private LocalDate transactionDate;
    private TransactionType type;
    private Category category;
    private String description;
    private LocalDateTime createdAt;
}
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ExpenseTransactionResponse {
    private Long id;
    private BigDecimal amount;
    private LocalDate transactionDate;
    private String type;
    private String category;
    private String description;
    private LocalDateTime createdAt;

    public static ExpenseTransactionResponse from(ExpenseTransactionDTO dto) {
        return new ExpenseTransactionResponse(
                dto.getId(),
                dto.getAmount(),
                dto.getTransactionDate(),
                dto.getType().name(),
                dto.getCategory().name(),
                dto.getDescription(),
                dto.getCreatedAt()
        );
    }
}
@Repository
public interface ExpenseTransactionRepository extends JpaRepository<ExpenseTransaction, Long> {
    @Query("SELECT new com.example.demo.dto.ExpenseTransactionDTO(e.id,e.amount,e.transactionDate,e.type,e.category,e.description,e.createdAt) "
            + "from ExpenseTransaction e")
    Page<ExpenseTransactionDTO> findExpenseTransactions(Pageable pageable);
}
@SpringBootTest
public class QueryTest {

    @Autowired
    ExpenseTransactionRepository expenseTransactionRepository;

    @Test
    void test() {
        ExpenseTransaction example = new ExpenseTransaction(1L, BigDecimal.ZERO, LocalDate.now(),
                ExpenseTransaction.TransactionType.EXPENSE, ExpenseTransaction.Category.RENT, "example",
                LocalDateTime.now());
        expenseTransactionRepository.save(example);

        PageRequest pageRequest = PageRequest.of(0, 5);
        Page<ExpenseTransactionDTO> expenseTransactions = expenseTransactionRepository.findExpenseTransactions(
                pageRequest);

        List<ExpenseTransactionResponse> collect = expenseTransactions.getContent()
                .stream()
                .map(ExpenseTransactionResponse::from)
                .toList();

        Assertions.assertThat(collect.getFirst().getType()).isEqualTo("EXPENSE");
    }
}

  • 除非是一个性能非常重要的系统而不是使用nativeQuery,否则使用JPA和从内存转换映射到想要的结果似乎更好。
    • 或者,您可以使用 Querydsl。

希望对您有所帮助。谢谢你:)

© www.soinside.com 2019 - 2024. All rights reserved.