英文:
Returning List<InvoiceDto> from SqlResultSetMapping in Spring Boot using native query?
问题
这是您提供的内容的翻译:
因此,我想从实体表中提取DTO
列表,而无需首先获取所有满足某些条件的实体,然后将实体转换为DTO
。以下是我目前的代码。
具有以下SQL映射
的Invoice
实体:
@Entity
@SqlResultSetMappings({
@SqlResultSetMapping(name = "findInvoicesDtoMapping",
classes = {@ConstructorResult(targetClass=com.path.to.dto.invoice.InvoiceDto.class,
columns = {@ColumnResult(name="invoiceId", type=Integer.class),
@ColumnResult(name="projectNumber", type=String.class),
})} ),
})
@NamedNativeQueries(value = {
@NamedNativeQuery(name = "findInvoicesDto", query = ""
+"SELECT invoice.invoice_id AS invoiceId, invoice.project_number AS projectNumber "
+"FROM Invoiceinvoice "
+"WHERE invoice_tour.paid = :paid OR invoice_tour.invoice_number LIKE % :invoiceNumber",
resultSetMapping = "findInvoicesTourMapping"),
})
@Table(name = "invoice_tour")
public class InvoiceTour {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "invoice_id")
private int invoiceId;
@Column(name = "date_of_finishing", unique = false)
private String dateOfFinishing;
@Column(name = "account_number", unique = false)
private String accountNumber;
@Column(name = "project_number", unique = false)
private String projectNumber;
@Column(name = "invoice_number", unique = false)
private String invoiceNumber;
@Column(name = "paid", unique = false)
private boolean paid;
//omitted getters & setters
}
这是DTO
类:
public class InvoiceDto {
private String projectNumber;
private int invoiceId;
public InvoiceDto(int invoiceId, String projectNumber){
this.invoiceId = invoiceId;
this.projectNumber = projectNumber;
}
//omitted getters & setters
}
我有InvoiceTour
实体的三个层次:Controller
,Service
和Repository
。我将只写调用服务层的方法,然后服务层调用存储库层,最后存储库层方法引用@NamedNativeQuery
。
public interface InvoiceTourService {
List<InvoiceDto> findInvoices(String invoiceNumber, boolean paid);
}
InvoiceTour
的Service
:
@Service
@Validated
public class InvoiceTourServiceImpl implements InvoiceTourService{
private final InvoiceTourRepository repository;
@Inject
public InvoiceTourServiceImpl(final InvoiceTourRepository repository) {
this.repository = repository;
}
@Override
public List<InvoiceDto> findInvoices(String invoiceNumber, boolean paid) {
return repository.findInvoices(invoiceNumber, paid);
}
}
InvoiceTour
的Repository
:
@Repository
public interface InvoiceTourRepository extends JpaRepository<InvoiceTour, Integer>{
@Query(name = "findInvoicesDto", nativeQuery = true)
List<InvoiceDto> findInvoices(@Param("invoiceNumber") String invoiceNumber, @Param("paid") boolean paid);
}
这产生了以下异常:
无法提取ResultSet;SQL [n/a];嵌套异常是org.hibernate.exception.SQLGrammarException:无法提取ResultSet
是否有一种方式可以使用@SqlResultSetMappings
提取自定义DTO
的列表,并直接形成DTO
集合?
更新1
好的,我测试了另外两种情况:
情况1:
在WHERE
子句中,我在%
运算符和:invoiceNumber
之间放置了一个空格,如下所示:
LIKE % :invoiceNumber %
在这种情况下,我得到了:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'invoice_tour.invoice_number LIKE % 'a' % OR invoice_tour.company_id = 2' at line 1
情况2:
在WHERE
子句中,我在%
运算符和:invoiceNumber
之间没有放置空格,如下所示:
LIKE %:invoiceNumber%
在这种情况下,我得到了:
org.springframework.dao.InvalidDataAccessApiUsageException: Unknown parameter name : invoiceNumber; nested exception is java.lang.IllegalArgumentException: Unknown parameter name : invoiceNumber
更新2
好的,我又测试了一次,完全使用了更新1之前的代码。
在这种情况下,在WHERE子句中的代码如下:
LIKE % :invoiceNumber %
我还在控制台上生成了SQL语句,这是我得到的:
SELECT invoice_tour.invoice_id AS invoiceId, invoice_tour.project_number AS projectNumber
FROM Invoice_tour invoice_tour WHERE invoice_tour.paid = ? OR invoice_tour.invoice_number LIKE % ? %
我得到了异常:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
希望这些翻译能对您有所帮助。
英文:
So, I want to extract list of DTO
s from entity table, without having to first get all entities (which are satisfying some condition) and then to make DTO
s out of entities. Here is what I have now.
Invoice
entity with following sql mappings
:
@Entity
@SqlResultSetMappings({
@SqlResultSetMapping(name = "findInvoicesDtoMapping",
classes = {@ConstructorResult(targetClass=com.path.to.dto.invoice.InvoiceDto.class,
columns = {@ColumnResult(name="invoiceId", type=Integer.class),
@ColumnResult(name="projectNumber", type=String.class),
})} ),
})
@NamedNativeQueries(value = {
@NamedNativeQuery(name = "findInvoicesDto", query = ""
+"SELECT invoice.invoice_id AS invoiceId, invoice.project_number AS projectNumber "
+"FROM Invoiceinvoice "
+"WHERE invoice_tour.paid = :paid OR invoice_tour.invoice_number LIKE % :invoiceNumber",
resultSetMapping = "findInvoicesTourMapping"),
})
@Table(name = "invoice_tour")
public class InvoiceTour {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "invoice_id")
private int invoiceId;
@Column(name = "date_of_finishing", unique = false)
private String dateOfFinishing;
@Column(name = "account_number", unique = false)
private String accountNumber;
@Column(name = "project_number", unique = false)
private String projectNumber;
@Column(name = "invoice_number", unique = false)
private String invoiceNumber;
@Column(name = "paid", unique = false)
private boolean paid;
//omitted gettes&setters
}
Here is DTO
class:
public class InvoiceDto {
private String projectNumber;
private int invoiceId;
public(int invoiceId, String projectNumber){
this.invoiceId = invoiceId;
this.projectNumber = projectNumber;
}
//omitted gettes&setters
}
I have three layers for my InvoiceTour
entity: Contoller
, Service
and Repository
. I will not write entire controller layer just the method which is calling the service layer, which in turns calls repository layer and finally repository layer method which refers to @NamedNativeQuery
.
public interface InvoiceTourService {
List<InvoiceDto> findInvoices(String invoiceNumber, boolean paid);
}
Service
for InvoiceTour
:
@Service
@Validated
public class InvoiceTourServiceImpl implements InvoiceTourService{
private final InvoiceTourRepository repository;
@Inject
public InvoiceTourServiceImpl(final InvoiceTourRepository repository) {
this.repository = repository;
}
@Override
public List<InvoiceDto> findInvoices(String invoiceNumber, boolean paid) {
return repository.findInvoices(invoiceNumber, paid);
}
}
Repository
for InvoiceTour
:
@Repository
public interface InvoiceTourRepository extends JpaRepository<InvoiceTour, Integer>{
@Query(name = "findInvoicesDto", nativeQuery = true)
List<InvoiceDto> findInvoices(@Param("invoiceNumber") String invoiceNumber, @Param("paid") boolean paid);
}
This produces following exception:
could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Is there a way to extract list of DTO
custom POJO
s using @SqlResultSetMappings
and directly form collection of DTO
s?
UPDATED 1
Okey, I tested two more situations:
Case 1:
In WHERE
clause, I placed one space between %
operators and :invoiceNumber
, like so:
LIKE % :invoiceNumber %
In that case I got:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'invoice_tour.invoice_number LIKE % 'a' % OR invoice_tour.company_id = 2' at line 1
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_261]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_261]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_261]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_261]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.Util.getInstance(Util.java:408) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:944) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3976) ~[mysql-connector-java-5.1.46.jar:5.1.46]
Case 2:
In WHERE
clause, I placed no space between %
operators and :invoiceNumber
, like so:
LIKE %:invoiceNumber%
In that case I got:
org.springframework.dao.InvalidDataAccessApiUsageException: Unknown parameter name : invoiceNumber; nested exception is java.lang.IllegalArgumentException: Unknown parameter name : invoiceNumber
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:367) ~[spring-orm-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227) ~[spring-orm-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527) ~[spring-orm-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.dao.support.DataAccessUtils.translateI
fNecessary(DataAccessUtils.java:242) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
UPDATED 2
Okey, I tested it one more time, exactly with code that is before UPDATE 1.
In this case, in WHERE clause code goes like this:
LIKE % :invoiceNumber %
I also turn od sql statment generation in the console and this is what I have:
SELECT invoice_tour.invoice_id AS invoiceId, invoice_tour.project_number AS projectNumber
FROM Invoice_tour invoice_tour WHERE invoice_tour.paid = ? OR invoice_tour.invoice_number LIKE % ? %
Exception I got:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:242) ~[spring-orm-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225) ~[spring-orm-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527) ~[spring-orm-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
答案1
得分: 1
Sure, here's the translated content:
- 尝试以以下方式更正您的命名查询:
@NamedNativeQuery(
name = "findInvoicesDto",
query =
"SELECT " +
" invoice.invoice_id AS invoiceId, " +
" invoice.project_number AS projectNumber " +
"FROM invoice_tour " +
"WHERE invoice_tour.paid = :paid OR invoice_tour.invoice_number LIKE :invoiceNumber",
resultSetMapping = "findInvoicesTourMapping"
)
- 检查您的
@SqlResultSetMapping
。应该像这样:
@SqlResultSetMapping(name = "findInvoicesTourMapping",
classes = @ConstructorResult(
targetClass=com.path.to.dto.invoice.InvoiceDto.class,
columns = {@ColumnResult(name="invoiceId"),
@ColumnResult(name="projectNumber"),
}
)
)
请注意 @SqlResultSetMapping.name
应等于 @NamedNativeQuery.resultSetMapping
。
- 您应该像这样传递
%
:
@Service
@Validated
public class InvoiceTourServiceImpl implements InvoiceTourService{
private final InvoiceTourRepository repository;
// ...
@Override
public List<InvoiceDto> findInvoices(String invoiceNumber, boolean paid) {
return repository.findInvoices("%" + invoiceNumber + "%", paid);
}
}
英文:
- Try to correct your named query in the following way:
@NamedNativeQuery(
name = "findInvoicesDto",
query =
"SELECT " +
" invoice.invoice_id AS invoiceId, " +
" invoice.project_number AS projectNumber " +
"FROM invoice_tour " +
"WHERE invoice_tour.paid = :paid OR invoice_tour.invoice_number LIKE :invoiceNumber",
resultSetMapping = "findInvoicesTourMapping"
)
- Check your
@SqlResultSetMapping
. It should be like this:
@SqlResultSetMapping(name = "findInvoicesTourMapping",
classes = @ConstructorResult(
targetClass=com.path.to.dto.invoice.InvoiceDto.class,
columns = {@ColumnResult(name="invoiceId"),
@ColumnResult(name="projectNumber"),
}
)
)
Pay attention that @SqlResultSetMapping.name
equal to @NamedNativeQuery.resultSetMapping
.
- You should pass
%
like this:
@Service
@Validated
public class InvoiceTourServiceImpl implements InvoiceTourService{
private final InvoiceTourRepository repository;
// ...
@Override
public List<InvoiceDto> findInvoices(String invoiceNumber, boolean paid) {
return repository.findInvoices("%" + invoiceNumber + "%", paid);
}
}
答案2
得分: 1
为什么在这里直接使用SQL?JPQL/HQL 完全可以表示这种类型的查询。通过避免使用SQL,您可以从 Spring Data JPA 提供的一些抽象中受益,即您可以移除实体上所有的命名本地查询注释,改为在存储库上使用以下内容:
@Query("FROM InvoiceTour t WHERE t.paid = :paid OR t.invoiceNumber LIKE '%' || :invoiceNumber")
List<InvoiceDto> findInvoices(@Param("invoiceNumber") String invoiceNumber, @Param("paid") boolean paid);
您可能还会喜欢 Blaze-Persistence Entity Views 提供的功能。
我创建了这个库,以便在 JPA 模型和自定义接口或抽象类定义的模型之间实现简单映射,类似于功能强化版的 Spring Data Projections。思路是您按照所需定义目标结构(领域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。
对于您的用例,使用 Blaze-Persistence Entity-Views 的 DTO 模型可能如下所示:
@EntityView(InvoiceTour.class)
public interface InvoiceDto {
@IdMapping
Integer getInvoiceId();
String getProjectNumber();
}
查询只需将实体视图应用于查询,最简单的方法只是根据 id 进行查询。
InvoiceDto a = entityViewManager.find(entityManager, InvoiceDto.class, id);
Spring Data 集成使您几乎可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
您可以使用 Spring Data 命名约定构建查询,大致如下所示:
List<InvoiceDto> findByInvoiceNumberOrPaid(@Param("invoiceNumber") String invoiceNumber, @Param("paid") boolean paid);
英文:
Why are you using SQL directly here? JPQL/HQL is perfectly capable to represent this kind of query. By avoiding the use of SQL, you can benefit from some of the abstractions Spring Data JPA provides i.e. you could remove all the named native query annotations on the entity and instead use the following on the repository:
@Query("FROM InvoiceTour t WHERE t.paid = :paid OR t.invoiceNumber LIKE '%' || :invoiceNumber")
List<InvoiceDto> findInvoices(@Param("invoiceNumber") String invoiceNumber, @Param("paid") boolean paid);
You might also like what Blaze-Persistence Entity Views has to offer.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
@EntityView(InvoiceTour.class)
public interface InvoiceDto {
@IdMapping
Integer getInvoiceId();
String getProjectNumber();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
InvoiceDto a = entityViewManager.find(entityManager, InvoiceDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You could use the Spring Data naming convention to construct the query which would look roughly like this:
List<InvoiceDto> findByInvoiceNumberOrPaid(@Param("invoiceNumber") String invoiceNumber, @Param("paid") boolean paid);
答案3
得分: 0
因此,总结一下。我能够得到期望的结果,要归功于SternK的回答和文档。因此,以下两种方法给了我正确的结果:
方法1:
List<InvoiceDto> invoices = em.createNamedQuery("findInvoicesDto" )
.setParameter("invoiceNumber", "%" + invoiceNumber + "%")
.setParameter("paid", paid)
.getResultList();
字符串findInvoicesDto
必须与@SqlResultSetMapping.name
匹配。
方法2:
就像在SternK的回答中一样。
注意:我没有预料到Hibernate中会有带有LIKE
和%
运算符的这种语法。这让我想起了在学校上学期时我在PreparedStatements
和JDBC
的方式中编写SQL查询的方式 也许这是一个愚蠢的问题,但在某种程度上这难道不容易受到SQL注入的影响吗?
英文:
So, to summarize. I was able to get desired result thanks to SternK's answer and documentation. So the two following approaches gave me correct result:
Approach 1:
List<InvoiceDto> invoices = em.createNamedQuery("findInvoicesDto" )
.setParameter("invoiceNumber", "%" + invoiceNumber + "%")
.setParameter("paid", paid)
.getResultList();
The string findInvoicesDto
has to match @SqlResultSetMapping.name
.
Approach 2:
Is like in SternK's answer.
Note: I didn't expect this syntax with LIKE
and %
operator in Hibernate. This reminds me of PreparedStatements
and JDBC
way of writing SQL queries, which I did in previous semester at school Maybe this is stupid quesion, but isn't this prone to SQL Injection to some extent?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论