How to load #ElementCollection within custom query? - java

I want to reduce the amount of querys run by spring. When getting an object with #ElementCollection via SQL I want to get the data for the ElementCollections directly via a JOIN within the quers.
The attribute with the ElementCollection
#ElementCollection(fetch = FetchType.EAGER)
#JoinTable(name = "song_genre_list")
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<String> genre;
The Method that uses a custom string:
#Query(
value = "select distinct s.*, g.* from musicdb.songs s left join musicdb.song_genre_list g on s.id = g.song_id where s.name like ?1 or s.artist like ?1",
nativeQuery = true)
List<Song> searchSong(String title);
How I would imagine defining a query that also loads this element collection:
SELECT DISTINCT s.*, g.* FROM musicdb.songs s LEFT JOIN musicdb.song_genre_list g ON s.id = g.song_id WHERE s.name LIKE ?1 OR s.artist LIKE ?1
What Spring currently does (loading the genres for 3 songs with more querys):
Hibernate: select distinct s.*, g.* from musicdb.songs s left join musicdb.song_genre_list g on s.id = g.song_id where s.name like ?1 or s.artist like ?1
Hibernate: select genre0_.song_id as song_id1_4_0_, genre0_.genre as genre2_4_0_ from musicdb.song_genre_list genre0_ where genre0_.song_id=?
Hibernate: select genre0_.song_id as song_id1_4_0_, genre0_.genre as genre2_4_0_ from musicdb.song_genre_list genre0_ where genre0_.song_id=?
Hibernate: select genre0_.song_id as song_id1_4_0_, genre0_.genre as genre2_4_0_ from musicdb.song_genre_list genre0_ where genre0_.song_id=?
What I want Spring to do:
Hibernate: select distinct s.*, g.* from musicdb.songs s left join musicdb.song_genre_list g on s.id = g.song_id where s.name like ?1 or s.artist like ?1
The required Data for the ElementCollection is already included with the join. How can I tell spring to import that Data?

FetchType.EAGER will already load the genres for you, so you dont need to write a join in your original query. But for that hibernate hibernate uses separate queries by default. To change this add the annotation '#Fetch(FetchMode.JOIN)' on the genre field

Related

Get data from join table hibernate many to many

I have this diagram:
table diagram
and I want to filter by the employees that have a project.
In normal SQL I will go like this
select * from employees e
join employee_projects ep on ep.employee_id = e.id
How can I achieve the same with Hibernate?
I tried using criteria builder and specifications but I can't get the data from the join table.
You can select all employees that have a project like this
em.createQuery(
"SELECT e FROM Employee e JOIN e.projects p WHERE p IS NOT NULL", Employee.class).getResultList()
You can join tables using join method of root object.
try something like this below
I have used it for one to many relation
Join<Post, Tag> join = root.join("tags", JoinType.INNER);
Predicate tagPredicate = criteriaBuilder.like(join.get("name"), "%" + search + "%");
for many to many relation,
Join<Post, Tag> postTagsTable = root.join("tags", JoinType.INNER);
return postTagsTable.<String>get("name").in(tagNames);
here I have tags field in Post entity, which is used inside join

The problem that jpa custom query does not take effect

I am designing a 'like' function for a blog system, and I want to judge whether the likes have been added/applied according to the user's login status when querying the content of the homepage.
So I have created a SQL statement to implement the left join to the query to achieve this function. I used the JPA #Query jpql method to customize the query, but the effect is different from what I expected.
When looking at the statement, I found that the jpql statement is indeed executed once, but it is an associated query to another/subsequent jpa query after the initial query was executed, so I think it may be overwritten.
I am using jpa for the first time and they may be thing that I do not fully understand, please feel free to ask any further questions to help me and thank you.
This is my expected sql statement:
SELECT a.*,l.*FROM article a LEFT JOIN user_like_record l ON a.id=l.target_id AND l.target_type=0 AND l.user_id=17
This is my current code:
#Query(value = "SELECT\n" +
"\tarticle\n" +
"FROM\n" +
"\tArticle article LEFT JOIN article.userLikeRecord ON article.userLikeRecord.targetType = 0\n" +
"\t AND article.userLikeRecord.userId = :userId")
Page<Article> findAllLikeStatusb(Long userId, Pageable pageable);
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "target_id", insertable = false, updatable = false)
private UserLikeRecord userLikeRecord;
This is the sql statement generated by the last query of jpa:
Hibernate:
select
user0_.id as id1_9_,
user0_.create_time as create_t2_9_,
user0_.delete_time as delete_t3_9_,
user0_.update_time as update_t4_9_,
user0_.email as email5_9_,
user0_.mobile as mobile6_9_,
user0_.nick_name as nick_nam7_9_,
user0_.openid as openid8_9_,
user0_.receive_like_counts as receive_9_9_,
user0_.unify_uid as unify_u10_9_,
user0_.user_birthday as user_bi11_9_,
user0_.wx_profile as wx_prof12_9_
from
user user0_
where
(
user0_.delete_time is null
)
and user0_.openid=?
Hibernate:
SELECT
a.*,
l.*
FROM
article a
LEFT JOIN
user_like_record l
ON a.id = l.target_id
AND l.target_type=0
AND l.user_id =?
order by
a.create_time desc limit ?
The following sql is executed multiple times
Hibernate:
select
userlikere0_.target_id as target_i1_10_0_,
userlikere0_.create_time as create_t2_10_0_,
userlikere0_.delete_time as delete_t3_10_0_,
userlikere0_.update_time as update_t4_10_0_,
userlikere0_.id as id5_10_0_,
userlikere0_.like_status as like_sta6_10_0_,
userlikere0_.target_type as target_t7_10_0_,
userlikere0_.user_id as user_id8_10_0_
from
user_like_record userlikere0_
where
userlikere0_.target_id=?
and (
userlikere0_.delete_time is null
)

Native query changed by hibernate

I am currently trying to execute this method in my repository:
#Query(value = "SELECT a.ID as 'ID', a.STATUS as 'STATUS', a.SOURCE as 'SOURCE', a.EXTERNAL_REFERENCE_ID as 'EXTERNAL_REFERENCE_ID', s.ID as 'STREET_ID', l.ID as 'LOCALITY_ID', c.ID as 'COUNTRY_ID', p.ID as 'POSTCODE_ID', a.INSERTED_TIMESTAMP as 'INSERTED_TIMESTAMP', a.UPDATED_TIMESTAMP as 'UPDATED_TIMESTAMP', a.DELETED_TIMESTAMP as 'DELETED_TIMESTAMP'\n" +
"FROM address_management_svc.subaddress sa\n" +
"INNER JOIN address_management_svc.address a\n" +
"ON (sa.ADDRESS_ID = a.ID)\n" +
"INNER JOIN street s\n" +
"ON (s.id = a.STREET_ID)\n" +
"INNER JOIN locality l\n" +
"ON (l.id = a.LOCALITY_ID)\n" +
"INNER JOIN country c\n" +
"ON (c.id = a.COUNTRY_ID)\n" +
"INNER JOIN post_code p\n" +
"ON (p.id = a.POSTCODE_ID)", nativeQuery = true)
List<AddressEntity> getAll();
I switched on the jpa logging and for some reason, I am getting all of this (below is a sample as in reality I am getting all records like this which is hundreds of thousands)
select
streetenti0_.ID as id1_4_0_,
streetenti0_.DELETED_TIMESTAMP as deleted_2_4_0_,
streetenti0_.EXTERNAL_REFERENCE_ID as external3_4_0_,
streetenti0_.INSERTED_TIMESTAMP as inserted4_4_0_,
streetenti0_.LOCALITY_ID as locality9_4_0_,
streetenti0_.NAME as name5_4_0_,
streetenti0_.SOURCE as source6_4_0_,
streetenti0_.STATUS as status7_4_0_,
streetenti0_.UPDATED_TIMESTAMP as updated_8_4_0_,
localityen1_.ID as id1_2_1_,
localityen1_.COUNTRY_ID as country_9_2_1_,
localityen1_.DELETED_TIMESTAMP as deleted_2_2_1_,
localityen1_.EXTERNAL_REFERENCE_ID as external3_2_1_,
localityen1_.INSERTED_TIMESTAMP as inserted4_2_1_,
localityen1_.NAME as name5_2_1_,
localityen1_.SOURCE as source6_2_1_,
localityen1_.STATUS as status7_2_1_,
localityen1_.UPDATED_TIMESTAMP as updated_8_2_1_,
countryent2_.ID as id1_1_2_,
countryent2_.DELETED_TIMESTAMP as deleted_2_1_2_,
countryent2_.INSERTED_TIMESTAMP as inserted3_1_2_,
countryent2_.NAME as name4_1_2_,
countryent2_.UPDATED_TIMESTAMP as updated_5_1_2_
from
STREET streetenti0_
left outer join
LOCALITY localityen1_
on streetenti0_.LOCALITY_ID=localityen1_.ID
left outer join
COUNTRY countryent2_
on localityen1_.COUNTRY_ID=countryent2_.ID
where
streetenti0_.ID=?
2021-09-06 17:06:19.012 address-management-service TRACE natty-lappy-work [http-nio-8080-exec-1] org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [INTEGER] - [179779]
2021-09-06 17:06:19.031 address-management-service DEBUG natty-lappy-work [http-nio-8080-exec-1] org.hibernate.engine.jdbc.spi.SqlStatementLogger -
select
localityen0_.ID as id1_2_0_,
localityen0_.COUNTRY_ID as country_9_2_0_,
localityen0_.DELETED_TIMESTAMP as deleted_2_2_0_,
localityen0_.EXTERNAL_REFERENCE_ID as external3_2_0_,
localityen0_.INSERTED_TIMESTAMP as inserted4_2_0_,
localityen0_.NAME as name5_2_0_,
localityen0_.SOURCE as source6_2_0_,
localityen0_.STATUS as status7_2_0_,
localityen0_.UPDATED_TIMESTAMP as updated_8_2_0_,
countryent1_.ID as id1_1_1_,
countryent1_.DELETED_TIMESTAMP as deleted_2_1_1_,
countryent1_.INSERTED_TIMESTAMP as inserted3_1_1_,
countryent1_.NAME as name4_1_1_,
countryent1_.UPDATED_TIMESTAMP as updated_5_1_1_
from
LOCALITY localityen0_
left outer join
COUNTRY countryent1_
on localityen0_.COUNTRY_ID=countryent1_.ID
where
localityen0_.ID=?
I want it to execute the exact statement I give it, without translating anything. I also had it set to nativeQuery = true but that doesn't seem to help. Any idea what I can do?
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(AddressEntity.class)
public interface AddressDto {
#IdMapping
Long getId();
String getStatus();
String getSource();
String getExternalReferenceId();
#Mapping("street.id")
Long getStreetId();
#Mapping("locality.id")
Long getLocalityId();
#Mapping("country.id")
Long getCountryId();
#Mapping("postCode.id")
Long getPostcodeId();
Instant getInsertedTimestamp();
Instant getUpdatedTimestamp();
Instant getDeletedTimestamp();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AddressDto a = entityViewManager.find(entityManager, AddressDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<AddressDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
In your case, it will even avoid all the joins and just select the foreign keys as they are in the address table.

JPQL include elementCollection map in select statement

I have an #Entity class Company with several attributes, referencing a companies Table in my db. One of them represents a Map companyProperties where the companies table is extended by a company_properties table, and the properties are saved in key-value format.
#Entity
#Table(name = "companies")
public class Company extends AbstractEntity {
private static final String TABLE_NAME = "companies";
#Id
#GeneratedValue(generator = TABLE_NAME + SEQUENCE_SUFFIX)
#SequenceGenerator(name = TABLE_NAME + SEQUENCE_SUFFIX, sequenceName = TABLE_NAME + SEQUENCE_SUFFIX, allocationSize = SEQUENCE_ALLOCATION_SIZE)
private Long id;
//some attributes
#ElementCollection
#CollectionTable(name = "company_properties", joinColumns = #JoinColumn(name = "companyid"))
#MapKeyColumn(name = "propname")
#Column(name = "propvalue")
private Map<String, String> companyProperties;
//getters and setters
}
The entity manager is able to perform properly find clauses
Company company = entityManager.find(Company.class, companyId);
However, I am not able to perform JPQL Queries in this entity and retrieve the Map accordingly. Since the object is big, I just need to select some of the attributes in my entity class. I also do not want to filter by companyProperties but to retrieve all of them coming with the proper assigned companyid Foreign Key. What I have tried to do is the following:
TypedQuery<Company> query = entityManager.createQuery("SELECT c.id, c.name, c.companyProperties " +
"FROM Company as c where c.id = :id", Company.class);
query.setParameter("id", companyId);
Company result = query.getSingleResult();
The error I get is:
java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
Exception Description: Problem compiling [SELECT c.id, c.name, c.companyProperties FROM Company as c where c.id = :id]. [21, 40] The state field path 'c.companyProperties' cannot be resolved to a collection type.
org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1616)
org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1636)
com.sun.enterprise.container.common.impl.EntityManagerWrapper.createQuery(EntityManagerWrapper.java:476)
Trying to do it with joins (the furthest point I got was with
Query query = entityManager.createQuery("SELECT c.id, c.name, p " +
"FROM Company c LEFT JOIN c.companyProperties p where c.id = :id");
does not give me either the correct results (it only returns the value of the property and not a list of them with key-value).
How can I define the right query to do this?
Your JPA syntax looks off to me. In your first query you were selecting individual fields in the Company entity. But this isn't how JPA works; when you query you get back the entire object, with which you can access any field you want. I propose the following code instead:
TypedQuery<Company> query = entityManager.createQuery("from Company as c where c.id = :id", Company.class);
query.setParameter("id", companyId);
Company result = query.getSingleResult();
Similarly, for the second join query I suggest the following code:
Query query = entityManager.createQuery("SELECT c" +
"FROM Company c LEFT JOIN c.companyProperties p WHERE c.id = :id");
query.setParameter("id", companyId);
List<Company> companies = query.getResultList();
The reason why only select a Company and not a property entity is that properties would appear as a collection inside the Company class. Assuming a one to many exists between companies and properties, you could access the propeties from each Company entity.
You are expecting to get a complete Company object when doing select only on particular fields, which is not possible. If you really want to save some memory (which in most cases would not be that much of a success) and select only some field, then you should expect a List<Object[]>:
List<Object[]> results = entityManager.createQuery("SELECT c.id, c.name, p " +
"FROM Company c LEFT JOIN c.companyProperties p where c.id = :id")
.setParameter("id", companyId)
.getResultList();
Here the results will contain a single array of the selected fields. You can use getSingleResult, but be aware that it will throw an exception if no results were found.

named query to fetch result by using IN clause with null or without null conditionally

I need a single named query that fulfills below both named query conditions only by setting the Query parameter.
Named Query, to fetch record where "softwareVersion" is null OR softwareVersion matched with the list.
#NamedQuery(name = "getIPDetectionDetailsForPanIndia",
query = "select cssc.css.sapId,
cssc.css.hostName,
cssc.cssGoldenConfiguration.category,
cssc.cssGoldenConfiguration.parameter,
cssc.cssGoldenConfiguration.vendor,
cssc.recommendedValue,
cssc.actualValue,
cssc.cssGoldenConfiguration.id,
cssc.cssGoldenConfiguration.information,
cssc.css.softwareVersion,
jioc.name,
cssc.cssGoldenConfiguration.impact,
cssc.cssGoldenConfiguration.command
from CssComplianceDetail cssc
join cssc.css c
left join c.cluster club
left join clus.jiocenter jioc
where cssc.cssGoldenConfiguration.impact in (:impactType)
and cssc.cssGoldenConfiguration.category in (:category)
and TO_CHAR(cssc.creationDate, 'yyyy-MM-dd') = TO_CHAR(:date, 'yyyy-MM-dd')
and (cssc.css.softwareVersion in (:softwareVersion)
or cssc.css.softwareVersion is null)";
Named Query, to fatch record where "softwareVersion" matched with the list only, no need to fatch null softwareVersion.
#NamedQuery(name = "getIPDetectionDetailsForPanIndiaAcceptNull",
query = "select cssc.css.sapId,
cssc.css.hostName,
cssc.cssGoldenConfiguration.category,
cssc.cssGoldenConfiguration.parameter,
cssc.cssGoldenConfiguration.vendor,
cssc.recommendedValue,
cssc.actualValue,
cssc.cssGoldenConfiguration.id,
cssc.cssGoldenConfiguration.information,
cssc.css.softwareVersion,
jioc.name,
cssc.cssGoldenConfiguration.impact,
cssc.cssGoldenConfiguration.command
from CssComplianceDetail cssc
join cssc.css c
left join c.cluster club
left join clus.jiocenter join
where cssc.cssGoldenConfiguration.impact in (:impactType)
and cssc.cssGoldenConfiguration.category in (:category)
and TO_CHAR(cssc.creationDate, 'yyyy-MM-dd') = TO_CHAR(:date, 'yyyy-MM-dd')
and cssc.css.softwareVersion in (:softwareVersion)";
Below is my Java code:
List softwareVersion = new ArrayList();
if(softwareVersion.contains("All")) {
query = getEntityManager().createNamedQuery("getIPDetectionCountForPanIndiaAcceptNull");
} else {
query = getEntityManager().createNamedQuery("getIPDetectionCountForPanIndia");
}
query.setParameter("softwareVersion", softwareVersion);

Categories

Resources