I get the error "Cannot create TypedQuery for query with more than one return using requested result type"
for the following query using JPA on Glassfish, any ideas what is wrong here? I want to get the latest debit record with a certain debit status.
entityManager.createQuery("select dd, MAX(dd.createdMillis) from T_DEBIT dd" +
" where dd.debitStatus in (:debitStatus)" +
" and dd.account = :account" , Debit.class)
.setParameter("debitStatus", false)
.setParameter("account", account)
.getSingleResult();
A generic parameter is normally specified for a TypedQuery. If you declared a TypedQuery you would use an Object[] as the generic parameter for the TypedQuery, since you are projecting columns and not returning a complete entity.
However, since you have not declared a TypedQuery (your using a concise coding style), you need to change Debit.class to Object[].class since your not selecting an object, but instead only two fields.
Object[] result = entityManager.createQuery("select dd, MAX(dd.createdMillis) from T_DEBIT dd" +
" where dd.debitStatus in (:debitStatus)" +
" and dd.account = :account" , Object[].class) //Notice change
.setParameter("debitStatus", false)
.setParameter("account", account)
.getSingleResult();
Executing this query will return a Object[] where each index in the Object[] corresponds with a field in your select statement. For example:
result[0] = dd
result[1] = max(dd.createdMillis)
To avoid using the Object[] you could create a new class to retrieve these values in a more strongly typed fashion. Something like:
public class Result {
String dd;
Date createdMillis;
public Result(String dd, Date createdMillis) {
super();
this.dd = dd;
this.createdMillis = createdMillis;
}
public String getDd() {
return dd;
}
public void setDd(String dd) {
this.dd = dd;
}
public Date getCreatedMillis() {
return createdMillis;
}
public void setCreatedMillis(Date createdMillis) {
this.createdMillis = createdMillis;
}
}
Then in your JPQL statement you could call the constructor:
Result result = entityManager.createQuery("select NEW fully.qualified.Result(dd, MAX(dd.createdMillis)) from T_DEBIT dd" +
" where dd.debitStatus in (:debitStatus)" +
" and dd.account = :account" , Result.class)
.setParameter("debitStatus", false)
.setParameter("account", account)
.getSingleResult();
Recently, I have blogged about this exact topic. I encourage you to view this video tutorial I created: https://tothought.cloudfoundry.com/post/16
Related
I want to create a Search specification where I can select data based on Date range. I tried this:
#Getter
#Setter
public class BillingSummarySearchParams {
private LocalDateTime startDate;
private LocalDateTime endDate;
}
Search Specification
public List<BillingSummaryFullDTO> findBillingInvoicesSummary(BillingSummarySearchParams params)
{
Specification<BillingSummary> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (params.getStartDate() != null | params.getEndDate() != null) {
predicates.add(cb.like(cb.lower(root.get("startDate")), "%" + params.getStartDate() + "%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
return billingSummaryService.findAll(spec).stream().map(billingSummaryMapper::toFullDTO).collect(Collectors.toList());
}
Search SQL
public List<BillingSummary> findAll(Specification<BillingSummary> spec)
{
String hql = "select e from " + BillingSummary.class.getName() + " e where e.createdAt BETWEEN :startDate AND :endDate ORDER BY e.createdAt DESC";
TypedQuery<BillingSummary> query = entityManager.createQuery(hql, BillingSummary.class).setParameter("startDate", spec).setParameter("endDate", spec);
List<BillingSummary> list = query.getResultList();
return list;
}
It's not clear to me how I can build the specification with 2 dates to work and with only one.
What is the proper way to get the dates from the spec object?
I think you are missunderstanding what Specifications are used for. They aren't meant to be used in combination with "old" typed queries. If you set a Specification as parameter for them it should result in errors because the jpa provider has no idea how to work with that object.
Instead they are implemented to have an easier time to create and use criteria queries in spring data. The first step to make them work would be to implement JpaSpecificationExecutor in your repository. The interface contains methods like findAll(Specification<T> spec) and spring will automatically create these methods for your repository. Spring generates the criteria query in the background and adds the predicate you create in your specification to it.
#Repository
public interface BillingSummaryRepository extends JpaRepository<BillingSummary, Long>, JpaSpecificationExecutor<BillingSummary> {
//Other methods
}
Specification<BillingSummary> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (params.getStartDate() != null | params.getEndDate() != null) {
predicates.add(cb.like(cb.lower(root.get("startDate")), "%" + params.getStartDate() + "%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
List<BillingSummary> summaries = billingSummaryService.findAll(spec, Sort.by(Sort.Direction.DESC, "createdAt"));
I have these two tables
And I am using this query to get the results
#Query(value = "SELECT bet.bet, match.name, match.week FROM bet INNER JOIN match ON match.id=bet.match_id WHERE match.week = ?1", nativeQuery = true)
List<Object[]> customQuery(Long week);
So far this is the only way I could retrieve the results and actually use them later.
To use them I am using this code currently
List<Object[]> bets = gr.customQuery(2l);
for (Object[] object : bets) {
int bet = (BigInteger) object[0];
String name = (String) object[1];
int week = (BigInteger) object[2];
System.out.println(bet + " " + name + " " + week);
}
But using it that way seems odd to me. Is there a better way to directly map the result to a DTO or something...
There are some options. The most straighforward way would be to define a projection Interface, like:
public interface BetDTO {
Long getBet();
String getName();
Integer getWeek();
}
With that you could change your query return type to:
List<BetDTO> customQuery(Integer week);
The rest would then be using getters.
I have an issue with mapping retrieved data via JDBi3 using PostgreSQL query in my DAO interface.
In my Dropwizard app I have Book DTO class which is has Many-To-Many relation with Author and Category DTO classes and have a problem with mapping queried rows onto BookDTO class. Here are the code snippets of DTO classes:
class BookDTO {
private Long bookId;
// other fields are left for code brevity
private List<Long> authors;
private List<Long> categories;
// empty constructor + constructor with all fields excluding Lists + getters + setters
}
class AuthorDTO {
private Long authorId;
// other fields are left for code brevity
private List<Long> books;
// empty constructor + constructor with all fields excluding List + getters + setters
}
class CategoryDTO {
private Long categoryId;
// other fields are left for code brevity
private List<Long> books;
// empty constructor + constructor with all fields excluding List + getters + setters
}
...and since I am using JDBi3 DAO interfaces for performing CRUD operations this is how my method for querying all books in database looks like:
#Transaction
#UseRowMapper(BookDTOACMapper.class)
#SqlQuery("SELECT book.book_id AS b_id, book.title, book.price, book.amount, book.is_deleted, author.author_id AS aut_id, category.category_id AS cat_id FROM book " +
"LEFT JOIN author_book ON book.book_id = author_book.book_id " +
"LEFT JOIN author ON author_book.author_id = author.author_id " +
"LEFT JOIN category_book ON book.book_id = category_book.book_id " +
"LEFT JOIN category ON category_book.category_id = category.category_id ORDER BY b_id ASC, aut_id ASC, cat_id ASC")
List<BookDTO> getAllBooks();
...and this is map method of BookDTOACMapper class look like:
public class BookDTOACMapper implements RowMapper<BookDTO> {
#Override
public BookDTO map(ResultSet rs, StatementContext ctx) throws SQLException {
final long bookId = rs.getLong("b_id");
// normally retrieving values by using appropriate rs.getXXX() methods
Set<Long> authorIds = new HashSet<>();
Set<Long> categoryIds = new HashSet<>();
long authorId = rs.getLong("aut_id");
if (authorId > 0) {
authorIds.add(authorId);
}
long categoryId = rs.getLong("cat_id");
if (categoryId > 0) {
categoryIds.add(categoryId);
}
while (rs.next()) {
if (rs.getLong("b_id") != bookId) {
break;
} else {
authorId = rs.getLong("aut_id");
if (authorId > 0) { authorIds.add(authorId); }
categoryId = rs.getLong("cat_id");
if (categoryId > 0) { categoryIds.add(categoryId); }
}
}
final List<Long> authorIdsList = new ArrayList<>(authorIds);
final List<Long> categoryIdsList = new ArrayList<>(categoryIds);
return new BookDTO(bookId, title, price, amount, is_deleted, authorIdsList, categoryIdsList);
}
}
Problem I encounter is that when invoking my GET method (defined in Resource class which invokes getAllBooks() method from BookDAO class) displays inconsistent results while the query itself returns proper results.
Many questions that I've managed to find on Stackoverflow, official JDBi3 Docs API and Google Groups are considering One-To-Many relationship and using #UseRowReducer annotation which contains class which impelements LinkedHashMapRowReducer<TypeOfEntityIdentifier, EntityName> but for this case I could not find a way to implement it. Any example/suggestion is welcome. :)
Thank you in advance.
Versions of used tools:
Dropwizard framework 1.3.8
PostgreSQL 11.7
Java8
This will be too long for a comment:
This is basically a debugging question. Why?
while (rs.next()) {
if (rs.getLong("b_id") != bookId) {
break;
} else {
The firstif after the while is eating the row after the current (the one that wass current when the row mapper is called). You are skipping the processing there (putting the data in the Java objects) for the bookId, authorId, etc. That's why you get
inconsistent results while the query itself returns proper results.
So you need to revisit how you process the data. I see two paths:
Revisit the logic of the processing loop to store the data when stopping the processing for given bookId. It is possible to achieve this with scrollable ResultSets - i.e. request a scrollable ResultSet and before the brake; call rs.previous(). On the next call to the row mapper the processing will start from the correct line in the result set.
Use the power of the SQL/PostgreSQL and do it properly: https://dba.stackexchange.com/questions/173831/convert-right-side-of-join-of-many-to-many-into-array Aggregate and shape the data in the database. The database is the best tool for this job.
Also take your time and check the other answers of https://dba.stackexchange.com/users/3684/erwin-brandstetter. They give invaluable insights in the SQL and PostgreSQL.
As zloster mentioned in his answer I've chosen 2nd option (by this answer for Many-To-Many relationships) which was to use edit my PostgreSQL query #SqlQuery annotation above List<BookDTO> getAllBooks(); method. Query now uses array_agg aggregate function in SELECT statement to group my results in an ARRAY and now looks like this:
#UseRowMapper(BookDTOACMapper.class)
#SqlQuery("SELECT b.book_id AS b_id, b.title, b.price, b.amount, b.is_deleted, ARRAY_AGG(aut.author_id) as aut_ids, ARRAY_AGG(cat.category_id) as cat_ids " +
"FROM book b " +
"LEFT JOIN author_book ON author_book.book_id = b.book_id " +
"LEFT JOIN author aut ON aut.author_id = author_book.author_id " +
"LEFT JOIN category_book ON category_book.book_id = b.book_id " +
"LEFT JOIN category cat ON cat.category_id = category_book.category_id " +
"GROUP BY b_id " +
"ORDER BY b_id ASC")
List<BookDTO> getAllBooks();
Therefore map(..) method of BookDTOACMapper class had to be edited and now looks like this:
#Override
public BookDTO map(ResultSet rs, StatementContext ctx) throws SQLException {
final long bookId = rs.getLong("b_id");
String title = rs.getString("title");
double price = rs.getDouble("price");
int amount = rs.getInt("amount");
boolean is_deleted = rs.getBoolean("is_deleted");
Set<Long> authorIds = new HashSet<>();
Set<Long> categoryIds = new HashSet<>();
/* rs.getArray() retrives java.sql.Array and after it getArray gets
invoked which returns array of Object(s) which are being casted
into array of Long elements */
Long[] autIds = (Long[]) (rs.getArray("aut_ids").getArray());
Long[] catIds = (Long[]) (rs.getArray("cat_ids").getArray());
Collections.addAll(authorIds, autIds);
Collections.addAll(categoryIds, catIds);
final List<Long> authorIdsList = new ArrayList<>(authorIds);
final List<Long> categoryIdsList = new ArrayList<>(categoryIds);
return new BookDTO(bookId, title, price, amount, is_deleted, authorIdsList, categoryIdsList);
}
Now all results are consistent and here's a screenshot of query in pgAdmin4.
A bit of context: I have a Spring app with Hibernate.
I want to get all Location entities filtered by ID so I pass a set of IDs as parameter to the query. The problem is that on the query.setParameter("ids", locationIds); row I get the following error:
:Parameter value element [728331] did not match expected type [java.lang.Long (n/a)]
I am confused since the set I am giving is set of Long values. So I assume no explicit casting should be done when passing it as parameter, right? Does anyone has suggestion what is causing the error?
I checked other similar questions but I didn't find one that solve my issue.
#Repository
#Transactional(propagation = Propagation.MANDATORY)
public class LocationDao {
#PersistenceContext
private EntityManager em;
public List<Location> getLocationsByIds(Set<Long> locationIds) {
if (locationIds == null || locationIds.isEmpty()) {
return null;
}
final TypedQuery<Location> query =
em.createQuery("FROM Location l WHERE l.id IN :ids", Location.class);
query.setParameter("ids", locationIds);
return query.getResultList();
}
}
#Entity
#Table(name = "location")
public class Location {
#Id
private Long id;
// other fields
}
EDIT: Hibernate entity manager version: 4.3.8.Final
Found the problem. The locationIds are not exactly Set<Long> locationIds but Set<BigInteger>.
I retrieve the IDs through a native query since I need to perform recursive search in locations. Although I cast it to List<Long> it is actually returns a List<BigInteger>. Here is the code:
private static final String SQL_FIND_LOCATION_AND_CHILDREN_IDS =
" WITH RECURSIVE result_table(id) AS ( "
+ " SELECT pl.id "
+ " FROM location AS pl "
+ " WHERE pl.id = :parentId "
+ "UNION ALL "
+ " SELECT c.id "
+ " FROM result_table AS p, location AS c "
+ " WHERE c.parent = p.id "
+ ") "
+ "SELECT n.id FROM result_table AS n";
#SuppressWarnings("unchecked")
public List<Long> getLocationAndAllChildren(Long parentId) {
final Query query = em.createNativeQuery(SQL_FIND_LOCATION_AND_CHILDREN_IDS);
query.setParameter("parentId", parentId);
return query.getResultList();
}
Then I can just take the long value of the BigInteger since I am sure the values fit in Long's size.
#SuppressWarnings("unchecked")
public List<Long> getLocationAndAllChildren(Long parentId) {
final Query query = em.createNativeQuery(SQL_FIND_LOCATION_AND_CHILDREN_IDS);
query.setParameter("parentId", parentId);
final List<BigInteger> resultList = query.getResultList();
final List<Long> result = new ArrayList<Long>();
for (BigInteger bigIntId : resultList) {
result.add(bigIntId.longValue());
}
return result;
}
Thanks to all for replying and sorry for wasting your time.
I have a problem this getting result list from query. Query return me an null Object. I dont have any idea why its happen. But if I comment its WHERE statement its work fine, but i have two Enum that can specify the result. I dosent think that Im first with it, and google didnt give any answer except to use NamedQuery. This is my code :
#Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public DeviceProfileAttribute getRandomDeviceProfileAttribute(Category category, Platform platform) {
Query q = em.createQuery("SELECT d FROM DeviceProfileAttribute d " +
"WHERE d.tenantAttribute.attribute.category=:category AND " +
"d.tenantAttribute.attribute.platform=:platform " +
"ORDER BY RAND()");
q.setParameter("category", category);
q.setParameter("platform", platform);
q.setMaxResults(1);
if (q.getResultList().isEmpty()) {
return null;
} else {
return (DeviceProfileAttribute) q.getResultList().get(0);
}
}
Im sure that null isnt only one answer.
Thanks in advance.
P.S May be somebody now to check this query after puting all parameters ?
P.P.S The problem is in using Enum and ORDER by RAND() in one SQL Query.
The only way out for me, is to use such code :
#Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public DeviceProfileAttribute getRandomDeviceProfileAttribute(Category category, Platform platform) {
Query q = em.createQuery(
"SELECT d FROM DeviceProfileAttribute d " +
"WHERE d.tenantAttribute.attribute.category=:category AND " +
"d.tenantAttribute.attribute.platform=:platform "
);
q.setParameter("category", category);
q.setParameter("platform", platform);
if (q.getResultList().isEmpty()) {
return null;
} else {
return (DeviceProfileAttribute) q.getResultList().get( new Random().nextInt(q.getResultList().size()));;
}
}