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]
@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
. 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
.
%
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);
}
}
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 ie 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 whatBlaze-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);
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?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.