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.
Related
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.
I'm using NamedNativeQueries with SqlResultSetMappings in a Spring Data (JPA Hibernate MySQL) application, and I've been successful with the Pagination, but not with the sorting.
I've tried two forms of queries:
#NamedNativeQuery(
name = "DatasetDetails.unallocatedDetailsInDataset",
resultClass = DatasetDetails.class,
resultSetMapping = "DatasetDetails.detailsForAllocation",
query = "SELECT dd.id, fk_datasets_id, fk_domains_id, fk_sources_id, dom.name AS domain, " +
"src.name AS source " +
"FROM datasets AS d " +
"JOIN datasets_details AS dd ON dd.fk_datasets_id = d.id " +
"JOIN sources AS src ON src.id = dd.fk_sources_id " +
"JOIN domains AS dom ON dom.id = dd.fk_domains_id " +
"WHERE fk_datasets_id = :datasetId " +
"AND dd.id NOT IN (" +
"SELECT fk_datasets_details_id from allocations_datasets_details) \n/* #page */\n"),
and the second is simply using the count notation on a second query instead of using the #page notation.
#NamedNativeQuery(
name = "DatasetDetails.unallocatedDetailsInDataset.count",
resultClass = DatasetDetails.class,
resultSetMapping = "DatasetDetails.detailsForAllocation",
query = "SELECT count(*)
....
Both methods work for pagination, but the sorting is ignored.
Here is the repository:
public interface DatasetDetailsRepository extends PagingAndSortingRepository<DatasetDetails, Long> {
#Query(nativeQuery = true)
List<DatasetDetails> unallocatedDetailsInDataset(#Param("datasetId") long datasetId,
#Param("page") Pageable page);
}
And the pageable gets assembled like this:
Sort sort = Sort.by(Sort.Order.asc(DatasetDetails.DOMAIN), Sort.Order.asc(DatasetDetails.SOURCE));
Pageable page = PageRequest.of(page, limit, sort);
No errors are thrown, but the sorting simply doesn't get done and no ORDER BY is generated.
Explicitly adding something like ORDER BY #{#page} won't compile.
I encountered the same problem, where I had to dynamically filter/sort using a NamedNativeQuery by different columns and directions; apparently the Sorting was ignored. I found this workaround, which is not necessarily nice but it does the job:
For the repository:
List<MyEntity> findMyEntities(
#Param("entityId") long entityId,
#Param("sortColumn") String sortColumn,
#Param("sortDirection") String sortDirection,
Pageable page);
The native queries look like this:
#NamedNativeQueries({
#NamedNativeQuery(name = "MyEntity.findMyEntities",
query = "select e.field1, e.field2, ..." +
" from my_schema.my_entities e" +
" where condition1 and condtition2 ..." +
" order by " +
" CASE WHEN :sortColumn = 'name' and :sortDirection = 'asc' THEN e.name END ASC," +
" CASE WHEN :sortColumn = 'birthdate' and :sortDirection = 'asc' THEN e.birthdate END ASC," +
" CASE WHEN :sortColumn = 'name' and :sortDirection = 'desc' THEN e.name END DESC," +
" CASE WHEN :sortColumn = 'birthdate' and :sortDirection = 'desc' THEN e.birthdate END DESC" +
),
#NamedNativeQuery(name = "MyEntity.findMyEntities.count",
query = "select count(*) from my_schema.my_entities e" +
" where condition1 and condtition2 ..." +
" and :sortColumn = :sortColumn and :sortDirection = :sortDirection"
)
})
Notice in the count query I use the 2 redundant conditions for :sortColumn and :sortDirection, because once specified as #Param in the repository function, you need to use them in the actual query.
When calling the function, in my service I had a boolean which dictates the direction and a string that dictates the sorting column like this:
public Page<MyEntity> serviceFindFunction(Long entityId, String sortColumn, Boolean sortDirection, Integer pageNumber, Integer pageSize) {
String sortDir = (sortDirection) ? 'asc' : 'desc';
Pageable pageable = new PageRequest(pageNumber, pageSize); // Spring Data 1.0 syntax
// for Spring Data 2.0, as you were using, simply:
// Pageable pageable = PageRequest.of(pageNumber, pageSize);
return entityRepository.findMyEntities(entityId, sortColumn, sortDir, pageable)
}
The 2 things that I don't like about this are the redundant usage of the sortColumn and sortDirection params in the count query, and the way I wrote the order by statement. The reasoning for having separate CASE statements is because I had different data types for the columns that I sorted by, and if they are incompatible (e.g. nvarchar and date), the query will fail with the error:
Conversion failed when converting date and/or time from character string
I could also probably nest the conditionals, i.e. first making a case for the direction, the making an inner case for the columns, but my SQL skills only went this far.
Hope this helps! Any feedback or improvements are very welcomed.
I'm using Hibernate4 + PostgresSql.
I need to get a list of ids from my DB without getting a whole object.
I use a native query (because it's hierarchical) and it looks like:
String s = hierachical_part_skipped +"select id " +
"from search_hierarchy " +
"order by id";
Query q = getEntityManager().createNativeQuery(s);
List<Long> resList = new ArrayList<>();
List<BigInteger> biglist = q.getResultList();
for (Object id : biglist) {
System.out.print(id+",");
resList.add(id.longValue());
}
Well, 1 time out of three my bigList is filled with wrong values!
I have
10,20,30,40,50,60,70,80,90,100 ... 27350
instead of
1, 2... 2735
(a zero is added to each value).
Native query executed manually not through Hibernate returns correct values.
I've tried retrieving result as List of Object, but the result was the same
I have found a sufficient workaround
s.append("select id \\:\\:numeric " + //that's a hack
"from search_department " +
"order by id");
Query q = getEntityManager().createNativeQuery(s);
List<Long> resList = new ArrayList<>();
List<BigDecimal> biglist = q.getResultList();
for (BigDecimal id : biglist) {
System.out.print(id + ",");
resList.add(id.longValue());
}
Still, I'm wondering why BigInteger works incorrectly
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 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