Spring Boot + Java 11 + Mybatis: #Many is ignored in Annotations approach - java

I'm a newbie in Mybatis and I have a problem with getting data with #Many.
I have two classes, objects of the second class are values of first-class objects Set<>;
Classes are:
public class InformationObjectMergedWithCopyrights {
private Long id;
private String titleLt;
private String accountingNumber;
private String inventoryNumber;
private Long referenceYear;
private Long duration;
private String propertyRights;
private Set<BavicCredit> creditsList = new HashSet<>();
getters/setters, constructor, toString...
}
public class BavicCredit {
private Long id;
private Long informationObjectId;
private String bavicLabel;
private String bavicRole;
getters/setters, constructor, toString...
}
I have sql provider, which gnerates query String:
public class InformationObjectSqlProvider {
public String selectByInventoryNumberConditionAccountingNumber(InventoryNumberReportParamsDao reportParamsDao) {
StringBuilder sql = new StringBuilder("SELECT " +
"io.ID AS id, " +
"io.TITLE_LT AS titleLt, " +
"io.ACCOUNTING_NUMBER AS accountingNumber, " +
"io.INVENTORY_NUMBER AS inventoryNumber, " +
"io.REFERENCE_YEAR AS referenceYear, " +
"io.DURATION AS duration, " +
"cvt.NAME AS propertyRights " +
"FROM SCHEMA_ONE.INFORMATION_OBJECT io " +
"INNER JOIN SCHEMA_ONE.COPYRIGHTS c ON c.INFORMATION_OBJECT_ID = io.ID " +
"INNER JOIN SCHEMA_TWO.CLASSIFIER_VALUES cv2 ON c.PROPERTY_RIGHTS = cv2.CODE " +
"INNER JOIN SCHEMA_TWO.CLASSIFIER_VALUE_TRANSLATIONS cvt ON cv2.ID = cvt.CLASSIFIER_VALUE_ID AND cvt.LANGUAGE_ID = 1 " +
"WHERE io.INVENTORY_NUMBER = #{reportParamsDao.inventoryNumberCode} ORDER BY accountingNumber ASC");
return sql.toString();
}
}
And finally, I have created a mapper:
#Mapper
public interface InformationObjectMapper {
InformationObjectSqlProvider provider = new InformationObjectSqlProvider();
#SelectProvider(type = InformationObjectSqlProvider.class, method = "selectByInventoryNumberConditionAccountingNumber")
#Results(value = {
#Result(property = "creditsList",
column = "SCHEMA_ONE.INF_OBJ_CLASIFFIER.INFORMATION_OBJECT_ID",
javaType = Set.class, many = #Many(select = "selectCredits"))
})
List<InformationObjectMergedWithCopyrights> selectByInventoryNumberConditionAccountingNumber(#Param("reportParamsDao") InventoryNumberReportParamsDao reportParamsDao);
#Select("SELECT * FROM SCHEMA_ONE.INF_OBJ_CLASSIFIER ioc WHERE ioc.INFORMATION_OBJECT_ID = #{informationObjetId} AND ioc.\"TYPE\" = 'CREDIT' ")
#Results(value = {
#Result(property = "bavicLabel", column = "BAVIC_LABEL"),
#Result(property = "bavicRole", column = "ROLE")
})
Set<BavicCredit> selectCredits(#Param("informationObjetId") Long informationObjetId);
}
My problem is that this code doesn't return Set of BavicCredit classes. Set is always empty, just like defined in the first class. Seems like the method selectCredits isn't called at all.
The foreign key exists in the database. I believe, that I'm missing some small code or property mapping.
Java 11, Spring Boot 2.5.0, Mybatis mybatis-spring-boot-starter 2.2.0, database Oracle com.oracle.database.jdbc ojdbc8
Thanks in advance.
PS I have tried One to many relationship in MyBatis but still missing something

When using a nested select [1], the value of the column attribute should be a column name in the result set of the first query.
In the result set of your first query, the parameter to the nested query is id, so the #Result should look as follows.
#Result(
property = "creditsList",
column = "id",
javaType = Set.class,
many = #Many(select = "selectCredits"))
[1] For the basics, please read this section of the doc.

Related

Problem mapping to DTO classes with Many-To-Many relations using JDBi3 in Dropwizard

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.

how to return a list of object which is not entity from a typed query in hibernate?

I need to pull few fields from entity class Employee and add few extra hard coded field and return the result using GROUP BY clause.
Below is the code I tried:
String query = "SELECT emp.category, emp.salary 0 as somevalue, 0 as dummy FROM employee emp "
+ "WHERE emp.date = :date AND emp.class = :class AND emp.classificationDetail.shortDescription = :classificationType GROUP BY emp.category";
TypedQuery<CustomEmployee> typQuery = entityManager.createQuery(query, CustomEmployee.class);
typQuery.setParameter("date", req.getDate());
typQuery.setParameter("class", req.getClass());
return typQuery.getResultList();
But I am getting exception that Cannot create TypedQuery for query with more than one return using requested result type.
How to achieve this.
Thanks.
First check this part: emp.salary 0 as somevalue. This should be either emp.salary as somevalue or 0 as somevalue, but not both.
Define a class like following (to keep it short; I use public properties, but you can change it if you want):
public class CustomEmployee {
public String category;
public Double salary;
public Double dummy;
...
}
The use it in the query as follows:
String query = "SELECT new mypackage.CategorySalary( " +
" emp.category, " +
" emp.salary as somevalue, " +
" 0 as dummy " +
") from ... " +
"WHERE ... ";
TypedQuery<CustomEmployee> typQuery = entityManager.createQuery(query, CustomEmployee.class);

How to get Long (data type) value back from redis cache

I am storing userId as Long in redis cache in my spring boot application but while retrieving it returns Integer type and throws exception
java.lang.Integer cannot be cast to java.lang.Long
Below is the code snippet I am using.
#Cacheable(value = CacheConstants.GAUTH_CACHE, key = "#root.target.PASSWORD_SALT.concat(#loginTxt.toString())", unless = "#result == null")
public Long getPasswordSaltFromLoginText(String loginTxt) {
Long userId = null;
if(StringUtils.isNotBlank(loginTxt)) {
userId = profileRepository.getUserIdForPasswordSalt(loginTxt, "PasswordSalt");
}
return userId;
}
My Hibernate query is something like that. in which A.USR_ID is type Long
#Query(nativeQuery = true, value = "select A.USR_ID from user_tbl A, another_table B WHERE A.USR_ID = B.USR_ID AND "
+ " UPPER(A.loginTxt) = UPPER(:loginTxt) "
+ " AND B.prefName=:prefName ")
Long getUserId(#Param("loginTxt") String loginTxt, #Param("prefName") String prefName);
Entity class is
#Entity
#Table(name="Table1", schema = "Schema_name")
public class Profile {
#Id
#Column(name="USR_Id")
public Long USR_ID;
#Column(name="other_column")
public Long other_column;
#Column(name="other_column2")
public Long other_column2;
}
Redis cache doesn't support the Long data type. So I have stored whole profile object into redis cache and using getter(), I was able to get the Long value.
#Query(nativeQuery = true, value = "select A.USR_ID,A.other_column,A.other_column2 from user_tbl A, another_table B WHERE A.USR_ID = B.USR_ID AND "
+ " UPPER(A.loginTxt) = UPPER(:loginTxt) "
+ " AND B.prefName=:prefName ")
Profile getUserId(#Param("loginTxt") String loginTxt, #Param("prefName") String prefName);

JPQL collection parameter - mismatch the expected type

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.

Problems with scalar values in native hibernate/jpa query using annotations

I'm trying to run a simple SQL query containig a group by clause.
HistoryEntity:
#Entity
#NamedNativeQueries(value = {
#NamedNativeQuery(name = HistoryEntity.FIND_ALL_BY_REFERENCE,
query = "SELECT h.id, h.reference, h.lrn "
+ "FROM dataHistory h "
+ "WHERE h.reference = :referenceNumber OR h.lrn = :referenceNumber",
resultSetMapping = HistoryEntity.FIND_ALL_BY_REFERENCE_MAPPING),
#NamedNativeQuery(name = HistoryEntity.FIND_REFERENCE_BY_LRN,
query = "SELECT h.reference as reference "
+ "FROM dataHistory h "
+ "WHERE h.lrn = :lrn "
+ "GROUP BY h.reference",
resultSetMapping = "resultMapping")
})
#SqlResultSetMappings(value = {
#SqlResultSetMapping(name = HistoryEntity.FIND_ALL_BY_REFERENCE_MAPPING, entities = {
#EntityResult(entityClass = HistoryEntity.class, fields = {
#FieldResult(name = "id", column = "id"),
#FieldResult(name = "reference", column = "reference"),
#FieldResult(name = "lrn", column = "lrn")
})
}),
#SqlResultSetMapping(name = "resultMapping", columns = {
#ColumnResult(name = "reference")
})
})
public class HistoryEntity implements Serializable {
/**
* #param referenceNumber Referenz (LRN oder BRN)
* #param brnList Liste von BRNs
*/
public static final String FIND_ALL_BY_REFERENCE = "HistoryEntity.findAllByReference";
public static final String FIND_ALL_BY_REFERENCE_MAPPING = "HistoryEntity.findAllByReferenceMapping";
private Integer id;
private String reference;
private String lrn;
public HistoryEntity() {}
#Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getReference() {
return reference;
}
public void setReference(String reference) {
this.reference= reference;
}
public String getLrn() {
return lrn;
}
public void setLrn(String lrn) {
this.lrn = lrn;
}
In the service class, I execute the query as follow:
Query query = entityManager.createNamedQuery("myQuery");
query.setParameter("lrn", lrn);
List resultList = query.getResultList();
Depending on the result of the query, the list contains java.lang.Character's:
Case 1:
SQL-Result (if I'm running the sql in a sql client):
| Reference |
| 123456780678MMM |
| 123456781678MMM |
| 123456782678MMM |
| 123456783678MMM |
Java-List-Result (in Java Debug View):
[1, 1, 1, 1]
Case 2:
SQL-Result:
| Reference |
| 123456780678MMM |
Java-List-Result:
[1]
I'm looking for a way to run a simple sql query with scalar values (using hibernate/jpa) to get as result a list with the values of the result.
Is there any way to do this without using the criteria api?
If you need more information, let me know!
Many thanks in advance for your help.
Marco
The Problem is a bug in hibernate, that casts the result to java.lang.Character insteat of java.lang.String. Only the first character of each row was returned in the result.
There is another question with a more detailed description of the solution:
Hibernate native query - char(3) column
If I use the cast-function in my query as follow, it works:
#NamedNativeQuery(name = HistoryEntity.FIND_REFERENCE_BY_LRN,
query = "SELECT cast(h.reference as VARCHAR(27)) as reference "
+ "FROM dataHistory h "
+ "WHERE h.lrn = :lrn "
+ "GROUP BY h.reference",
resultSetMapping = "resultMapping")
PS: I found the referenced question to late. Sorry for that!

Categories