Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 859 Vote(s) - 3.48 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Spring Data JPA map the native query result to Non-Entity POJO

#1
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 ?
Reply

#2
Assuming GroupDetails as in orid's answer have you tried JPA 2.1 [@ConstructorResult][1]?

@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][2], 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


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#3
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;
}
Reply

#4
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.
Reply

#5
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;
}
}
Reply

#6
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.][1]


[1]:

[To see links please register here]


**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.
Reply

#7
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.
Reply

#8
**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);
}

Reply

#9
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);
}

Reply

#10
***If you are looking for running a custom SQL query in spring boot with @repository and @service structures. Please have a look.***

[To see links please register here]

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through