Selecting distinct records using Quarkus PanacheEntity - java

I have a table(MySql) like
(ID(primary-key), Name, rootId)
(2, asdf, 1)
(3, sdf, 1)
(12, tew, 4)
(13, erwq, 4)
Now I want to select distinct root tsg_ids present in database. In this case It should return 1,4.
I tried
List<Entity> entityList = Entity.find(SELECT DISTINCT t.rootId from table t).list().
In debug mode, I see entity list contains ("1", "4"). Entity.find() can only be taken into "Entity" object, but what I am getting from select query is String. So, I was not able to convert the Entity object to String object in this case.
Is there a way to get distinct values of a non-primary column using PanacheEntity?

I don't know if you are using Panache with Hibernate Reactive or with Hibernate ORM, but, at the moment, if you want to use Panache, you have to use a projection:
#RegisterForReflection
public class EntityRootIdView {
public final Long rootId;
public EntityRootIdView(Long rootId){
this.rootId = rootId;
}
}
// Panache with Hibernate ORM
List<EntityRootIdView> rootIds = Entity
.find("SELECT DISTINCT t.rootId from Entity t")
.project(EntityRootIdView.class)
.list()
// Panache with Hibernate Reactive
Uni<List<EntityRootIdView>> rootIds = Entity
.find("SELECT DISTINCT t.rootId from Entity t")
.project(EntityRootIdView.class)
.list()
Note that this requires at least Quarkus 2.12.0.Final
Alternatively, you can use the Hibernate Reactive session:
Uni<List<Long>> rootIds = Panache.getSession()
.chain(session -> session
.createQuery("SELECT DISTINCT t.rootId from Entity t", Long.class)
.getResultList() )
);
Or, if you are using Hibernate ORM, the entity manager:
List<Long> rootIds = Panache.getEntityManager()
.createQuery( "SELECT DISTINCT t.rootId from Entity t", Long.class )
.getResultList();
Funny enough, I've just created an issue to make this easier.

Related

How to get batching using the old hibernate criteria?

I'm still using the old org.hibernate.Criteria and get more and more confused about fetch modes. In various queries, I need all of the following variants, so I can't control it via annotations. I'm just switching everything to #ManyToOne(fetch=FetchType.LAZY), as otherwise, there's no change to change anything in the query.
What I could find so far either concerns HQL or JPA2 or offers just two choices, but I need it for the old criteria and for (at least) the following three cases:
Do a JOIN, and fetch from both tables. This is OK unless the data is too redundant (e.g., the master data is big or repeated many times in the result). In SQL, I'd write
SELECT * FROM item JOIN order on item.order_id = order.id
WHERE ...;
Do a JOIN, fetch from the first table, and the separation from the other. This is usually the more efficient variant of the previous query. In SQL, I'd write
SELECT item.* FROM item JOIN order on item.order_id = order.id
WHERE ...;
SELECT order.* FROM order WHERE ...;
Do a JOIN, but do not fetch the joined table. This is useful e.g., for sorting based on data the other table. In SQL, I'd write
SELECT item.* FROM item JOIN order on item.order_id = order.id
WHERE ...
ORDER BY order.name, item.name;
It looks like without explicitly specifying fetch=FetchType.LAZY, everything gets fetched eagerly as in the first case, which is sometimes too bad. I guess, using Criteria#setFetchMode, I can get the third case. I haven't tried it out yet, as I'm still missing the second case. I know that it's somehow possible, as there's the #BatchSize annotation.
Am I right with the above?
Is there a way how to get the second case with the old criteria?
Update
It looks like using createAlias() leads to fetching everything eagerly. There are some overloads allowing to specify the JoinType, but I'd need to specify the fetch type. Now, I'm confused even more.
Yes you can satisfy all three cases using FetchType.LAZY, BatchSize, the different fetch modes, and projections (note I just made up a 'where' clause with Restrictions.like("name", "%s%") to ensure that I retrieved many rows):
Do a JOIN, and fetch from both tables.
Because the order of an item is FetchType.LAZY, the default fetch mode will be 'SELECT' so it just needs to be set as 'JOIN' to fetch the related entity data from a join rather than separate query:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.setFetchMode("order", FetchMode.JOIN);
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getOrder().getName()));
The resulting single SQL query:
select
this_.id as id1_0_1_,
this_.name as name2_0_1_,
this_.order_id as order_id3_0_1_,
order2_.id as id1_1_0_,
order2_.name as name2_1_0_
from
item_table this_
left outer join
order_table order2_
on this_.order_id=order2_.id
where
this_.name like ?
Do a JOIN, fetch from the first table and the separately from the other.
Leave the fetch mode as the default 'SELECT', create an alias for the order to use it's columns in sorting, and use a projection to select the desired subset of columns including the foreign key:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.createAlias("order", "o");
cr.addOrder(org.hibernate.criterion.Order.asc("o.id"));
cr.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name")
.add(Projections.property("order"), "order"))
.setResultTransformer(org.hibernate.transform.Transformers.aliasToBean(Item.class));
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getOrder().getName()));
The resulting first SQL query:
select
this_.id as y0_,
this_.name as y1_,
this_.order_id as y2_
from
item_table this_
inner join
order_table o1_
on this_.order_id=o1_.id
where
this_.name like ?
order by
o1_.id asc
and subsequent batches (note I used #BatchSize(value=5) on the Order class):
select
order0_.id as id1_1_0_,
order0_.name as name2_1_0_
from
order_table order0_
where
order0_.id in (
?, ?, ?, ?, ?
)
Do a JOIN, but do not fetch the joined table.
Same as the previous case, but don't do anything to prompt the loading of the lazy-loaded orders:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.createAlias("order", "o");
cr.addOrder(Order.asc("o.id"));
cr.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name")
.add(Projections.property("order"), "order"))
.setResultTransformer(org.hibernate.transform.Transformers.aliasToBean(Item.class));
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getName()));
The resulting single SQL query:
select
this_.id as y0_,
this_.name as y1_,
this_.order_id as y2_
from
item_table this_
inner join
order_table o1_
on this_.order_id=o1_.id
where
this_.name like ?
order by
o1_.id asc
My entities for all cases remained the same:
#Entity
#Table(name = "item_table")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Order order;
// getters and setters omitted
}
#Entity
#Table(name = "order_table")
#BatchSize(size = 5)
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// getters and setters omitted
}

Making query combining, one to many relationships in JPA with JPQL query

I have a database. with a one to many relationships.
pet 1--* event.
I want to make a query, that selects all pets, that has had an event on a given date. (I'm using the SQL date format)
As of now I just want to be able to get all entities, for a hardcoded date.
here is the reference in my PetEntity table
#OneToMany
private List<EventEntity> events = new ArrayList();
and in my EventEntity
#ManyToOne
PetEntity pet;
I'm using a pattern where I use a repository to handle the data layer, and then a facade to handle any logic(if any)
So far I have made a method like this.
public Set<PetEntity> getPetsWithEvents(Date date){
EntityManager em = emf.createEntityManager();
Set<PetEntity> entities = new HashSet<>();
List<EventEntity> eventEntities=
em.createQuery("SELECT e from EventEntity e where e.date =: date", EventEntity.class).setParameter("date", date).getResultList();
for(EventEntity entity: eventEntities){
entities.add(entity.getPet());
}
return entities;
}
}
Is there a way to simply method this method into using one query, instead of looping through the vent and finding each pet?
As the others already mentioned, you should be able to select pet join event.
The JPQL will be something like below:
SELECT p FROM PetEntity p join p.events e
WHERE e.date =: date

Hibernate Criteria with Projections.groupProperty cannot return full hibernate object (ClassCastException)

I am relatively new to Hibernate, and I have a problem when adding a "distinct" restriction on my hibernate class.
#Entity
public class TaggedOffer {
private Long tagged_offers_id;
private String brand;
private Long cid;
private Date created_date;
//Getter and Setter and more fields
}
Previously, we were creating a hibernate query as follows:
public DetachedCriteria build(final TaggedOfferRequest request) {
DetachedCriteria criteria = DetachedCriteria.forClass(TaggedOffer.class);
criteria.add(Restrictions.eq("brand", request.getBrand()));
criteria.add(Restrictions.in("cid", request.getCids()));
// sort by date
criteria.addOrder(Property.forName("createdDate").desc());
return criteria;
}
This would create the following (working) SQL query:
select
this_.tagged_offers_id as tagged1_2_3_,
this_.brand as brand2_3_,
this_.cid as cid2_3_,
this_.created_date as created6_2_3_
from
site.tagged_offers this_
where
this_.brand=?
and this_.country_code=?
and this_.cid in (
?, ?
)
order by
this_.created_date desc limit ?
Here comes the tricky part. We now need to ensure that the results that are returned are distinct on the cid field. Meaning, return as many results as possible, providing each record has a unique cid associated with it.
I looked into this in SQL, and it seems that the easiest way to do this is just to have a group by cid in the query. In terms of the hibernate criteria, this is basically what I've been trying:
public DetachedCriteria build(final TaggedOfferRequest request) {
DetachedCriteria criteria = DetachedCriteria.forClass(TaggedOffer.class);
criteria.add(Restrictions.eq("brand", request.getBrand()));
criteria.add(Restrictions.in("cid", request.getCids()));
// sort by date
criteria.addOrder(Property.forName("createdDate").desc());
// ** new ** distinct criteria
criteria.setProjection(Projections.groupProperty("cid"));
return criteria;
}
This almost creates the SQL that I am looking for, but it later throws a class cast exception (as it's just selecting the cid field as opposed to the entire object).
select
this_.cid as y0_
from
site.tagged_offers this_
where
this_.brand=?
and this_.country_code=?
and this_.cid in (
?, ?
)
and tagtype1_.tag_type=?
group by
this_.cid
order by
this_.created_date desc limit ?
And the exception:
java.lang.ClassCastException: java.lang.Long cannot be cast to com.mycompany.site.taggedoffers.dao.model.TaggedOffer
Any idea how I can use projections to do what I want?
Thanks for your help.
Add projections for all columns which you need.
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.groupProperty("cid"));
projectionList.add(Projections.property("tagged_offers_id"));
...
criteria.setProjection(projectionList);
I have a doubt why there is a need to use group by here without any aggregate calculations!!
In case if you use group by with projection , then the particular column used in group by will be included again in fetch sql ignoring even the same column is used already in select statement.
To map the extra columns generated by hibernate due to group by you have to write a field in entity class and marking it as #Transient or you can use setResultTransformer and map to another class

How do I find a value in a column that just have unique values with EclipseLink?

You have the EntityManager.find(Class entityClass, Object primaryKey) method to find a specific row with a primary key.
But how do I find a value in a column that just have unique values and is not a primary key?
You can use appropriate JPQL with TypedQuery.
try {
TypedQuery<Bean> tq = em.createQuery("from Bean WHERE column=?", Bean.class);
Bean result = tq.setParameter(1, "uniqueKey").getSingleResult();
} catch(NoResultException noresult) {
// if there is no result
} catch(NonUniqueResultException notUnique) {
// if more than one result
}
For example, like this:
List<T> results = em.createQuery("SELECT t FROM TABLE t", T.class)
.getResultList();
With parameters:
List<T> results = em.createQuery("SELECT t FROM TABLE t where t.value = :value1")
.setParameter("value1", "some value").getResultList();
For single result replace getResultList() with getSingleResult():
T entity = em.createQuery("SELECT t FROM TABLE t where t.uniqueKey = :value1")
.setParameter("value1", "KEY1").getSingleResult();
One other way is to use Criteria API.
You can use a Query, either JPQL, Criteria, or SQL.
Not sure if your concern is in obtaining cache hits similar to find(). In EclipseLink 2.4 cache indexes were added to allow you to index non-primary key fields and obtain cache hits from JPQL or Criteria.
See,
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Caching/Indexes
Prior to 2.4 you could use in-memory queries to query the cache on non-id fields.
TL;DR
With in DSL level - JPA no practice mentioned in previous answers
How do I find a value in a column that just have unique values and is not a primary key?
There isn't specification for query with custom field with in root interface of javax.persistence.EntityManager, you need to have criteria base query.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<R> criteriaQuery = criteriaBuilder.createQuery(EntityType.class)
Root<R> root = criteriaQuery.from(type);
criteriaBuilder.and(criteriaBuilder.equal(root.get(your_field), value));
You can also group your predicates together and pass them all together.
andPredicates.add(criteriaBuilder.and(root.get(field).in(child)));
criteriaBuilder.and(andPredicates.toArray(new Predicate[]{});
And calling result(rather single entity or a list of entities) with
entityManager.createQuery(suitable_criteria_query).getSingleResult();
entityManager.createQuery(suitable_criteria_query).getResultList();

Hibernate entity with restriction

We have a DB table that is mapped into a hibernate entity. So far everything goes well...
However what we want is to only map enentitys that satisty a specific criteria, like ' distinct(fieldA,fieldB) '...
Is it possible to map with hibernate and hibernate annotations? How can we do it? With #Filter?
I would recommend that you use #Where annotation. This annotation can be used on the element Entity or target entity of a collection. You provide a clause attribute written in sql that will be applied to any select that hibernate performs on that entity. It is very easy to use and very readable.
Here is an example.
#Entity
//I am only interested in Donuts that have NOT been eaten
#Where(clause = "EATEN_YN = 'N'")
public class Donut {
#Column(name = "FILLING")
private String filling;
#Column(name = "GLAZED")
private boolean glazed = true;
#Column(name = "EATEN_YN")
private boolean eaten = false;
...
}
You could create a view and then map that view to entity:
create view my_data as
select ... from ...
#Entity(table="my_data")
public class MyData { ... }
One option is to map the table normally, then you could fetch your always entities through a query or a filter.
You could also make a native SQL query and map the entity on the results:
Query q = sess.createSQLQuery("SELECT DISTINCT fieldA, fieldB FROM some_table")
.addEntity(MyEntity.class);
List<MyEntity> cats = q.list();
It might be also possible to add DISTINCT to this type of HQL query:
select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr
Methods 1, 3 and 4 will make a read-only mapping.
Could you be more specific about the criteria you are using? The view approach is more generic since you can't do everything with a hibernate query or filter.
perhaps you could create a new Pojo that encapsulates the fields and the condition that they should statisy . And then then make that class a 'custom user defined type', such that Hibernate will have to use the mapping class that you provide, for mapping that 'type'..
In addition to the options mentioned by Juha, you can also create an object directly out of a SQL query using the NamedNativeQuery and SqlResultSetMapping annotations.
#Entity
#SqlResultSetMapping(name = "compositekey", entities =
#EntityResult(entityClass = MiniBar.class,
fields = { #FieldResult(name = "miniBar", column = "BAR_ID"), })
)
#NamedNativeQuery(name = "compositekey",
query = "select BAR_ID from BAR", resultSetMapping = "compositekey")
#Table(name = "BAR")
public class Bar {
Flavor the SQL query to your taste

Categories