Map any collection with NamedNativeQuery and SqlResultSetMapping - java

So I am writing native queries because they are very complex to get all the needed data and so on. I am currently facing a problem which is normally done by Hibernate/JPA etc.
Imagine the following:
#Entity
FooEntity{
#Id
public Long id;
#ManyToMany
public List<FeeEntity> feeEntities;
}
#Entity
FeeEntity{
#Id
public Long id;
#Column
public String name;
}
And some DTO
FooDTO{
private final Long id;
private final List<FeeDTO> someStrings;
public FooDTO(Long id, List<FeeDTO> feeDtos){
...
}
}
My #SqlResultSetMapping looks basically like
#SqlResultSetMapping(name = "FooDTO",
classes = #ConstructorResult(targetClass = FooDTO.class, columns = {
#ColumnResult(name = "id", type = Long.class),
//TODO #ColumnResult(name = "feeDtos", type = FeeDtos.class)
})
)
The named native query looks something like:
#NamedNativeQueries({
#NamedNativeQuery(
name = "FooData",
query = "SELECT MAINSELECT.ID AS id, " +
"???" +
" FROM Foo MAINSELECT WHERE ... " +
...,
resultSetMapping = "FooDTO")
})
How do I have to write the native query? Is this even possible without a subquery or do I have to do/execute a subquery for each datarow of the result? I was not able to find something on beloved google.

That's not possible.
The result of SQL query is always a table. So there are no nested tables and hence you cannot map it to a Collection.

Related

Spring Data JPA/Hibernate - using EntityManager for queries

I have these entities, from which I want get List of Tuples containing information from both of them:
which should look like this:
+--------+-----+---------+
|name |login|user_type|
+--------+-----+---------+
| |admin|admin |
|John Doe|john |user |
|Jane Doe|janed|user |
|........|.....|.........|
The thing is, my JPA skill got quite rusty and I forgot how to use Entity Managers. I know how to make basic JPQL queries or build-in stuff, but sadly it's not enough for me (since I need to use that list for filling up table in my UI). So, how did I should use Entity Managers? (if I should use that at all ;) )
Edit: now I think that using DTO projection is better; here's my mapping class
public class PersonUserMap {
private Integer personID;
private String name;
private String login;
private UserType userType;
public Integer getPersonID() {
return personID;
}
public String getName() {
return name;
}
public String getLogin() {
return login;
}
public UserType getUserType() { //custom Enum
return userType;
}
}
my annotation in People class:
#SqlResultSetMapping(
name = "PersonUserMapping",
classes = #ConstructorResult(
columns = { #ColumnResult(name = "personID", type=Integer.class),
#ColumnResult(name = "name"),
#ColumnResult(name = "login"),
#ColumnResult(name = "userType",type = UserType.class)},
targetClass = PersonUserMap.class))
and when using native query like this:
Query q = entityManager.createNativeQuery("Select p.personid, p.first_name || ' ' || p.last_name as name, u.login, u.user_type from people p join users u on p.user_idusers = u.idusers","PersonUserMapping");
it throws exception Could not resolve column name in result set [userType]
Thanks for #Chris help I finally got somewhere; my set mapping is looking like this:
#SqlResultSetMapping(
name = "PersonUserMapping",
classes = #ConstructorResult(
columns = { #ColumnResult(name = "personid", type=Integer.class),
#ColumnResult(name = "name"),
#ColumnResult(name = "login"),
#ColumnResult(name = "user_type",type = UserType.class)},
targetClass = PersonUserMap.class))
and my query looks like this
Query q = entityManager.createNativeQuery("select personid, concat(first_name, ' ', last_name) as 'name', users.login, users.user_type from aspirejestracja.people"
+ " full join aspirejestracja.users on user_idusers = users.idusers ", "PersonUserMapping");
now I can display all users which I need :)

Using arbitrary query as projection in spring data rest project

How it is possible to use arbitrary sql query (I mean native sql query) in some repository? My actual problem is this:
#Data //lombok thing
#Entity
public class A extends AuditModel {
private long id;
private String name;
#OneToMany(mappedBy="a") //Comments.a is owning side of association, i.e. comments table does have column called a_id as foreign key
#ToString.Exclude
private Set<Comments> comments = new HashSet();
#OneToMany(mappedBy="a") //SimpleFile.a is owning side of association
private Set<SimpleFile> comments = new HashSet();
}
Than I have my repository, which exposes nice crud interface using HAL+json representation. I am trying to enrich it with some projection/view particularly due to web UI to load one page data in single request. I am aware of excerps and projections, but they seems not to be enough powerful.
#Repository
#RepositoryRestResource
#Transactional(readOnly = true)
public interface ARepository extends PagingAndSortingRepository<A, Long> {
Page<A> findByNameContaining(String namePart, Pageable pageable);
#Query(
value = "SELECT a.name,\n" +
"(SELECT CAST(count(ac.id) AS int) FROM COMMENTS ac WHERE ac.a_id = a.id),\n" +
"(SELECT listagg(asf.id) FROM SIMPLE_FILES asf WHERE asf.a_id = a.id)\n" +
"FROM AS a\n" +
"WHERE a.id = :id",
nativeQuery = true
)
Optional<ACustomPage42DTO> getByIdProjectedForScreen42(Long id);
}
I have also tried to use JPQL, but there I had problem with fetch join (as I am not familiar with JPQL). My last evaluation query was something like this:
#Query("SELECT new sk.qpp.qqq.documents.projections.ACustomPage42DTO(" +
"a " +
"(SELECT CAST(count(ac) AS int) FROM COMMENTS ac WHERE ac.a = a)" +
")\n" +
"FROM A a\n" +
"LEFT JOIN FETCH a.simpleFiles\n" +
"WHERE a.id = :id"
)
I would like to get some general advice about what approach is best to implement custom and complex query to be returned in DTO (ideally with some specific links to actions when needed).
PS: Implementing interface and returning simple (primitive) data works. Also using JPQL to create custom DAO instance works (with simple types and with single instance of type A for example). Method for using given query method does appear in search methods of given entity endpoint. I would like to have something more reasonable, so I would like to have projection as defined in spring data rest project.
I have my DTO object fully under my control. I prefer it to use #Value or #Data annotation from project lombok, but it is not a need. I have tried also these versions of DTO definition (using interface works for simple data and similarly class works for simple data).
interface ACustomPage42DTO {
String getName();
long getCommentsCount();
Object getAsdf();
}
Or using equivalent class with some bonus, like custom toString() method possible, or some custom getter for computed data:
#Value //lombok thing, imutable "POJO"
public class ACustomPage42DTO {
String name;
long commentsCount;
Set<SimpleFile> simpleFiles;
public ACustomPage42DTO(A a, long count) {
// constructor used by JPQL, if it works
name = a.getName();
this.commentsCount = count;
this.simpleFiles = a.getSimpleFiles(); // should be already fetched, due to fetch join in JPQL
}
}
Both working approaches can be called using "search" url, instead of projection. I see my method getByIdProjectedForScreen42 on url http://localhost:9091/api/a/search listing. I would like to use it like (I think that is the "right" way) http://localhost:8080/api/a?projection=ACustomPage42DTOProjection .
Question is quite broad and touches couple of aspects:
custom JPA repository method using #Query
selecting results in your #Query
mapping #Query results to an interface
exposing new repository method through #RepositoryRestResource
TLDR: wrote an example of what is talked about with couple of basic tests https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
custom JPA repository method using #Query
As you have mentioned it is quite straightforward, just annotate a method with #Query and make sure your return type corresponds to what is being returned from the query, eg:
public interface FooRepository extends JpaRepository<FooEntity, Long> {
#Query(nativeQuery = true, value = "select f from foo f where f.name = :myParam")
Optional<FooEntity> getInSomeAnotherWay(String myParam);
}
selecting results in your #Query
You have given an example already but I'll simplify to make it easier and shorter.
Given entities FooEntity.java and BarEntity.java:
#Entity
#Table(name = "foo")
public class FooEntity {
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "foo")
private Set<BarEntity> bars = new HashSet<>();
// getter setters excluded for brevity
}
#Entity
#Table(name = "bar")
public class BarEntity {
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#ManyToOne(targetEntity = FooEntity.class)
#JoinColumn(name = "foo_id", nullable = false, foreignKey = #ForeignKey(name = "fk_bar_foo"))
private FooEntity foo;
// getter setters excluded for brevity
}
We want now to return custom result set which contains FooEntity.name and count of FooEntity.bars:
SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id
+-----------------+----------+
| name | barCount |
+-----------------+----------+
| Jonny tables | 1 |
+-----------------+----------+
mapping #Query results to an interface
To map above result set we need an interface where getters nicely reflect what is being selected:
public interface ProjectedFooResult {
String getName();
Long getBarCount();
}
Now we can rewrite our repository method to:
#Query(nativeQuery = true,
value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);
exposing new repository method through #RepositoryRestResource
I am not very familiar with this but after adding org.springframework.data:spring-data-rest-hal-browser dependency I got this nice interface that exposed available methods after repository was annotated with #RepositoryRestResource. For a given repository which contains above mentioned details:
#RepositoryRestResource(path = "foo")
public interface FooRepository extends JpaRepository<FooEntity, Long> {
#Query(nativeQuery = true, value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);
}
the method will be exposed through http://localhost:8080/foo/search/getByIdToProjected?id=1 when running locally.
As mentioned above the reference implementation is on Github https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
Additional helpful documentation for 'Custom Implementations for Spring Data Repositories'

hibernate native query complex constructor mapping

Java, Spring Data JPA
I have 2 entities:
class Source {
Integer id;
String name;
}
class Item {
Integer id;
String name;
Integer sourceId;
}
I need statistic native query result like this:
select s.id source_id, s.name source_name, count(i.id) item_count
from source s
left join item i on s.id = i.source_id
group by s.id
And i want to have result in Java object MyResult:
class MyResult {
Source source;
Integer itemCount;
MyResult(Source source, Integer itemCount) {...}
}
The closest solution is using #SqlResultSetMapping like this:
#SqlResultSetMapping(
name = "MyResultMapping",
entities = {
#EntityResult(
entityClass = Source.class,
fields = {
#FieldResult(name = "id", column = "source_id"),
#FieldResult(name = "name", column = "source_name"),
}
),
...
???
}
)
OR
#SqlResultSetMapping(
name = "MyResultMapping",
classes = {
#ConstructorResult(
targetClass = MyResult.class,
columns = {
#ColumnResult(name = "???"),
???
}
)
}
)
With second variant i can use something like this:
MyResult(Integer sourceId, String sourceName, Integer itemsCount) {
this.source = new Source(sourceId, sourceName);
this.itemsCount = itemsCount;
}
but i want it to automate with #SqlResultSetMapping... (because my real objects more complex)
With Spring Data JPA it's better to use projections to achieve you need, for example:
public interface SourceWithItemCount {
Source getSource();
Integer getItemCount();
}
Then in your Source repository create HQL query method, like this:
public interface SourceRepo extends JpaRepository<Source, Integer> {
#Query("select s as source, count(i) like itemCount from Source s left join Item i on i.sourceId = s.id group by s"
List<SourceWithItemCount> getSourcesWithItemCount();
}
Important note is to use aliases for returned values (s as source etc.) it's allows Spring Data JPA to map them to projections properties.
Join on <condition> works from Hibernate version 5.1+ (if I'm not mistaken) so I recommend you to create classic one-to-many relation between your objects, like this, for example:
#Entity
class Source {
#Id private Integer id;
private String name;
#OneToMany #JoinColumn(name = "source_id") private List<Item> items;
}
#Entity
class Item {
#Id private Integer id;
private String name;
}
Then create JPQL query method supported by all versions of Hibernate (and other ORM providers):
#Query("select s as source, count(i) like itemCount from Source s left join s.items i group by s"
List<SourceWithItemCount> getSourcesWithItemCount();

Spring JPA native query with Projection gives "ConverterNotFoundException"

I'm using Spring JPA and I need to have a native query. With that query, I need to get only two fields from the table, so I'm trying to use Projections. It isn't working, this is the error I'm getting:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.IdsOnly]
I tried to follow precisely the instructions of that page I linked, I tried to make my query non-native (do I actually need it to be native if I use projections, btw?), but I always get that error.
If I use an interface it works, but the results are proxies and I really need them to be "normal results" that I can turn into json.
So, here's my code. The Entity:
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#Entity
#Table(name = "TestTable")
public class TestTable {
#Id
#Basic(optional = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "Id")
private Integer id;
#Column(name = "OtherId")
private String otherId;
#Column(name = "CreationDate")
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
#Column(name = "Type")
private Integer type;
}
The class for the projection:
import lombok.Value;
#Value // This annotation fills in the "hashCode" and "equals" methods, plus the all-arguments constructor
public class IdsOnly {
private final Integer id;
private final String otherId;
}
The Repository:
public interface TestTableRepository extends JpaRepository<TestTable, Integer> {
#Query(value = "select Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);
}
And the code that tries to get the data:
#Autowired
TestTableRepository ttRepo;
...
Date theDate = ...
List<Integer> theListOfTypes = ...
...
Collection<IdsOnly> results = ttRepo.findEntriesAfterDate(theDate, theListOfTypes);
Thanks for the help. I really don't understand what I'm doing wrong.
with spring data you can cut the middle-man and simply define
public interface IdsOnly {
Integer getId();
String getOtherId();
}
and use a native query like;
#Query(value = "Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);
check out https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
The query should be using a constructor expression:
#Query("select new com.example.IdsOnly(t.id, t.otherId) from TestTable t where t.creationDate > ?1 and t.type in (?2)")
And i dont know Lombok, but make sure there is a constructor that takes the two IDs as parameters.
JPA 2.1 introduces an interesting ConstructorResult feature if you want to keep it native.
You can return list of Object Array (List) as return type of the native query method in repository class.
#Query(
value = "SELECT [type],sum([cost]),[currency] FROM [CostDetails] " +
"where product_id = ? group by [type],[currency] ",
nativeQuery = true
)
public List<Object[]> getCostDetailsByProduct(Long productId);
for(Object[] obj : objectList){
String type = (String) obj[0];
Double cost = (Double) obj[1];
String currency = (String) obj[2];
}
#Query(value = "select isler.saat_dilimi as SAAT, isler.deger as DEGER from isler where isler.id=:id", nativeQuery = true)
List<Period> getById(#Param("id") Long id);
public interface Period{
Long getDEGER();
Long getSAAT();
}
as seen in the example code for native query given above, cast return values to any value like as "SAAT", "DEGER" and then define interface "period" which have getDEGER() and getSAAT(). Even if I have not understand why parameter after get must be uppercase, in lowercase scenario it didn't work properly. ie. interface with getDeger(), getSaat() does not work properly in my case.

how to write JPA query

Learning how to write JPA query. Please advise me whether it possible to write the below queries more efficiently, may be in a single select statement. May be a join, but not sure how to do it.
class Relationship {
#ManyToOne
public String relationshipType; //can be MANAGER, CUSTOMER etc
#ManyToOne
public Party partyFrom; // a person who has a relation
#ManyToOne
public Party partyTo; // a group a person relate to
}
Queries:
String sql = "";
sql = "select rel.partyTo";
sql += " from Relationship rel";
sql += " where rel.partyFrom = :partyFrom";
sql += " and rel.relationshipType= :typeName";
Query query = Organization.em().createQuery(sql);
query.setParameter("partyFrom", mgr1);
query.setParameter("typeName", "MANAGER");
List<Party> orgList = query.getResultList();
String sql2 = "";
sql2 = "select rel.partyFrom";
sql2 += " from Relationship rel";
sql2 += " where rel.partyTo = :partyToList";
sql2 += " and rel.relationshipType = :typeName2";
Query query2 = Organization.em().createQuery(sql2);
query2.setParameter("partyToList", orgList);
query2.setParameter("typeName2", "CUSTOMER");
List<Party> personList2 = query2.getResultList();
Both the queries work. Query 1 returns a list of groups, where the person (mgr1) has a relation MANAGER with. Query 2 returns all the Persons they are CUSTOMER to the groups returned by query 1. In effect, I get a list of Person they are belong to (customer) the same group where the Person (mgr1) has a relation MANAGER with.
Is it possible to combine them into single sql statement so possibly only one db access?
You literally nest one query inside the other, and use a "where in" clause to specify that the outer query should fetch customers from the inner query.
select rel2.partyFrom
from Relationship rel2
where rel2.relationshipType = :typeName2 /* customer */
and rel2.partyTo.id in
(select rel.partyTo.id
from Relationship rel
where rel.partyFrom = :partyFrom
and rel.relationshipType = :typeName)
Your calling code passes typeName, typeName2, and partyFrom parameters as before. PartyTo parameter is not needed, since the data comes from the subselect (inner query.)
You can achieve the same thing using a self join, with a where clause that filters managers on the left side, and customers on the right side, but using an 'in' clause is semantically clearer.
EDIT: I addded .id to the subselect, which I think is needed.
This is not answer to question but helping other folks in case if someone looking into #OneToMany relation in Spring Data JPA using JPQL, because the question is related to JPA so thought to share my 2-cents, apologize in advance
#Entity
#Table(name = "MY_CAR")
public class MyCar {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "MY_CAR_NUMBER")
private String myCarNumber;
#Column(name = "RELEASE_DATE")
private Date releaseDate;
#OneToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "MY_CAR_VEHICLE_SERIES", joinColumns = #JoinColumn(name = "MY_CAR_ID "), inverseJoinColumns = #JoinColumn(name = "VEHICLE_SERIES_ID"))
private Set<VehicleSeries> vehicleSeries;
public MyCar() {
super();
vehicleSeries = new HashSet<VehicleSeries>();
}
// set and get method goes here
#Entity
#Table(name = "VEHICLE_SERIES ")
public class VehicleSeries {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "SERIES_NUMBER")
private String seriesNumber;
#OneToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "VEHICLE_SERIES_BODY_TYPE", joinColumns = #JoinColumn(name = "VEHICLE_SERIES_ID"), inverseJoinColumns = #JoinColumn(name = "BODY_TYPE_ID"))
private Set<BodyType> bodyTypes;
public VehicleSeries() {
super();
bodyTypes = new HashSet<BodyType>();
}
// set and get method goes here
#Entity
#Table(name = "BODY_TYPE ")
public class BodyType implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "NAME")
private String name;
// set and get method goes here
public interface MyCarRepository extends JpaRepository<MyCar, Long> {
public Set<MyCar> findAllByOrderByIdAsc();
#Query(value = "select distinct myCar from MyCar myCar "
+ "join myCar.vehicleSeries as vs join vs.bodyTypes as bt where vs.seriesNumber like %:searchMyCar% "
+ "or lower(bt.name) like lower(:searchMyCar) or myCar.bulletinId like %:searchMyCar% "
+ "or lower(myCar.description) like lower(:searchMyCar) "
+ "or myCar.bulletinNumber like %:searchMyCar% order by myCar.id asc")
public Set<MyCar> searchByMyCar(#Param("searchMyCar") String searchMyCar);
}
Some data in tables like
Select * from Vehicle_Series
ID SERIES_NUMBER
1 Yaris
2 Corolla
Select * from Body_Type
ID NAME
1 Compact
2 Convertible
3 Sedan

Categories