Spring Data JPA/Hibernate - using EntityManager for queries - java

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 :)

Related

Map any collection with NamedNativeQuery and SqlResultSetMapping

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.

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();

SQLResultSetMapping is throwing exception in case of null columns

I've got a value object for which i want to map my native query resultset.
I'm using #SQLResultSetMapping to map object fields but when value of any column is null, exception is throw:
Exception [EclipseLink-6177] (Eclipse Persistence Services - 2.6.1.v20150916-55dc7c3): org.eclipse.persistence.exceptions.QueryException
Exception Description: The column result [custom_properties] was not found in the results of the query.
My Entity Class and Mapping
#Entity
#Multitenant(MultitenantType.TABLE_PER_TENANT)
#Table(name = "account_master")
#SqlResultSetMapping(name = "DTO_MAPPING", classes = #ConstructorResult(
targetClass = AccountDTO.class,
columns = {#ColumnResult(name = "id"),
#ColumnResult(name = "name"),
#ColumnResult(name = "custom_properties")
})
)
public class Account implements Serializable {
// fields, setters and getters
}
Value Object:
public class AccountDTO {
public AssetDTO(){
}
public AssetDTO(int id, String name, String customProperties) {
this.id = id;
this.name = name;
this.customProperties = customProperties;
}
}
And the execution statement
List<AccountDTO> accList = entityManager.createNativeQuery("SELECT id, name, custom_properties FROM account_master WHERE acc_hierarchy <# 2.3", "DTO_MAPPING").getResultList()
If custom_properties (which is nullable) is replaced by a static value in query, mapping works perfectly fine. Is there something wrong with the implementation? As mapping null value seems like a common scenario.
This was submitted as a bug in EclipseLink: https://bugs.eclipse.org/bugs/show_bug.cgi?id=484276
As a workaround, you can specify the type in the
#ColumnResult
annotation.
So you should change your code to:
#Entity
#Multitenant(MultitenantType.TABLE_PER_TENANT)
#Table(name = "account_master")
#SqlResultSetMapping(name = "DTO_MAPPING", classes = #ConstructorResult(
targetClass = AccountDTO.class,
columns = {#ColumnResult(name = "id"),
#ColumnResult(name = "name"),
#ColumnResult(name = "custom_properties", type=String.class)
})
)
public class Account implements Serializable {
// fields, setters and getters
}

How to use CriteriaQuery for ElementCollection and CollectionTable

I have a very simple entity Product which has a code, name and tags. Tags are stored in another table (product_tag) with product_id and tag columns.
I need to search for products with certain tags using CriteriaQuery. To give an example I want to find products having 'fruit' and 'red' tags.
Using spring 4.1.x, spring-data-jpa 1.8 and hibernate 4.2.x.
My entity simply is;
#Entity
#Table(name = "product", uniqueConstraints ={
#UniqueConstraint(columnNames = "code")
}
)
#NamedQueries({
#NamedQuery(name = "Product.findAll", query = "select p from Product p")
})
public class Product extends EntityWithId {
#Column(name = "code", length = 128)
private String code;
#Column(name = "name", length = 512)
protected String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name="product_tag", joinColumns=#JoinColumn(name="product_id"))
#Column(name="tag")
private Set<String> productTags = new HashSet<>();
}
here is the code how I initiate the search;
private void search() {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> criteriaQuery = builder.createQuery(Product.class);
Root<Product> product = criteriaQuery.from(Product.class);
Predicate where = builder.conjunction();
if (!StringUtils.isEmpty(nameSearch.getValue())) {
where = builder.and(where, builder.like(product.<String>get("name"), nameSearch.getValue() + "%"));
}
if (!StringUtils.isEmpty(codeSearch.getValue())) {
where = builder.and(where, builder.like(product.<String>get("code"), codeSearch.getValue() + "%"));
}
if (!StringUtils.isEmpty(tagsSearch.getValue())) {
//Util.parseCommaSeparated returns Set<String>
where = builder.and(where, product.get("productTags").in(Util.parseCommaSeparated(tagsSearch.getValue())));
}
criteriaQuery.where(where);
List<Product> resultList = entityManager.createQuery(criteriaQuery).getResultList();
}
However when I run the search for tags 'fruit' I get an exception
java.lang.IllegalArgumentException: Parameter value [fruit] did not match expected type [java.util.Set (n/a)]
I really wonder to use CriteriaQuery for ElementCollection and CollectionTable.
productTags is mapped to a separate table, therefore you need to join with that table in your query.
...
if (!StringUtils.isEmpty(tagsSearch.getValue())) {
//Util.parseCommaSeparated returns Set<String>
where = builder.and(where, product.join("productTags").in(Util.parseCommaSeparated(tagsSearch.getValue())));
}
...
Note the product.join("productTags") instead of product.get("productTags")
Try to use isMember() rather than in()
Check the example 5 and 7

How to map the result set of a JPA NativeQuery to a POJO using SqlResultSetMapping

I am attempting to map the results of a Native query to a POJO using #SqlResultSetMapping with #ConstructorResult. Here is my code:
#SqlResultSetMapping(name="foo",
classes = {
#ConstructorResult(
targetClass = Bar.class,
columns = {
#ColumnResult(name = "barId", type = Long.class),
#ColumnResult(name = "barName", type = String.class),
#ColumnResult(name = "barTotal", type = Long.class)
})
})
public class Bar {
private Long barId;
private String barName;
private Long barTotal;
...
And then in my DAO:
Query query = em.createNativeQueryBar(QUERY, "foo");
... set some parameters ...
List<Bar> list = (List<Bar>) query.getResultList();
I have read that this functionality is only supported in JPA 2.1, but that is what I am using. Here's my dependency:
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
I found a couple of resources, including this one: #ConstructorResult mapping in jpa 2.1. But I am still not having any luck.
What am I missing? Why can't the SqlResultSetMapping be found?
javax.persistence.PersistenceException: org.hibernate.MappingException: Unknown SqlResultSetMapping [foo]
#SqlResultSetMapping annotation should not be put on a POJO. Put it at (any) #Entity class. "Unknown SqlResultSetMapping [foo]" tells you, that JPA provider doesn't see any mapping under name 'foo'. Please see another answer of mine for the correct example
JPA- Joining two tables in non-entity class
Short working example:
DTO POJO class
#lombok.Getter
#lombok.AllArgsConstructor
public class StatementDto {
private String authorName;
private Date createTime;
}
Repository bean:
#Repository
public class StatementNativeRepository {
#PersistenceContext private EntityManager em;
static final String STATEMENT_SQLMAP = "Statement-SQL-Mapping";
public List<StatementDto> findPipelinedStatements() {
Query query = em.createNativeQuery(
"select author_name, create_time from TABLE(SomePipelinedFun('xxx'))",
STATEMENT_SQLMAP);
return query.getResultList();
}
#SqlResultSetMapping(name= STATEMENT_SQLMAP, classes = {
#ConstructorResult(targetClass = StatementDto.class,
columns = {
#ColumnResult(name="author_name",type = String.class),
#ColumnResult(name="create_time",type = Date.class)
}
)
}) #Entity class SQLMappingCfgEntity{#Id int id;} // <- workaround
}
I able to do it this way:
Session session = em().unwrap(Session.class);
SQLQuery q = session.createSQLQuery("YOUR SQL HERE");
q.setResultTransformer( Transformers.aliasToBean( MyNotMappedPojoClassHere.class) );
List<MyNotMappedPojoClassHere> postList = q.list();
Domain Model
Let's consider we have the following post and post_comment tables in our database:
JPA SqlResultSetMapping
The SqlResultSetMapping JPA annotation looks as follows:
#Repeatable(SqlResultSetMappings.class)
#Target({TYPE})
#Retention(RUNTIME)
public #interface SqlResultSetMapping {
String name();
EntityResult[] entities() default {};
ConstructorResult[] classes() default {};
ColumnResult[] columns() default {};
}
The SqlResultSetMapping annotation is repeatable and is applied at the entity class level. Apart from taking a unique name, which is used by Hibernate to register the mapping, there are three mapping options:
EntityResult
ConstructorResult
ColumnResult
Next, we are going to see how al these three mapping options work, as well as the use cases where you will need to use them.
JPA SqlResultSetMapping - EntityResult
The EntityResult option allows you to map the JDBC ResultSet columns to one or more JPA entities.
Let's assume we want to fetch the first 5 Post entities along with all their associated PostComment entities that match a given title pattern.
As I explained in this article, we can use the DENSE_RANK SQL Window Function to know how to filter the post and post_comment joined records, as illustrated by the following SQL query:
SELECT *
FROM (
SELECT
*,
DENSE_RANK() OVER (
ORDER BY
"p.created_on",
"p.id"
) rank
FROM (
SELECT
p.id AS "p.id", p.created_on AS "p.created_on",
p.title AS "p.title", pc.post_id AS "pc.post_id",
pc.id as "pc.id", pc.created_on AS "pc.created_on",
pc.review AS "pc.review"
FROM post p
LEFT JOIN post_comment pc ON p.id = pc.post_id
WHERE p.title LIKE :titlePattern
ORDER BY p.created_on
) p_pc
) p_pc_r
WHERE p_pc_r.rank <= :rank
However, we don't want to return a list of scalar column values. We want to return JPA entities from this query, so we need to configure the entities attribute of the #SqlResultSetMapping annotation, like this:
#NamedNativeQuery(
name = "PostWithCommentByRank",
query = """
SELECT *
FROM (
SELECT
*,
DENSE_RANK() OVER (
ORDER BY
"p.created_on",
"p.id"
) rank
FROM (
SELECT
p.id AS "p.id", p.created_on AS "p.created_on",
p.title AS "p.title", pc.post_id AS "pc.post_id",
pc.id as "pc.id", pc.created_on AS "pc.created_on",
pc.review AS "pc.review"
FROM post p
LEFT JOIN post_comment pc ON p.id = pc.post_id
WHERE p.title LIKE :titlePattern
ORDER BY p.created_on
) p_pc
) p_pc_r
WHERE p_pc_r.rank <= :rank
""",
resultSetMapping = "PostWithCommentByRankMapping"
)
#SqlResultSetMapping(
name = "PostWithCommentByRankMapping",
entities = {
#EntityResult(
entityClass = Post.class,
fields = {
#FieldResult(name = "id", column = "p.id"),
#FieldResult(name = "createdOn", column = "p.created_on"),
#FieldResult(name = "title", column = "p.title"),
}
),
#EntityResult(
entityClass = PostComment.class,
fields = {
#FieldResult(name = "id", column = "pc.id"),
#FieldResult(name = "createdOn", column = "pc.created_on"),
#FieldResult(name = "review", column = "pc.review"),
#FieldResult(name = "post", column = "pc.post_id"),
}
)
}
)
With the SqlResultSetMapping in place, we can fetch the Post and PostComment entities like this:
List<Object[]> postAndCommentList = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setParameter("rank", POST_RESULT_COUNT)
.getResultList();
And, we can validate that the entities are properly fetched:
assertEquals(
POST_RESULT_COUNT * COMMENT_COUNT,
postAndCommentList.size()
);
for (int i = 0; i < COMMENT_COUNT; i++) {
Post post = (Post) postAndCommentList.get(i)[0];
PostComment comment = (PostComment) postAndCommentList.get(i)[1];
assertTrue(entityManager.contains(post));
assertTrue(entityManager.contains(comment));
assertEquals(
"High-Performance Java Persistence - Chapter 1",
post.getTitle()
);
assertEquals(
String.format(
"Comment nr. %d - A must read!",
i + 1
),
comment.getReview()
);
}
The #EntityResult is also useful when fetching JPA entities via SQL stored procedures. Check out this article for more details.
JPA SqlResultSetMapping - ConstructorResult
Let's assume we want to execute an aggregation query that counts the number of post_coment records for each post and returns the post title for reporting purposes. We can use the following SQL query to achieve this goal:
SELECT
p.id AS "p.id",
p.title AS "p.title",
COUNT(pc.*) AS "comment_count"
FROM post_comment pc
LEFT JOIN post p ON p.id = pc.post_id
GROUP BY p.id, p.title
ORDER BY p.id
We also want to encapsulate the post title and the comment count in the following DTO:
public class PostTitleWithCommentCount {
private final String postTitle;
private final int commentCount;
public PostTitleWithCommentCount(
String postTitle,
int commentCount) {
this.postTitle = postTitle;
this.commentCount = commentCount;
}
public String getPostTitle() {
return postTitle;
}
public int getCommentCount() {
return commentCount;
}
}
To map the result set of the above SQL query to the PostTitleWithCommentCount DTO, we can use the classes attribute of the #SqlResultSetMapping annotation, like this:
#NamedNativeQuery(
name = "PostTitleWithCommentCount",
query = """
SELECT
p.id AS "p.id",
p.title AS "p.title",
COUNT(pc.*) AS "comment_count"
FROM post_comment pc
LEFT JOIN post p ON p.id = pc.post_id
GROUP BY p.id, p.title
ORDER BY p.id
""",
resultSetMapping = "PostTitleWithCommentCountMapping"
)
#SqlResultSetMapping(
name = "PostTitleWithCommentCountMapping",
classes = {
#ConstructorResult(
columns = {
#ColumnResult(name = "p.title"),
#ColumnResult(name = "comment_count", type = int.class)
},
targetClass = PostTitleWithCommentCount.class
)
}
)
The ConstructorResult annotation allows us to instruct Hibernate what DTO class to use as well as which constructor to be called when instantiating the DTO objects.
Note that we used the type attribute of the #ColumnResult annotation to specify that the comment_count should be cast to a Java int. This is needed since some JDBC drivers use either Long or BigInteger for the SQL aggregation function results.
This is how you can call the PostTitleWithCommentCount named native query using JPA:
List<PostTitleWithCommentCount> postTitleAndCommentCountList = entityManager
.createNamedQuery("PostTitleWithCommentCount")
.setMaxResults(POST_RESULT_COUNT)
.getResultList();
And, we can see that the returned PostTitleWithCommentCount DTOs have been fetched properly:
assertEquals(POST_RESULT_COUNT, postTitleAndCommentCountList.size());
for (int i = 0; i < POST_RESULT_COUNT; i++) {
PostTitleWithCommentCount postTitleWithCommentCount =
postTitleAndCommentCountList.get(i);
assertEquals(
String.format(
"High-Performance Java Persistence - Chapter %d",
i + 1
),
postTitleWithCommentCount.getPostTitle()
);
assertEquals(COMMENT_COUNT, postTitleWithCommentCount.getCommentCount());
}
For more details about the best way to fetch DTO projections with JPA and Hibernate, check out this article.
JPA SqlResultSetMapping - ColumnResult
The previous example showed how we could map the SQL aggregation result set to a DTO. But, what if we want to return the JPA entity for which we are counting the comments?
To achieve this goal we can use the entities attribute to define the Post entity we are fetching, and the classes attribute of the #SqlResultSetMapping annotation to map the scalar value, which in our case is the number of associated post_comment records:
#NamedNativeQuery(
name = "PostWithCommentCount",
query = """
SELECT
p.id AS "p.id",
p.title AS "p.title",
p.created_on AS "p.created_on",
COUNT(pc.*) AS "comment_count"
FROM post_comment pc
LEFT JOIN post p ON p.id = pc.post_id
GROUP BY p.id, p.title
ORDER BY p.id
""",
resultSetMapping = "PostWithCommentCountMapping"
)
#SqlResultSetMapping(
name = "PostWithCommentCountMapping",
entities = #EntityResult(
entityClass = Post.class,
fields = {
#FieldResult(name = "id", column = "p.id"),
#FieldResult(name = "createdOn", column = "p.created_on"),
#FieldResult(name = "title", column = "p.title"),
}
),
columns = #ColumnResult(
name = "comment_count",
type = int.class
)
)
When executing the PostWithCommentCount named native query:
List<Object[]> postWithCommentCountList = entityManager
.createNamedQuery("PostWithCommentCount")
.setMaxResults(POST_RESULT_COUNT)
.getResultList();
we will get both the Post entity and the commentCount scalar column value:
assertEquals(POST_RESULT_COUNT, postWithCommentCountList.size());
for (int i = 0; i < POST_RESULT_COUNT; i++) {
Post post = (Post) postWithCommentCountList.get(i)[0];
int commentCount = (int) postWithCommentCountList.get(i)[1];
assertTrue(entityManager.contains(post));
assertEquals(i + 1, post.getId().intValue());
assertEquals(
String.format(
"High-Performance Java Persistence - Chapter %d",
i + 1
),
post.getTitle()
);
assertEquals(COMMENT_COUNT, commentCount);
}
The problem with adding #Entity to your DTO POJO is that it will create a table in your db that you don't need. Having to add #Id and a transient keyword to necessary fields is also a hassle. A simple solution is to move your #SqlResultSetMapping to an abstract class.
#MappedSuperclass
#SqlResultSetMapping(name="foo",
classes = {
#ConstructorResult(
targetClass = Bar.class,
columns = {
#ColumnResult(name = "barId", type = Long.class),
#ColumnResult(name = "barName", type = String.class),
#ColumnResult(name = "barTotal", type = Long.class)
})
})
public abstract class sqlMappingCode {}
Don't forget to add #MappedSuperclass. This will ensure Hibernate auto-wires your mapping.
Update: this functionality is broken in Hibernate 5.4.30.Final, Jira ticket created HHH-14572
Update 2: It looks like this functionality is permanently broken after 5.4.30.Final. New solution, you will need to add an #Entity with a table name, then create two classes:
public class CustomSchemaFilterProvider implements SchemaFilterProvider {
// implement code, use CustomSchemaFilter below
}
then create a singleton custom filter
public class CustomSchemaFilter implements SchemaFilter {
// implement code, check for table names you don't want in your schema
}
in your JPA Config file set:
jpaProp.put(Environment.HBM2DDL_FILTER_PROVIDER, "com.your.package.CustomSchemaFilterProvider");
// OR
hibernate.hbm2ddl.schema_filter_provider=com.your.package.CustomSchemaFilterProvider
this will ensure that your new #Entity is ignored by hibernate during mapping
tested with hibernate 5.6.9.Final
#Entity
#SqlResultSetMapping(name="ConnexionQueryBean",
entities={
#EntityResult(entityClass=com.collecteJ.business.bean.ConnexionQueryBean.class, fields={
#FieldResult(name="utilisateurId", column="UTILISATEUR_ID"),
#FieldResult(name="nom", column="NOM"),
#FieldResult(name="prenom", column="PRENOM"),
#FieldResult(name="nomConnexion", column="NOM_CONNEXION"),
#FieldResult(name="codeAgence", column="CODE_AGENCE"),
#FieldResult(name="codeBanque", column="CODE_BANQUE"),
#FieldResult(name="codeDevise", column="CODE_DEVISE"),
#FieldResult(name="codeCollecteur", column="CODE_COLLECTEUR")})
})
public class ConnexionQueryBean implements Serializable {
#Id
private long utilisateurId;
private String codeCollecteur;
private String nom;
private String prenom;
private String nomConnexion;
private String codeAgence;
private String codeBanque;
private String codeDevise;
public ConnexionQueryBean() {
}
public long getUtilisateurId() {
return utilisateurId;
}
public void setUtilisateurId(long utilisateurId) {
this.utilisateurId = utilisateurId;
}
public String getCodeCollecteur() {
return codeCollecteur;
}
public void setCodeCollecteur(String codeCollecteur) {
this.codeCollecteur = codeCollecteur;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public String getNomConnexion() {
return nomConnexion;
}
public void setNomConnexion(String nomConnexion) {
this.nomConnexion = nomConnexion;
}
public String getCodeAgence() {
return codeAgence;
}
public void setCodeAgence(String codeAgence) {
this.codeAgence = codeAgence;
}
public String getCodeBanque() {
return codeBanque;
}
public void setCodeBanque(String codeBanque) {
this.codeBanque = codeBanque;
}
public String getCodeDevise() {
return codeDevise;
}
public void setCodeDevise(String codeDevise) {
this.codeDevise = codeDevise;
}
#Override
public String toString() {
return "ConnexionQueryBean{" + "utilisateurId=" + utilisateurId + ", codeCollecteur=" + codeCollecteur + ", nom=" + nom + ", prenom=" + prenom + ", nomConnexion=" + nomConnexion + ", codeAgence=" + codeAgence + ", codeBanque=" + codeBanque + ", codeDevise=" + codeDevise + '}';
}
This is not really an entity as it does not match any database table. But the #Entity and the #Id annotations are compulsories for JPA to understand the mapping. If you don't really want to have #Entity / #Id in that class, you can remove the #SqlResultSetMapping annotation and put it in any other entity as far as JPA can scan it.
You should also make sure that your #ComponentScan content the corresponding package, if you are using a java based spring configuration, you should explicitly declare your entity in the persistence.xml/orm.xml under the META-INF directory.
This is the call
String connexionQuery = "SELECT u.UTILISATEUR_ID, u.NOM, u.PRENOM, u.NOM_CONNEXION, a.CODE_AGENCE, a.CODE_BANQUE, a.CODE_DEVISE, c.CODE_COLLECTEUR FROM UTILISATEUR u, AGENCE a, COLLECTEUR c "
+ " WHERE (a.CODE_AGENCE = c.CODE_AGENCE AND u.UTILISATEUR_ID = c.UTILISATEUR_ID AND u.NOM_CONNEXION = '"+nomConnextion+"')";
ConnexionQueryBean ConnexionResults = (ConnexionQueryBean) defaultService.getEntityManager().createNativeQuery(connexionQuery,"ConnexionQueryBean").getSingleResult();
System.out.println(ConnexionResults.toString());
I'm using Spring, JPA 2.1, Hibernate 5 and Oracle, i think this might not be possible with JPA lower version, find more http://www.thoughts-on-java.org/result-set-mapping-complex-mappings/
I have a slightly varied answer which is just derived from wildloop's answer.
Here is my answer:
Constants class: Constants.java
public class Constants {
public final String TESTQUERYRESULT_MAPPING_NAME = "TestQueryResultMapping";
}
Result Mapping Class: TestQueryResult.java
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.EntityResult;
import javax.persistence.FieldResult;
import javax.persistence.Id;
import javax.persistence.SqlResultSetMapping;
#Getter
#Setter
#SqlResultSetMapping(
//name = "TestQueryResultMapping"
name = Constants.TESTQUERYRESULT_MAPPING_NAME
,entities = #EntityResult(
entityClass = TestQueryResult.class
,fields = {
#FieldResult(name = "rowId", column = "row_id")
,#FieldResult(name = "rowName", column = "row_name")
,#FieldResult(name = "value", column = "row_value")
}
)
)
#Entity
public class TestQueryResult {
#Id
private Integer rowId;
private String rowName;
private String value;
}
Then... somewhere in my Repository Implementation code:
public class TestQueryRepository {
//... some code here to get the entity manager here
public TestQueryResult getTopMost(Integer rowName) {
//... some code here
String queryString = "... some query string here" + rowName;
TestQueryResult testQueryResult = null;
//this.entityManager.createNativeQuery(queryString ,"TestQueryResultMapping").getResultList();
List<TestQueryResult> results = this.entityManager.createNativeQuery(queryString ,Constants.TESTQUERYRESULT_MAPPING_NAME).getResultList();
if (results != null && !results.isEmpty()) {
testQueryResult = results.get(0);
}
return testQueryResult;
}
}
... then violah! I got some results :D!
Cheers,
Artanis Zeratul
QLRM could be a alternative: http://simasch.github.io/qlrm/
It is not related to a specific JPA implementation and also works with JDBC.
I recently put together a quick Proof of concept which worked quite well for myself and another developer.
A Spring boot endpoint which uses a Native Query and makes use of SqlResultSetMapping, here are the code snippets.
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.metrics.annotation.Timed;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
#RestController
#RequestMapping(value = "/rest/yum-query", produces = APPLICATION_JSON_VALUE)
#Api(tags = {"notification"})
#Timed(extraTags = {"controller", "YumController"})
public class YumController {
private final YumService yumService;
#Autowired
public YumController(YumService yumService) {
this.yumService = yumService;
}
#GetMapping(produces = APPLICATION_JSON_VALUE)
public List<ApprovedApplicationsDTO> findApprovedApplications() {
return yumService.findApprovedApplications();
}
}
Add my Service
import au.edu.qld.qcaa.sate.serviceaara.domain.repository.YumRepositoryCustom;
import au.edu.qld.qcaa.sate.serviceaara.dto.yum.ApprovedApplicationsDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
#Slf4j
#Service
public class YumService {
private YumRepositoryCustom yumRepositoryCustom;
#Autowired
public YumService(YumRepositoryCustom yumRepositoryCustom) {
this.yumRepositoryCustom = yumRepositoryCustom;
}
public List<ApprovedApplicationsDTO> findApprovedApplications() {
return yumRepositoryCustom.findApprovedApplicationsNativeQuery();
}
}
Setup a new Query Result Class for the native query to map to
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.SqlResultSetMapping;
import java.math.BigInteger;
#MappedSuperclass
#SqlResultSetMapping(name = "ApprovedApplicationQueryResultBean",
classes = {
#ConstructorResult(
targetClass = ApprovedApplicationQueryResultBean.class,
columns = {
#ColumnResult(name = "application_id", type = BigInteger.class),
#ColumnResult(name = "application_type", type = String.class),
#ColumnResult(name = "assessment_identifier", type = String.class),
#ColumnResult(name = "aara_code", type = String.class),
#ColumnResult(name = "aara_code_child", type = String.class),
#ColumnResult(name = "completion_year", type = Integer.class),
#ColumnResult(name = "reason", type = String.class),
#ColumnResult(name = "code", type = String.class),
#ColumnResult(name = "long_description", type = String.class),
#ColumnResult(name = "identified_condition", type = String.class),
#ColumnResult(name = "other", type = String.class),
#ColumnResult(name = "decision_code", type = String.class),
}
)
})
#NoArgsConstructor
#AllArgsConstructor
#Data
public class ApprovedApplicationQueryResultBean {
#Id
private BigInteger applicationId;
private String applicationType;
private String assessmentIdentifier;
private String aaraCode;
private String aaraCodeChild;
private Integer completionYear;
private String reason;
private String code;
private String longDescription;
private String identifiedCondition;
private String other;
private String decisionCode;
}
Creating a new Repository Interface and Implementation class to invoke the query from
import au.edu.qld.qcaa.sate.serviceaara.dto.yum.ApprovedApplicationsDTO;
import java.util.List;
public interface YumRepositoryCustom {
List<ApprovedApplicationsDTO> findApprovedApplicationsNativeQuery();
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
#Component
#Slf4j
public class YumRepositoryCustomImpl implements YumRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<ApprovedApplicationsDTO> findApprovedApplicationsNativeQuery() {
StringBuilder q = new StringBuilder();
q.append("select distinct a4.application_id, a4.application_type, a3.assessment_identifier, ");
q.append("a3.aara_code, ad.aara_code_child, a3.completion_year, c.reason, rd.code, rd.long_description, ic.identified_condition, ic.other, ad2.decision_code ");
q.append("from category c ");
q.append("left join application a4 on a4.application_id = c.application_id ");
q.append("left join arrangement a3 on a3.application_id = a4.application_id ");
q.append("left join arrangement_detail ad on a3.arrangement_id = ad.arrangement_id ");
q.append("left join identified_condition ic on ic.category_id = c.category_id ");
q.append("left join reference_data rd on rd.code = c.reason ");
q.append("left join arrangement_decision ad2 on ad2.arrangement_id = a3.arrangement_id ");
q.append("left JOIN (SELECT max(ad3.arrangement_decision_id) as theid ");
q.append("FROM arrangement_decision ad3 ");
q.append("GROUP BY ad3.arrangement_id) b on ad2.arrangement_decision_id = b.theid ");
q.append("where a4.application_type = ?1 and a3.completion_year = ?2 ");
q.append("and a4.is_active = true and a3.is_active = true and ic.is_active = true and c.is_active = true ");
q.append("order by 1 ");
Query query = em.createNativeQuery(q.toString(), "ApprovedApplicationQueryResultBean");
query.setParameter(1, ApplicationConstants.APPLICATION_TYPE_AARA);
query.setParameter(2, Calendar.getInstance().get(Calendar.YEAR));
List<ApprovedApplicationQueryResultBean> entityResults = query.getResultList();
return entityResults.stream().map(entity -> {
return mapToDTO(entity);
}).collect(Collectors.toList());
}
private ApprovedApplicationsDTO mapToDTO(ApprovedApplicationQueryResultBean entity) {
return ApprovedApplicationsDTO.builder()
.applicationId(entity.getApplicationId())
.applicationType(entity.getApplicationType())
.aaraCode(entity.getAaraCode())
.aaraCodeChild(entity.getAaraCodeChild())
.completionYear(entity.getCompletionYear())
.reason(entity.getReason())
.code(entity.getCode())
.longDescription(entity.getLongDescription())
.identifiedCondition(entity.getIdentifiedCondition())
.other(entity.getOther())
.decisionCode(entity.getDecisionCode())
.build();
}
}
Add my endpoint response DTO
import lombok.Builder;
import lombok.Data;
import java.math.BigInteger;
#Data
#Builder
public class ApprovedApplicationsDTO {
private BigInteger applicationId;
private String applicationType;
private String assessmentIdentifier;
private String aaraCode;
private String aaraCodeChild;
private Integer completionYear;
private String reason;
private String code;
private String longDescription;
private String identifiedCondition;
private String other;
private String decisionCode;
}
Then hit my endpoint via postman or curl or which ever tool you wish to use:
http://localhost:10050/rest/yum-query
This solution is independent of JPA implementation. Once you collect result of native query as
List<Object[]> = em.createNativeQueryBar(QUERY, "foo").getResultList();
and if you need to map each List element to a Person e.g.
Class Person { String name; Int age; }
where List element[0] is name and element [ 1] is age.
You can convert Object[] to Person using ObjectMapper. You need to add #JsonFormat annotation on the class.
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
Class Person { String name; Int age; }
and convert Object[] to Person as
ObjectMapper mapper = new ObjectMapper();
JSONArray jsonArray = new JSONArray(Arrays.asList(attributes)).toString();
Person person = mapper.readValue(jsonArray, Person.class);
#MappedSuperclass
#SqlResultSetMapping(name="foo",
classes = {
#ConstructorResult(
targetClass = Bar.class,
columns = {
#ColumnResult(name = "barId", type = Long.class),
#ColumnResult(name = "barName", type = String.class),
#ColumnResult(name = "barTotal", type = Long.class)
})
})
You can do it like this, but this is not very recommended because it is not very comfortable. You can do it like below.
Query query = em.createNativeQueryBar(QUERY);
... set some parameters ...
List<Object[]> result = query.getResultList();
List<Bar> list = result.stream().map(
objects -> {
Bar bar = new Bar();
bar.setBarId(Long.parseLong(objects[0].toString()));
bar.setBarName(objects[1].toString());
bar.setBarTotal(Long.parseLong(objects[2].toString()));
...
return bar;
}
).collect(Collectors.toList());

Categories