Folks!
Trying to limit amount of columns fetched from DB and found this: Hibernate Criteria Query to get specific columns
Criteria cr = session.createCriteria(User.class)
.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("Name"), "Name"))
.setResultTransformer(Transformers.aliasToBean(User.class));
List<User> list = cr.list();
This works awesome when you use Hibernate, but I am trying to do the same with JPA (JPA provider is Hibernate tho). Is it possible to do it with JPA? I mean limit columns with CriteriaBuilder and map to specific Object?
Also, I saw this:
https://docs.jboss.org/hibernate/orm/5.0/userguide/html_single/chapters/query/criteria/Criteria.html
Hibernate offers an older, legacy org.hibernate.Criteria API which should be considered deprecated. No feature development will target those APIs. Eventually, Hibernate-specific criteria features will be ported as extensions to the JPA javax.persistence.criteria.CriteriaQuery. For details on the org.hibernate.Criteria API, see Legacy Hibernate Criteria Queries.
So seems like it is possible to do this via hibernate extensions for JPA?
There is no alternative syntax within JPA, Hibernate is superset of JPA, it provides features beyond JPA specifications.
In JPA you will have to do projections individually explicitly with select statement (in your case multiselect statement).
Criteria Query
// Create instance of CriteriaBuilder, where em is EntityManager
CriteriaBuilder cb = em.getCriteriaBuilder();
// Use CriteriaBuilder interface to create an instance
// of CriteriaQuery. For multiselect result set is Object [].
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
// Establish the root of the query by invoking from() to get back a Root object.
Root<User> user = c.from(User.class);
// Establish the SELECT clause of the query by passing the root into the multiselect() method
criteriaQuery.multiselect(user.get("property1"), user.get("property2"));
alternative would be constructor call:
CriteriaQuery<User> c = cb.createQuery(User.class);
Root<User> user = c.from(User.class);
c.select(cb.construct(User.class,
user.get("property1"),
user.get("property2")));
TupleQuery
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<User> user = c.from(User.class);
c.select(cb.tuple(user.get("property1"), user.get("property2")));
Query query = entityManager.createQuery(c);
List<Tuple> results = query.getResultList();
There is also array() method (it returns Object[]) as well.
c.select(cb.array(user.get("property1"), user.get("property2")));
Related
The hibernate criteria query I'm trying to change looks like this
Criteria crit =
session.createCriteria(device.class)
.createCriteria("deviceConfigurationTemplate")
.createCriteria("deviceModel");
I want to simply change this to use JPA CriteriaQueries instead. The issue I'm having is how javax.persistence to create a query with multiple assoicated entities passed in as strings. I wanted to use JPA root criteria and multiselect them using the criteriaQuery like this
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Device> cq = cb.createQuery(Device.class);
Root<DeviceModel> model = cq.from(DeviceModel.class);
Root<"deviceConfigurationTemplate"> model2 =
cq.from("deviceConfigurationTemplate");
cq.multiselect(model);
The issue here is you can't pass in a string as a parameter to the root object. I'm not sure how else to go about creating a query like this.
Why would you try to pass a string as the type parameter? Once you join along an association, the resulting Join or Path should be parameterized with the association's target type.
Is the query supposed to be equivalent to SELECT d.deviceConfigurationTemplate.deviceModel FROM Device d? If so, the equivalent Criteria query would be:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<DeviceModel> criteria = cb.createQuery(DeviceModel.class);
Path<DeviceModel> model = criteria.from(Device.class).get("deviceConfigurationTemplate").get("model");
criteria.select(model);
With typing made a bit more explicit:
CriteriaQuery<DeviceModel> criteria = cb.createQuery(DeviceModel.class);
Root<Device> from = criteria.from(Device.class);
Path<ConfigurationTemplate> configTemplate = from.get("template");
Path<DeviceModel> model = configTemplate.get("model");
criteria.select(model);
You could also use from.join() instead.
Since the hibernate.criteria API is deprecated, I need assistance converting certain methods to JPA criteriaQuery.
The simpler calls with singular criteria and limited parameters I can handle, but how can I convert from a hibernate criteria created with multiple root entities. Here is the org.hibernate.criteria code that needs conversion
Criteria crit =
session.createCriteria(getType())
.createCriteria("deviceInfo"
.createCriteria("deviceSize");
crit.add(Restrictions.ilike("upperName", modelName));
List<Device> matches = crit.list();
The conversion for something like this with a singular call to createCriteria I can manage. They look something like this
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Workflow> cq = cb.createQuery(getType());
Root<getType()> model = cq.from(getType());
cq.select(model);
cq.where(cb.equal(model.get("primaryEntityId"), entityId));
TypedQuery<getType()> q = entityManager.createQuery(cq);
List<Workflow> matches = q.getResultList();
The only thing I can't seem to recreate in JPA is the two association paths passed in to createCriteria
Using JPA 2.0. It seems that by default (no explicit fetch), #OneToOne(fetch = FetchType.EAGER) fields are fetched in 1 + N queries, where N is the number of results containing an Entity that defines the relationship to a distinct related entity. Using the Criteria API, I might try to avoid that as follows:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> query = builder.createQuery(MyEntity.class);
Root<MyEntity> root = query.from(MyEntity.class);
Join<MyEntity, RelatedEntity> join = root.join("relatedEntity");
root.fetch("relatedEntity");
query.select(root).where(builder.equals(join.get("id"), 3));
The above should ideally be equivalent to the following:
SELECT m FROM MyEntity m JOIN FETCH myEntity.relatedEntity r WHERE r.id = 3
However, the criteria query results in the root table needlessly being joined to the related entity table twice; once for the fetch, and once for the where predicate. The resulting SQL looks something like this:
SELECT myentity.id, myentity.attribute, relatedentity2.id, relatedentity2.attribute
FROM my_entity myentity
INNER JOIN related_entity relatedentity1 ON myentity.related_id = relatedentity1.id
INNER JOIN related_entity relatedentity2 ON myentity.related_id = relatedentity2.id
WHERE relatedentity1.id = 3
Alas, if I only do the fetch, then I don't have an expression to use in the where clause.
Am I missing something, or is this a limitation of the Criteria API? If it's the latter, is this being remedied in JPA 2.1 or are there any vendor-specific enhancements?
Otherwise, it seems better to just give up compile-time type checking (I realize my example doesn't use the metamodel) and use dynamic JPQL TypedQueries.
Instead of root.join(...) you can use root.fetch(...) which returns Fetch<> object.
Fetch<> is descendant of Join<> but it can be used in similar manner.
You just need to cast Fetch<> to Join<> it should work for EclipseLink and Hibernate
...
Join<MyEntity, RelatedEntity> join = (Join<MyEntity, RelatedEntity>)root.fetch("relatedEntity");
...
Starting with JPA 2.1 you can use dynamic entity graphs for this. Remove your fetch and specify an entity graph as follows:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> query = builder.createQuery(MyEntity.class);
Root<MyEntity> root = query.from(MyEntity.class);
Join<MyEntity, RelatedEntity> join = root.join("relatedEntity");
query.select(root).where(builder.equal(join.get("id"), 3));
EntityGraph<MyEntity> fetchGraph = entityManager.createEntityGraph(MyEntity.class);
fetchGraph.addSubgraph("relatedEntity");
entityManager.createQuery(query).setHint("javax.persistence.loadgraph", fetchGraph);
Using root.fetch() on EclipseLink will create a SQL with INNER JOIN because have 3 types and the default is INNER.
INNER, LEFT, RIGHT;
The suggestion is to use CreateQuery.
TypedQuery<T> typedQuery = entityManager.createQuery(query);
EDIT: You can Cast the root to From like this:
From<?, ?> join = (From<?, ?>) root.fetch("relatedEntity");
I have a code that looks like this:
Session session = MySessionFactory.createSession();
session.beginTransaction();
Criteria cq = session.createCriteria(clazz);
// Do stuff.
List list = cq.list();
session.getTransaction().commit();
session.close();
Do I actually need the beginTransaction(), commit(), and close()?
I heard in JPA, CriteriaQuery (as opposed to Criteria) does not require active transaction management.
Yes, you need the session management. However, you can do multiple operations/queries within a single transaction/session. I would recommend starting with a single transaction per request (if you are creating a web server), job, etc. and increase the granularity of the transactions as needed.
If you want to avoid Spring then this can still be easily done with aspects however you will quickly end up repeating a lot of the work spring has done.
> I heard in JPA, CriteriaQuery (as opposed to Criteria) does not require active transaction management.
That's correct.
If you change your code to standard JPA, then no query is forced to run within a transaction - provided you are SELECTING entities (and not INSERTING/UPDATING/DELETING) and are not setting a LockMode on the query.
If this was the desired JPQL:
SELECT c
FROM Customer c JOIN c.orders o JOIN o.lineItems i
WHERE i.product.productType = 'printer'
Stardard JPA Criteria Query code would be:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
Root<Customer> customer = cq.from(Customer.class);
Join<Customer, Order> order = customer.join(Customer_.orders); // or .join("orders")
Join<Order, Item> item = order.join(Order_.lineItems); // or .join("lineItems")
ParameterExpression<String> p = cb.parameter(String.class, "prodType");
cq.select(customer)
.where(cb.equal(item.get(Item_.product).get(Product_.productType), p));
// if you haven't generated JPA metamodel Customer_, Product_, etc,
// can replace this with
// .where(cb.equal(item.get("product").get("productType"), p));
TypedQuery<Employee> tq = em.createQuery(cq);
q.setParameter("prodType", "printer");
return q.getResultList();
It's a bit ugly, but it's strongly-typed (which JPQL isn't), standard, object-oriented, compiled on initialisation and hence fast.
:-)
I am working with postgres and using JPA with Hibernate, in postgres and other DBMS can do this:
SELECT *, function(parameter) AS xyz FROM table WHERE condition
My question is this, the query can display an additional field (xyz), although there is no such column. I can do that with Hibernate JPA.
If you don't have a mapped entity then you will need to use native queries:
Query query = entityManager
.createQuery(
"SELECT t.*, myfunction(:parameter) FROM table t WHERE t.attr = condition");
query.setParameter("parameter", value);
List resultList = query.getResultList();
Otherwise, if you have a mapped entity you can do this with typed queries or the criteria API.
With typed queries:
TypedQuery<Object[]> query = entityManager
.createQuery(
"SELECT t.*, FUNC('MyFunction', :parameter) FROM table t WHERE t.attr = condition",
Object[].class);
query.setParameter("parameter", value);
List<Object[]> resultList = query.getResultList();
With criteria API:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();
Root<Table> root = query.from(Table.class);
Expression<Long> funcExpr = criteriaBuilder.function("myfunction",
Long.class, root.get("parameter"));
query.multiselect(query.select(root), funcExpr);
List<Tuple> resultList = entityManager.createQuery(query)
.getResultList();
By using org.hibernate.SQLQuery, you can create native SQL queries with Hibernate. You then execute these queries the same way you would with a normal Hibernate query. Obviously you're going to lose many of the benefits of letting Hibernate and JPA manage your queries, but if they do not support the feature you want, it may be the only way to do it.
Another solution is to use Formulas, if you invoke your function on some fields of the table, and you always want the result of the function you can map another property in your entity and annotate it with #Formula("myFunction(field1, field2)"). Hibernate will add this to all of your queries which have your entity.