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!
Related
I'm having a strange error using #SqlResultSetMapping and #NamedNativeQuery.
My Entity has:
#NamedNativeQuery(
name = "example_query",
query = "SELECT table_A.id, " +
"COUNT(*) AS numberOfA " +
"FROM table_A table_A " +
" JOIN example example ON example.id = :idExample " +
"GROUP BY id " +
"ORDER BY numberOfA DESC;",
resultSetMapping = "myMapping"
)
#SqlResultSetMapping(
name = "myMapping",
classes = #ConstructorResult(
targetClass = ExampleDTO.class,
columns = {
#ColumnResult(name = "id", type = Integer.class),
#ColumnResult(name = "numberOfA", type = Integer.class)
}
)
)
public class Entity implements Serializable { /////// }
My DTO is like:
public class ExampleDTO {
private Integer id;
private Integer numberOfA;
}
My Repository:
public interface Entity extends JpaRepository<Entity,Integer> {
#Query(name = "example_query", nativeQuery = true)
List<ExampleDTO> findExampleQuery(#Param("id") Integer id);
}
On the database the same query return:
id | numberofa
1 11
2 5
and the mapping return an object with:
id | numberofa
1 154
2 70
How is that possible?
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.
I have the following entity (some columns omitted for brevity):
#Entity
#Table(name = "INSTRUCTION")
public class Instruction {
#Id
#Column(name = "ID", nullable = false, unique = true)
public Long id;
#Column(name = "CURRENT_STATUS", nullable = false)
private InstructionState currentStatus;
#Column(name = "SUBTYPE", nullable = false)
private InstructionAction subtype;
//Getters & Setters
}
I want to write the following query in JPA to retrieve a count of the instructions grouped by their CURRENT_STATUS and SUBTYPE. I know that the following SQL works:
SELECT CURRENT_STATUS, SUBTYPE, COUNT(*) count
FROM (SELECT ID, CURRENT_STATUS, SUBTYPE
FROM INSTRUCTION
WHERE VALUE_DATE= '17-JUN-21'
AND LAST_UPDATED >= '16-JUN-21'
AND LAST_UPDATED < '17-JUN-21'
GROUP BY ID, CURRENT_STATUS, SUBTYPE)
GROUP BY CURRENT_STATUS, SUBTYPE;
I want to take the result from this query and map it to a new object call InstructionCount:
public class InstructionCount {
private InstructionState status;
private InstructionAction subType;
private Integer count;
public InstructionCount(final InstructionState status, final InstructionAction subType, final Integer count) {
this.status = status;
this.subType = subType;
this.count = count;
}
//Getters and setters
}
The Problem
I have come up with the following query in JPA for this in my repository class:
#Query(value = "SELECT new com.modles.InstructionCount(CURRENT_STATUS status, SUBTYPE subType, COUNT(*) count) \n" +
"FROM (SELECT ID, CURRENT_STATUS, SUBTYPE \n" +
"\t\tFROM LTD_RTGS_CASHINSTRUCTION \n" +
"\t\tWHERE VALUE_DATE= :valueDate \n" +
"\t\tAND LAST_UPDATED >= :lastUpdatedFrom \n" +
"\t\tAND LAST_UPDATED < :lastUpdatedTo \n" +
"\t\tGROUP BY ID, CURRENT_STATUS, SUBTYPE)\n" +
"GROUP BY CURRENT_STATUS, SUBTYPE", nativeQuery = true)
List<InstructionCount> findInstructionCounts(#Param("valueDate") LocalDate valueDate, #Param("lastUpdatedFrom") LocalDateTime lastUpdatedFrom, #Param("lastUpdatedTo") LocalDateTime lastUpdatedTo);
The issue is that this does not work, and I have found it is because I cannot use the approach of mapping it to a new object using the SELECT new com.modles.InstructionCount with nativeQuery = true. However when I try to remove the nativeQuery = true part and run my test I get the following error:
I also notice that the SQL gets syntax errors in the IDE, highlighted on the second SELECT statement, so I presume there is an issue with this format when not using nativeQuery.
Can anyone help with how I can resolve this issue? I know this can work, because when I remove the SELECT new com.modles.InstructionCount part, it will just return a list of Object[] with the correct values I'm expecting, but I would much prefer to map this to the correct object as part of the query. Alternatively, if there is a way to write this query using the Specification API, I would also use that approach!
Below query should just work just fine and there is no need of your inner/sub query
SELECT CURRENT_STATUS, SUBTYPE, COUNT(ID)
FROM INSTRUCTION
WHERE VALUE_DATE= '17-JUN-21'
AND LAST_UPDATED >= '16-JUN-21'
AND LAST_UPDATED < '17-JUN-21'
GROUP BY CURRENT_STATUS, SUBTYPE;
This should works seemlessly while returning the result as
InstructionCount
------->Edit------------->
#Query(value = "SELECT new com.modles.InstructionCount(currentStatus, subtype, count(id)) FROM Instruction WHERE valueDate= :valueDate AND lastUpdatedFrom >= :lastUpdatedFrom AND lastUpdatedTo < :lastUpdatedTo GROUP BY id, currentStatus, subtype")
List<InstructionCount> findInstructionCounts(#Param("valueDate") LocalDate valueDate, #Param("lastUpdatedFrom") LocalDateTime lastUpdatedFrom, #Param("lastUpdatedTo") LocalDateTime lastUpdatedTo);
I have a select statement that goes into two table and obtain ids from each table. Here is my pseudo code:
Table 1 (study) has study_id
Table 2 (image) has image_id
NamedNativeQuery(name = "test", query = "select study.study_id, image.image_id from study study, image image where image_id=:imageId and study_id=:studyId")
In my code, I have:
Query query = getSession().getNamedNativeQuery("test");
query.setParameter(STUDY_ID, studyId);
query.setParameter(IMAGE_ID, imageId);
List result = query.getResultList(); //result is List<Object>
result is list of Object, where each Object has two values, one study_id and one image_id.
How do I extract these information from Object? What is the best approach to do this?
You can use the #SqlResultSetMapping with #ConstructorResult in the following way:
public class StudyImageIds {
private final Long studyId;
private final Long imageId;
public StudyImageIds(Long studyId, Long imageId) {
this.studyId = studyId;
this.imageId = imageId;
}
public Long getStudyId() {
return studyId;
}
public Long getImageId() {
return imageId;
}
}
// ...
#NamedNativeQuery(
name = "test",
query = "select "
+ " study.study_id as std_id, image.image_id as img_id "
+ "from study study, image image "
+ "where image_id = :imageId and study_id = :studyId",
resultSetMapping = "study_image_ids_dto"
)
#SqlResultSetMapping(
name = "study_image_ids_dto",
classes = #ConstructorResult(
targetClass = StudyImageIds.class,
columns = {
#ColumnResult(name = "std_id"),
#ColumnResult(name = "img_id")
}
)
)
and then use it:
List<StudyImageIds> studyImageIds = session.getNamedQuery("test")
.setParameter(STUDY_ID, studyId)
.setParameter(IMAGE_ID, imageId)
.list();
IMHO this is much more safer and elegant than types casting. See further details in the documentation.
Have hql query
List<Developer> developers = session.createQuery("from Developer d " +
"left join ProjectDeveloper pd on pd.developer.id = d.id " +
"where pd.project.id = " + project.getId()).getResultList();
it work and get a list of the objects, but not a Developer.
Actually, the object with i get looks like a list of arrays, which includes object arrays, which include developer class objects and ProjectDeveloper (mtm class). Unfortunately I can not place a link with the image, but Schema look like:
developers = {ArrayList#4889} size = 4
\/ 0 = {object[2]4897} //what does this do in ArrayList<**Developer**>>??
-> 0 = {Developer#4901} //This is exactly the class I expect in ArrayList
-> 1 = {ProjectDeveloper}//?????
-> 1 = {object[2]4898}
Developer class:
#Table(name = "developers")
#Entity
public class Developer implements GenerallyTable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "first_name")
private String name;
#Column(name = "age")
private int age;
#Column(name = "sex")
private boolean sex;
#Column(name = "salary")
private BigDecimal salary;
//...getters + setters
}
How it impossible (how Developer.class transformed in array with unknown structure), and how do I can get exactly ArrayList of Developers?
Try with createQuery with the type defined https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/Session.html#createQuery-java.lang.String-java.lang.Class-
List<Developer> developers = session.createQuery("select d from Developer d " +
"left join ProjectDeveloper pd on pd.developer.id = d.id " +
"where pd.project.id = " + project.getId(), Developer.class)
.getResultList();
Also you should use setParameter methods
List<Developer> developers = session.createQuery("select d from Developer d " +
"left join ProjectDeveloper pd on pd.developer.id = d.id " +
"where pd.project.id = :proj_id", Developer.class)
.setParameter("proj_id", project.getId())
.getResultList();