I am using hibernate for this select:
NativeQuery sqlQuery = session.createSQLQuery("select :id, min(a.time) " +
"from table1 a, (" +
" select parentid" +
" from (" +
" select LEVEL, parentid" +
" from table2 " +
" START WITH id = :id" +
" CONNECT BY NOCYCLE PRIOR parentid = id" +
" order by level desc)" +
" where rownum = 1" +
" ) b " +
"where a.id = b.parentid");
sqlQuery.setParameter("id", id);
List<Object[]> list = sqlQuery.list();
I need to use this for a lot of ids. Ideally I would pass a list of ids, run that in oracle and return the result. I cannot do that because of the START WITH clause.
This way I send a query to database for each id, which is really slow.
Is there any way to put a list of ids to the query, do this in loop and return back to the application with a list of results?
NativeQuery sqlQuery = session.createSQLQuery("...?...");
sqlQuery.setListParameter("ids", ids);
List<Object[]> list = sqlQuery.list();
Edit: I cannot use recursive with, because we use Oracle 10.
I don't know about the Oracle specific syntax, but with the SQL standard WITH RECURSIVE syntax, you can of course list multiple parameters. Just use the IN predicate e.g. for batches of 5 id IN (:id1, :id2, :id3, :id4, :id5) and then bind the values with setParameter("id1", ...). If you are interested, you can also formulate this with Blaze-Persistence on top of Hibernate by using the JPA model: https://persistence.blazebit.com/documentation/1.6/core/manual/en_US/#recursive-ctes
In your case this would look like the following:
#CTE
#Entity
public class ResultCte {
#Id
Long parentId;
Long rootId;
int level;
}
CriteriaBuilder<Tuple> cb = criteriaBuilderFactory.create(entityManager, Tuple.class);
cb.withRecursive(ResultCte.class)
.from(Entity2.class, "e2")
.where("e2.id").in(idList)
.bind("parentId").select("e2.parent.id")
.bind("rootId").select("e2.id")
.bind("level").select("1")
.unionAll()
.from(Entity2.class, "e2")
.from(ResultCte.class, "cte")
.where("e2.id").eqExpression("cte.parentId")
.bind("parentId").select("e2.parent.id")
.bind("rootId").select("cte.rootId")
.bind("level").select("cte.level + 1")
.end()
.from(Entity1.class "a")
.from(ResultCte.class, "cte")
.where("a.id").eqExpression("cte.parentId")
.select("cte.rootId")
.select("min(a.time)")
Related
I'm getting a warning in the Server log "firstResult/maxResults specified with collection fetch; applying in memory!". However everything working fine. But I don't want this warning.
My code is
public employee find(int id) {
return (employee) getEntityManager().createQuery(QUERY).setParameter("id", id).getSingleResult();
}
My query is
QUERY = "from employee as emp left join fetch emp.salary left join fetch emp.department where emp.id = :id"
Although you are getting valid results, the SQL query fetches all data and it's not as efficient as it should.
So, you have two options.
Fixing the issue with two SQL queries that can fetch entities in read-write mode
The easiest way to fix this issue is to execute two queries:
. The first query will fetch the root entity identifiers matching the provided filtering criteria.
. The second query will use the previously extracted root entity identifiers to fetch the parent and the child entities.
This approach is very easy to implement and looks as follows:
List<Long> postIds = entityManager
.createQuery(
"select p.id " +
"from Post p " +
"where p.title like :titlePattern " +
"order by p.createdOn", Long.class)
.setParameter(
"titlePattern",
"High-Performance Java Persistence %"
)
.setMaxResults(5)
.getResultList();
List<Post> posts = entityManager
.createQuery(
"select distinct p " +
"from Post p " +
"left join fetch p.comments " +
"where p.id in (:postIds) " +
"order by p.createdOn", Post.class)
.setParameter("postIds", postIds)
.setHint(
"hibernate.query.passDistinctThrough",
false
)
.getResultList();
Fixing the issue with one SQL query that can only fetch entities in read-only mode
The second approach is to use SDENSE_RANK over the result set of parent and child entities that match our filtering criteria and restrict the output for the first N post entries only.
The SQL query can look as follows:
#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.id as \"pc.id\", " +
" pc.created_on AS \"pc.created_on\", " +
" pc.review AS \"pc.review\", " +
" pc.post_id AS \"pc.post_id\" " +
" 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"),
}
)
}
)
The #NamedNativeQuery fetches all Post entities matching the provided title along with their associated PostComment child entities. The DENSE_RANK Window Function is used to assign the rank for each Post and PostComment joined record so that we can later filter just the amount of Post records we are interested in fetching.
The SqlResultSetMapping provides the mapping between the SQL-level column aliases and the JPA entity properties that need to be populated.
Now, we can execute the PostWithCommentByRank #NamedNativeQuery like this:
List<Post> posts = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter(
"titlePattern",
"High-Performance Java Persistence %"
)
.setParameter(
"rank",
5
)
.unwrap(NativeQuery.class)
.setResultTransformer(
new DistinctPostResultTransformer(entityManager)
)
.getResultList();
Now, by default, a native SQL query like the PostWithCommentByRank one would fetch the Post and the PostComment in the same JDBC row, so we will end up with an Object[] containing both entities.
However, we want to transform the tabular Object[] array into a tree of parent-child entities, and for this reason, we need to use the Hibernate ResultTransformer.
The DistinctPostResultTransformer looks as follows:
public class DistinctPostResultTransformer
extends BasicTransformerAdapter {
private final EntityManager entityManager;
public DistinctPostResultTransformer(
EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public List transformList(
List list) {
Map<Serializable, Identifiable> identifiableMap =
new LinkedHashMap<>(list.size());
for (Object entityArray : list) {
if (Object[].class.isAssignableFrom(entityArray.getClass())) {
Post post = null;
PostComment comment = null;
Object[] tuples = (Object[]) entityArray;
for (Object tuple : tuples) {
if(tuple instanceof Identifiable) {
entityManager.detach(tuple);
if (tuple instanceof Post) {
post = (Post) tuple;
}
else if (tuple instanceof PostComment) {
comment = (PostComment) tuple;
}
else {
throw new UnsupportedOperationException(
"Tuple " + tuple.getClass() + " is not supported!"
);
}
}
}
if (post != null) {
if (!identifiableMap.containsKey(post.getId())) {
identifiableMap.put(post.getId(), post);
post.setComments(new ArrayList<>());
}
if (comment != null) {
post.addComment(comment);
}
}
}
}
return new ArrayList<>(identifiableMap.values());
}
}
The DistinctPostResultTransformer must detach the entities being fetched because we are overwriting the child collection and we don’t want that to be propagated as an entity state transition:
post.setComments(new ArrayList<>());
Reason for this warning is that when fetch join is used, order in result sets is defined only by ID of selected entity (and not by join fetched).
If this sorting in memory is causing problems, do not use firsResult/maxResults with JOIN FETCH.
To avoid this WARNING you have to change the call getSingleResult to
getResultList().get(0)
This warning tells you Hibernate is performing in memory java pagination. This can cause high JVM memory consumption.
Since a developer can miss this warning, I contributed to Hibernate by adding a flag allowing to throw an exception instead of logging the warning (https://hibernate.atlassian.net/browse/HHH-9965).
The flag is hibernate.query.fail_on_pagination_over_collection_fetch.
I recommend everyone to enable it.
The flag is defined in org.hibernate.cfg.AvailableSettings :
/**
* Raises an exception when in-memory pagination over collection fetch is about to be performed.
* Disabled by default. Set to true to enable.
*
* #since 5.2.13
*/
String FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH = "hibernate.query.fail_on_pagination_over_collection_fetch";
the problem is you will get cartesian product doing JOIN. The offset will cut your recordset without looking if you are still on same root identity class
I guess the emp has many departments which is a One to Many relationship. Hibernate will fetch many rows for this query with fetched department records. So the order of result set can not be decided until it has really fetch the results to the memory. So the pagination will be done in memory.
If you do not want to fetch the departments with emp, but still want to do some query based on the department, you can achieve the result with out warning (without doing ordering in the memory). For that simply you have to remove the "fetch" clause. So something like as follows:
QUERY = "from employee as emp left join emp.salary sal left join emp.department dep where emp.id = :id and dep.name = 'testing' and sal.salary > 5000 "
As others pointed out, you should generally avoid using "JOIN FETCH" and firstResult/maxResults together.
If your query requires it, you can use .stream() to eliminate warning and avoid potential OOM exception.
try (Stream<ENTITY> stream = em.createQuery(QUERY).stream()) {
ENTITY first = stream.findFirst().orElse(null); // equivalents .getSingleResult()
}
// Stream returned is an IO stream that needs to be closed manually.
I'm using NamedNativeQueries with SqlResultSetMappings in a Spring Data (JPA Hibernate MySQL) application, and I've been successful with the Pagination, but not with the sorting.
I've tried two forms of queries:
#NamedNativeQuery(
name = "DatasetDetails.unallocatedDetailsInDataset",
resultClass = DatasetDetails.class,
resultSetMapping = "DatasetDetails.detailsForAllocation",
query = "SELECT dd.id, fk_datasets_id, fk_domains_id, fk_sources_id, dom.name AS domain, " +
"src.name AS source " +
"FROM datasets AS d " +
"JOIN datasets_details AS dd ON dd.fk_datasets_id = d.id " +
"JOIN sources AS src ON src.id = dd.fk_sources_id " +
"JOIN domains AS dom ON dom.id = dd.fk_domains_id " +
"WHERE fk_datasets_id = :datasetId " +
"AND dd.id NOT IN (" +
"SELECT fk_datasets_details_id from allocations_datasets_details) \n/* #page */\n"),
and the second is simply using the count notation on a second query instead of using the #page notation.
#NamedNativeQuery(
name = "DatasetDetails.unallocatedDetailsInDataset.count",
resultClass = DatasetDetails.class,
resultSetMapping = "DatasetDetails.detailsForAllocation",
query = "SELECT count(*)
....
Both methods work for pagination, but the sorting is ignored.
Here is the repository:
public interface DatasetDetailsRepository extends PagingAndSortingRepository<DatasetDetails, Long> {
#Query(nativeQuery = true)
List<DatasetDetails> unallocatedDetailsInDataset(#Param("datasetId") long datasetId,
#Param("page") Pageable page);
}
And the pageable gets assembled like this:
Sort sort = Sort.by(Sort.Order.asc(DatasetDetails.DOMAIN), Sort.Order.asc(DatasetDetails.SOURCE));
Pageable page = PageRequest.of(page, limit, sort);
No errors are thrown, but the sorting simply doesn't get done and no ORDER BY is generated.
Explicitly adding something like ORDER BY #{#page} won't compile.
I encountered the same problem, where I had to dynamically filter/sort using a NamedNativeQuery by different columns and directions; apparently the Sorting was ignored. I found this workaround, which is not necessarily nice but it does the job:
For the repository:
List<MyEntity> findMyEntities(
#Param("entityId") long entityId,
#Param("sortColumn") String sortColumn,
#Param("sortDirection") String sortDirection,
Pageable page);
The native queries look like this:
#NamedNativeQueries({
#NamedNativeQuery(name = "MyEntity.findMyEntities",
query = "select e.field1, e.field2, ..." +
" from my_schema.my_entities e" +
" where condition1 and condtition2 ..." +
" order by " +
" CASE WHEN :sortColumn = 'name' and :sortDirection = 'asc' THEN e.name END ASC," +
" CASE WHEN :sortColumn = 'birthdate' and :sortDirection = 'asc' THEN e.birthdate END ASC," +
" CASE WHEN :sortColumn = 'name' and :sortDirection = 'desc' THEN e.name END DESC," +
" CASE WHEN :sortColumn = 'birthdate' and :sortDirection = 'desc' THEN e.birthdate END DESC" +
),
#NamedNativeQuery(name = "MyEntity.findMyEntities.count",
query = "select count(*) from my_schema.my_entities e" +
" where condition1 and condtition2 ..." +
" and :sortColumn = :sortColumn and :sortDirection = :sortDirection"
)
})
Notice in the count query I use the 2 redundant conditions for :sortColumn and :sortDirection, because once specified as #Param in the repository function, you need to use them in the actual query.
When calling the function, in my service I had a boolean which dictates the direction and a string that dictates the sorting column like this:
public Page<MyEntity> serviceFindFunction(Long entityId, String sortColumn, Boolean sortDirection, Integer pageNumber, Integer pageSize) {
String sortDir = (sortDirection) ? 'asc' : 'desc';
Pageable pageable = new PageRequest(pageNumber, pageSize); // Spring Data 1.0 syntax
// for Spring Data 2.0, as you were using, simply:
// Pageable pageable = PageRequest.of(pageNumber, pageSize);
return entityRepository.findMyEntities(entityId, sortColumn, sortDir, pageable)
}
The 2 things that I don't like about this are the redundant usage of the sortColumn and sortDirection params in the count query, and the way I wrote the order by statement. The reasoning for having separate CASE statements is because I had different data types for the columns that I sorted by, and if they are incompatible (e.g. nvarchar and date), the query will fail with the error:
Conversion failed when converting date and/or time from character string
I could also probably nest the conditionals, i.e. first making a case for the direction, the making an inner case for the columns, but my SQL skills only went this far.
Hope this helps! Any feedback or improvements are very welcomed.
I am using following code to execute native SQL query with hibernate:
Query zonesQuery = session.createNativeQuery(
"Select * \n" +
"FROM dbo.Structure AS S\n" +
"JOIN dbo.StructureLocationType AS SLT ON SLT.StructureId = S.Id\n" +
"WHERE SLT.LocationTypeId = :lc").addEntity(StructureEntity.class);
zonesQuery.setParameter("lc", locationTypeID);
List<StructureEntity> zones = zonesQuery.list();
So it works and it gets me list of StructureEntity
now, because my sql query "join" from StructureLocationType table, is there possibility to get whole StructureLocationType row as well, still using single query?
Thank you.
It can be achieved with the following (notice curly braces in SQL and entities aliases):
Query query = session
.createNativeQuery(
"SELECT {S.*}, {SLT.*} " +
"FROM dbo.Structure AS S " +
"JOIN dbo.StructureLocationType AS SLT ON SLT.StructureId = S.Id " +
"WHERE SLT.LocationTypeId = :lc")
.unwrap(SQLQuery.class)
.addEntity("S", StructureEntity.class)
.addEntity("SLT", StructureLocationTypeEntity.class)
.setParameter("lc", locationTypeID);
List<Pair<StructureEntity, StructureLocationTypeEntity>> result = ((List<Object[]>) query.list())
.stream()
.map(p -> Pair.of((StructureEntity) p[0], (StructureLocationTypeEntity) p[1]))
.collect(Collectors.toList());
You can't get multiple Objects from one query.
But you could either select which columns you want and then iterate the returned Object array:
The query:
SELECT s.id, s.someColumn, slt.id, slt.structureId
FROM dbo.Structure AS s
JOIN dbo.StructureLocationType AS slt on slt.structureId = s.id
WHERE slt.locationTypeId = :lc
Then iterate over the Object array:
List<Object[]> result = query.getResultList();
Or you could create a view on your database and map it to a java entity using the Table annotation like it was a normal table:
#Entity
#Table(name = "STRUCTURE_LOCATION_TYPE_VIEW")
public class StructureAndLocationType {
// ...
}
I thought there is a way to map from a query to an Object without creating a DB view but couldn't find it right now.
I have problem with sorting.
Repository method:
#Query(nativeQuery = true,
value = "SELECT D.ID as dealerId , D.NAME as dealerName, K.ID as kpiId, " +
"K.NAME as kpiName FROM REGION R, DEALER D, KPI K "
+ "WHERE R.IMPORTER_ID = :importerId "
+ "AND D.REGION_ID = R.ID "
+ "AND K.IMPORTER_ID = :importerId ")
Page<DealersKpiTargets> getDealersKpiTargets(#Param("importerId") Long importerId, Pageable pageable);
Pageable object:
Page request [number: 0, size 20, sort: name: DESC]
Hibernate log:
Hibernate: SELECT D.ID as dealerId , D.NAME as dealerName, K.ID as kpiId, K.NAME as kpiName FROM REGION R, DEALER D, KPI K WHERE R.IMPORTER_ID = ? AND D.REGION_ID = R.ID AND K.IMPORTER_ID = ? order by R.name desc limit ?
I don't understand where R.name prefix came from, in the order by part in Hibernate (towards the end).
For reference, I am using:
spring-data-jpa version 2.0.7.RELEASE
spring-boot-starter-data-jpa version 2.0.2.RELEASE
UPDATE
I have solved this by changing the query from the native query to jpa query and it's working. And I changed cartesian to join version.
#Query("SELECT dealerEntity.id AS dealerId , dealerEntity.name AS dealerName, kpiEntity.id AS kpiId, " +
"kpiEntity.name AS kpiName FROM KpiEntity kpiEntity "
+ "JOIN RegionEntity regionEntity ON regionEntity.importerEntity = kpiEntity.importerEntity "
+ "JOIN DealerEntity dealerEntity ON dealerEntity.importerEntity = regionEntity.importerEntity "
+ "WHERE kpiEntity.importerEntity = :importerEntity ")
Page<DealersKpiTargets> getDealersKpiTargets(#Param("importerEntity") ImporterEntity importerEntity, Pageable pageable);
here is jira ticket with more details which can be key for resolution (https://jira.spring.io/browse/DATAJPA-1613).
QueryUtils.ALIAS_MATCH
(?<=from)(?:\s)+([._[\P\\{Z}&&\P\\{Cc}&&\P\\{Cf}&&\P\\{P}]]+)(?:\sas)*(?:\s)+(?!(?:where|group\s*by|order\s*by))(\w+)
responsible to incorrect alias extraction. The solution for my case was rewrite native query, so it doesn't match the provided regexp.
This may be a little late to answer this question. But thought to share how I got around this issue.
For native queries, it seems like hibernate tries to use the alias of the first table used in the query when it applies the sorting criteria. In your case, the first table alias is R hence you see R.name desc in the query generated by hibernate.
One way to get around this issue is to wrap your query in a select clause and name it as R, like
"SELECT * FROM(SELECT D.ID as dealerId , D.NAME as dealerName, K.ID as kpiId, " +
"K.NAME as kpiName FROM REGION R, DEALER D, KPI K "
+ "WHERE R.IMPORTER_ID = :importerId "
+ "AND D.REGION_ID = R.ID "
+ "AND K.IMPORTER_ID = :importerId ) R"
This way at runtime hibernate would apply the sort criteria on top of your query which corresponds to R now.
It has Sort class for this you can use this maybe. Besides, it is easy to use.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.sorting
I faced similar issue especially in case of complex queries where there is ORDER BY with in the query. I was getting syntax error because a , was getting added before ORDER BY.
The way I solved this issue was to create a VIEW with the SELECT query having necessary fields required for result set and WHERE condition (so you can run query with params in WHERE condition against the VIEW). And write native query to SELECT FROM the VIEW
CREATE VIEW my_view AS (// your complex select query with required fields);
#Query("SELECT field1 AS alias1, field2 AS alias2 FROM my_view "
+ "WHERE field3 = :param1 AND field4 = :param2")
Page<MyDto> myFunction(#Param("param1") Long param1, #Param("param1") String param2, Pageable pageable);
I try to do the function searchBook with java and jpa. I have 2 classes which are Media and Book. Book extends Media. And I keep the data in the different table. I try to select the data from the query below:
TypedQuery<Media> query = em.createQuery(
"SELECT m.title, b.isbn, b.authors"
+ " FROM Book b, Media m" + " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors", Media.class);
query.setParameter("isbn", book.getisbn());
query.setParameter("title", "%" + book.getTitle().toLowerCase()
+ "%");
query.setParameter("authors", "%" + book.getAuthors() + "%");
bookList = query.getResultList();
But I got the error:
java.lang.IllegalArgumentException: Cannot create TypedQuery for query
with more than one return
This is the first time I use JPA. I can't find the the mistake.
As a workaround, to get entity composed by other entity attributes, you can create it within query, providing constructor for it.
Query :
TypedQuery<Media> query = em.createQuery("SELECT NEW package_name.Media(m.title, b.isbn, b.authors)"
+ " FROM Book b, Media m"
+ " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors", Media.class);
Entity :
public Media(String title, int isbn, String author){
//-- Setting appropriate values
}
I have provided sample, change the datatypes of the constructor accordingly.
Without goind into details about how Media and Book should be modeled, I will at least explain why you get this exception.
You're doing:
em.createQuery(someJPQL, Media.class);
This means: create a query using someJPQL, and this query will return instances of the Media entity.
But your JPQL is:
SELECT m.title, b.isbn, b.authors ...
So the query does not return entities of type Media. It returns three fields, from two different entities. There is no way your JPA engine could magically create instances of Media from these 3 columns. A query would return instances of Media if it looked like this:
select m from Media m ...
If you still want to use TypedQuery you can change the result type to Object[].
List<Object[]> results = entityManager
.createQuery("SELECT m.title, b.isbn, b.authors ...", Object[].class)
.getResultList();
Each Object[] in the List represents a row of data. It contains the selected values for that row in the order in which they were selected in the query. Element 0 is the title, element 1 is the ISBN, and element 2 is the authors. You'll likely need to cast those values if you want to use them in a meaningful way. Since the field values come from two different tables, you could store them in some kind of container object.
List<MediaContainer> mediaList = new ArrayList<>();
for (Object[] row : results) {
MediaContainer container = new MediaContainer();
container.setTitle((String) row[0]);
container.setIsbn((int) row[1]);
container.setAuthors((String) row[2]);
mediaList.add(container);
}
#WebUser instead of doing
List<EntityIDKey> companies =
getEntityManager().createQuery(sql, EntityIDKey.class).getResultList();
Try this :
List<EntityIDKey> companies =
(List<EntityIDKey>)getEntityManager().createQuery(sql).getResultList();
works for me.
if your are using Hibernate version < 4, you can meet this bug.
I go same problem with v3.5. Finally i had to use simple Query and cast each parameter manually
see other comments here : https://groups.google.com/forum/#!topic/axonframework/eUd1d4rotMY
I... remove
Media.class
of
createQuery
because you return more Entities in this source "SELECT m.title, b.isbn, b.authors"
Ex.:
TypedQuery<Media> query = em.createQuery(
"SELECT m.title, b.isbn, b.authors"
+ " FROM Book b, Media m" + " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors");
You can resolve the issue by doing this kind of query:
em.createQuery( "SELECT m"
+ " FROM Book b, Media m" + " WHERE b.isbn = :isbn"
+ " OR lower(m.title) LIKE :title"
+ " OR b.authors LIKE :authors", Media.class);
but this works only if you need fields only from one of the requested tables