How to take Object[] as return type in Spring DataJpa #Query methods? - spring-boot

I want to fetch one row only by taking Object[] as return type in the #Query method of Repo interface. But while accessing the values it's showing
java.lang.ArrayIndexOutOfBoundsException: 1
If i will take Object (not Object[])as return type in the #Query method and while calling it again i have to cast return type to Object[] to get the values. But I don't understand why i can't take Object[] as return type directly.
Repo interface
public interface EmployeeRepository extends JpaRepository<Employee, Integer>{
#Query("select e.empName,e.empSal from org.st.model.Employee e where e.empId= ?1")
Object[] getData2(Integer eid);
}
Runner class
#Component
public class ConsoleRunner implements CommandLineRunner{
#Autowired
private EmployeeRepository repo;
public void run(String...strings) throws Exception{
Object[] obj = repo.getData2(106);//
System.out.println(obj[]);//No Exception no result but [Ljava.lang.Object;#68b366
System.out.println(obj[1]);//AIOBException
}
}
I should get name(String) and sal(Double) value as result.
#Query("select e.empName,e.empSal from org.st.model.Employee e where e.empId= ?1 ")
Object getData2(Integer eid);
Object[] obj = (Object[]) repo.getData2(106);
System.out.println(obj[1]);
It's working fine. cause i have taken Object as return type. But i dont understand why Object[] as Return type is showing AIOBE.

Java array indexes starts with 0. If your Object[] obj has length 1, you need to retrieve it like this: obj[0]
Try this:
public void run(String...strings) throws Exception{
Object[] obj = repo.getData2(106);
System.out.println(obj[]);
System.out.println(obj[0]);
}
}

java.lang.ArrayIndexOutOfBoundsException means you are trying to
access a array index that doesn't exist
Object[] results = repo.getData2(106);
for(Employee emp : results){
System.out.println(emp.getName() + " " + emp.getSal());
}
Employee class must have getters.
But using the List<Employee> makes type safety,
public interface EmployeeRepository extends JpaRepository<Employee, Integer>{
#Query("select e.empName,e.empSal from Employee e where e.empId= ?1")
List<Employee> getData2(Integer eid);
}
and you can easily access
List<Employee> results = repo.getData2(106);
for(Employee emp : results){
System.out.println(emp.getName() + " " + emp.getSal());
}

Returning Object[] means that the query returns an array of records. Each record is itself an Object[]. Therefore access the first column of the first record you need to use (Object[]) o[0]).
You probably want to use use a projection instead:
interface EmployeeView {
String getEmpName();
String getEmpSal();
}
#Query("select e.empName as empName, e.empSal as empSal from org.st.model.Employee e where e.empId= ?1")
EmployeeView getData2(Integer eid);
EmployeeView emp = repo.getData2(106);

Related

No converter found capable of converting from type

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.skywarelabs.runwayreporting.api.modules.asset.Asset] to type [com.skywarelabs.runwayreporting.api.modules.asset.AssetPageView]
I have two repository methods which do essentialy the same thing.
I'm expecting them to return an object of <Page>.
If I call findPageByAerodrome, this works correctly.
However, If I call findPageByFilter, I get the error below.
..the reason I want to use findPageByFilter is that I wish to extend this with additional filtering parameters.
'''
public interface AssetRepository extends PageCrudRepository<Asset, Long> {
#Query("SELECT a FROM Asset a WHERE " +
"(a.aerodrome = :aerodrome)")
<T> Page<T> findPageByFilter(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type);
<T> Page<T> findPageByAerodrome(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type);
,,,
Use this instead
<T> Page<T> findAll(Specification s, Pageable pageable, Class<T> type);
and pass a custom Specification where you create your equality filter like this:
<T> Page<T> findPageByFilter(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type) {
return findAll(
new Specification() {
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get("aerodrome"), aerodrome);
}
},
pageable,
type
);
}
Figured it out. I need to add an entity specific constructor.
'''
public AssetPageView(Asset asset) {
this ( asset.getId(), asset.getAssetType(), asset.getAerodrome(), asset.getName(),
asset.getAreaOfOperationsLoc(), asset.getDisplayOrder() );
}
public AssetPageView(Long id, AssetType assetType, Aerodrome aerodrome, String name,
String areaOfOperationsLoc, Long displayOrder) {
this.id = id;
this.assetType = assetType.getName();
this.aerodrome = aerodrome.getCallSign();
this.name = name;
this.areaOfOperationsLoc = areaOfOperationsLoc;
this.displayOrder = displayOrder;
}
'''

Hibernate callable attribute throwing error in #NamedNativeQuery in Hibernate 3.2.5

May i know why callable not working in hibernate 3.2.5 inside #NamedNativeQuery and it throws compilation error. Can anyone know why it is throwing error and please let me know which version it will support.
I need to call oracle stored procedure from Hibernate entity class using #NamedNativeQuery . Please help.
I implemented like below and everything works fine.
Student.class
#Entity
#NamedNativeQueries(
#NamedNativeQuery(
name = Student.UPDATE,
query = "CALL updateStudent(:studentId, :studentName)",
)
)
public class Student extends ABaseEntity {
public static final String UPDATE = "Student.update";
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
updateStudent procedure
CREATE OR REPLACE PROCEDURE UPDATESTUDENT
(
studentId IN NUMBER
, studentName IN VARCHAR2
) AS
BEGIN
Update student set name = studentName where id = studentId;
END UPDATESTUDENT;
StudentDao
#Stateless
public class StudentDao {
#PersistenceContext(name = "tngo")
private EntityManager entityManager;
public void updateStudent(Long id, String name){
Query query = entityManager.createNamedQuery(Student.UPDATE);
query.setParameter("studentId", id);
query.setParameter("studentName", name);
query.executeUpdate();
}
}
I'm using jpa with hibernate provider version 5.1.0.Final. If you want to return result sets from procedure, you can provide resultClass and resultSetMapping in #NamedNativeQuery annotation.

Spring Data JPA map the native query result to Non-Entity POJO

I have a Spring Data repository method with a native query
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
and I'd like to map the result to Non-Entity POJO GroupDetails.
Is it possible and if so, could you please provide an example ?
I think the easiest way to do that is to use so called projection. It can map query results to interfaces. Using SqlResultSetMapping is inconvienient and makes your code ugly :).
An example right from spring data JPA source code:
public interface UserRepository extends JpaRepository<User, Integer> {
#Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
public static interface NameOnly {
String getFirstname();
String getLastname();
}
}
You can also use this method to get a list of projections.
Check out this spring data JPA docs entry for more info about projections.
Note 1:
Remember to have your User entity defined as normal - the fields from projected interface must match fields in this entity. Otherwise field mapping might be broken (getFirstname() might return value of last name et cetera).
Note 2:
If you use SELECT table.column ... notation always define aliases matching names from entity. For example this code won't work properly (projection will return nulls for each getter):
#Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
But this works fine:
#Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
In case of more complex queries I'd rather use JdbcTemplate with custom repository instead.
Assuming GroupDetails as in orid's answer have you tried JPA 2.1 #ConstructorResult?
#SqlResultSetMapping(
name="groupDetailsMapping",
classes={
#ConstructorResult(
targetClass=GroupDetails.class,
columns={
#ColumnResult(name="GROUP_ID"),
#ColumnResult(name="USER_ID")
}
)
}
)
#NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")
and use following in repository interface:
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
According to Spring Data JPA documentation, spring will first try to find named query matching your method name - so by using #NamedNativeQuery, #SqlResultSetMapping and #ConstructorResult you should be able to achieve that behaviour
I think Michal's approach is better. But, there is one more way to get the result out of the native query.
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
Now, you can convert this 2D string array into your desired entity.
You can write your native or non-native query the way you want, and you can wrap JPQL query results with instances of custom result classes.
Create a DTO with the same names of columns returned in query and create an all argument constructor with same sequence and names as returned by the query.
Then use following way to query the database.
#Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")
Create DTO:
package example;
public class CountryAndCapital {
public String countryName;
public String capitalName;
public CountryAndCapital(String countryName, String capitalName) {
this.countryName = countryName;
this.capitalName = capitalName;
}
}
This is my solution for converting to Map and then to custom Object
private ObjectMapper objectMapper;
public static List<Map<String, Object>> convertTuplesToMap(List<?> tuples) {
List<Map<String, Object>> result = new ArrayList<>();
tuples.forEach(object->{
if(object instanceof Tuple single) {
Map<String, Object> tempMap = new HashMap<>();
for (TupleElement<?> key : single.getElements()) {
tempMap.put(key.getAlias(), single.get(key));
}
result.add(tempMap);
}else{
throw new RuntimeException("Query should return instance of Tuple");
}
});
return result;
}
public <T> List<T> parseResult(List<?> list, Class<T> clz){
List<T> result = new ArrayList<>();
convertTuplesToMap(list).forEach(map->{
result.add(objectMapper.convertValue(map, clz));
});
return result;
}
public static class CustomDTO{
private String param1;
private Integer param2;
private OffsetDateTime param3;
}
public List<CustomDTO> doSomeQuery(){
Query query = entityManager.createNativeQuery("SELECT param1, param2 param3 ... ", Tuple.class);
return parseResult(query.getResultList(), CustomDTO.class);
}
Use the default method in the interface and get the EntityManager to get the opportunity to set the ResultTransformer, then you can return the pure POJO, like this:
final String sql = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = ? WHERE g.group_id = ?";
default GroupDetails getGroupDetails(Integer userId, Integer groupId) {
return BaseRepository.getInstance().uniqueResult(sql, GroupDetails.class, userId, groupId);
}
And the BaseRepository.java is like this:
#PersistenceContext
public EntityManager em;
public <T> T uniqueResult(String sql, Class<T> dto, Object... params) {
Session session = em.unwrap(Session.class);
NativeQuery q = session.createSQLQuery(sql);
if(params!=null){
for(int i=0,len=params.length;i<len;i++){
Object param=params[i];
q.setParameter(i+1, param);
}
}
q.setResultTransformer(Transformers.aliasToBean(dto));
return (T) q.uniqueResult();
}
This solution does not impact any other methods in repository interface file.
USE JPA PROJECTIONS
In your case it may be desirable to retrieve data as objects of customized types. These types reflect partial views of the root class, containing only properties we care about. This is where projections come in handy.
first declare Entity as #immutable
#Entity
#Immutable
public class Address {
#Id
private Long id;
set your Repository
public interface AddressView {
String getZipCode();
}
Then use it in a repository interface:
public interface AddressRepository extends Repository<Address, Long> {
#Query("EXEC SP_GETCODE ?1")
List<AddressView> getAddressByState(String state);
}
If you are looking for running a custom SQL query in spring boot with #repository and #service structures. Please have a look.
https://stackoverflow.com/a/71501509/4735043
You can do something like
#NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,
query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName,
cat.issueCategory, idc.issueDescriptor, idc.description)
from Department dep
inner join dep.issues iss
inner join iss.category cat
inner join cat.issueDescriptor idc
where idc.id in(?1)")
And there must be Constructor like
public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
String description) {
super();
this.id = id;
this.department = department;
this.issueName = issueName;
this.issueCategory = issueCategory;
this.issueDescriptor = issueDescriptor;
this.description = description;
}

About spring Rowmapper , mapRow

I have a some questions about Spring rowmapper. I'm going to receive data from my DB using rowmapper.But my command object 'Table' only have List variable.
is Rowmapper automatically map each record to List ? is it posibble?
i know spring bind tag is automatically bind value to list.
right this.
Table.java
public class Table implements Serializable{
private List<String> tableNum = new ArrayList<String>();
// setter and getter
}
Dao
private class TableRowMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Table table = new Table();
table.setTableNum(rs.getString("TABLE_LOCATION"));
return table;
}
}
The RowMapper is used to map a single row to a single domain object, not a bunch of rows results to a single domain object. Also the RowMapper isn't a Dao type object. It is to be used with some query method, like JdbcTemplate.query(sql,args,rowMapper)
But in your case, you don't want a RowMapper. You should instead just use a JdbcTemplate.queryForList. See the JdbcTemplate API for more query method. A simple example would be something like:
public class YourDaoImpl extends JdbcTemplate implements YourDao {
private static final String SQL =
"select SOME_FIELD from SOME_TABLE where SOMETHING = ?";
#Override
public List<String> getSomeFieldBySomething(String something) {
return (List<String>)queryForList( SQL,
new Object[] { something },
String.class);
}
}
You use the dao for your services.
UPDATE
Because of your help, I can get a one column from my DB. but I got a problems. my db table is made of multiple columns. and i must receive all of them. and.. how can i do it? plz help me~!!!
You posted question in no points that out. In this case you need to make a List<DomainObject>. Not a List<String>. List<String> only allows for one value. If you have a List<DomainObject>, then the class DomainObject can have all your fields. Then that's when you use the RowMapper. And you can still use queryForList that uses the RowMapper variant
public class Table {
private String field1;
private String field2;
private String field3;
// getters and setters
}
public class YourDaoImpl extends JdbcTemplate implements YourDao {
private static final String SQL =
"select * from SOME_TABLE where SOMETHING = ?";
#Override
public List<Table> getTableBySomething(String something) {
return (List<Table>)queryForList( SQL,
new Object[] { something },
new RowMapper<Table>(){
#Override
public Table mapRow(ResultSet rs, int rowNumber) {
Table table = new Table();
table.setField1(rs.getString("feild1"));
// set the others
return table;
}
});
}
}
An aside, if I were you, I would forget the jdbc and go for an ORM framework like JPA. If you want entire domain objects, this is the way to go.

Is it possible to avoid typecast on custom #Query?

Imagine that we have an entity:
#Entity
public class Person implements Serializable {
#Id
private String name;
private Long age;
private Boolean isMad;
...
}
And a repository with a trivial (and unnecessary) example for a custom query:
#Repository
public interface PersonRepository extends PagingAndSortingRepository<Info, String> {
#Query("select p.isMad, count(*) from Person p group by p.isMad")
List<Object> aggregateByMadness();
}
Now to parse this List we need to do something like this:
for (Object element : list) {
Object[] result = (Object[]) element;
Boolean isMad = (Boolean) result[0];
Long count = (Long) result[1];
}
which is a pain, can we cast the result of the query directly to List of a POJO?
Yes, you could use the JPQL construction expression:
package com.foo;
public class Madness {
public Madness(boolean isMad, Number count) { /* ...*/ }
}
And in your repository:
#Query("select new com.foo.Madness(p.isMad, count(*)) from Person p group by p.isMad")
List<Madness> aggregateByMadness();

Resources