jpa criteria query fails after MapJoin - jpa

I have the following #Entities
#Entity
public class Configuration{
#OneToMany
protected Map<String, Component> components;
}
and
#Entity
public class Component{
protected String displayName;
}
I do not understand why this works, returning all Configurations
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Configuration> cq = cb.createQuery(Configuration.class);
Root<Configuration> pc = cq.from(Configuration.class);
cq.select(pc);
But if I do a MapJoin, even without setting any conditions, it does not return anything
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Configuration> cq = cb.createQuery(Configuration.class);
Root<Configuration> pc = cq.from(Configuration.class);
MapJoin<Configuration, String, Component> mapJoin = pc.join(Configuration_.components);
cq.select(pc);
What am I missing? I'm at a loss, I've been through the tutorials, but have not found the answers I need. Any help much appreciated.

Because the join type is inner by default, which means that for a configuration to be returned it has to at least have one component. If none of your configurations have a component, nothing is returned.
The first query is equivalent to
select configuration.* from configuration
And the second one is equivalent to
select configuration.* from configuration
inner join component on component.id = configuration.id

Related

How to select multiple rows without using JPQL?

I am using JPA, so to get one row of data, I could do:
Person p = this.em.find(Person.class, 123);
// Note: 'em' is the EntityManager.
But how can I get multiple rows? How can I get something like SQL's SELECT * FROM people WHERE age>18;?
I know how to use JPQL to get multiple rows (in this case, all rows):
TypedQuery<Person> q = em.createQuery("SELECT p FROM Person p", Person.class);
List<Person> results = q.getResultList();
But is there a way to do this without having to write the JPQL (i.e. SELECT p FROM Person p) at all?
Note: This is a learning exercise. I want to stick with Jakarta EE only (no Spring or any other API that is outside Jakarta EE).
CriteriaQuery is the way to go, however another "standard" approach is:
// imports
#Entity
#Table(name = "Person")
#NamedQuery(
name = "Person.queryAll",
query = "SELECT p FROM Person p")
public class Person implements Serializable {
...
}
The benefit of using the criteria is that errors can be detected earlier. Compile time vs Run time. However, a lot of readers/devs find JPQL easier to use and understand.
Using CriteriaQuery will look like below:
//skipping imports & config
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> q = cb.createQuery(Person.class);
Root<Person> c = q.from(Person.class);
q.select(c);
Example of CriteriaQuery API:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> criteria = cb.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);
criteria.select(root);
criteria.where(cb.equal(root.get("id"), 123));
List<Person> people = em.createQuery(criteria).getResultList();
official docs
You can use the Criteria API as an alternative to JPQL:
// Imports.
import java.util.List;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
// Example usage of the Criteria API to get a list of all Persons in the database.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
Root<Person> root = cq.from(Person.class);
cq.select(root);
TypedQuery<Person> q = em.createQuery(cq);
List<Person> allPeople = q.getResultList();
where em is a javax.persistence.EntityManager.
Note: Most people use JPQL instead of Criteria API because JPQL (e.g. SELECT p FROM Person p) is far more concise than the code above. But the Criteria API has an advantage over JPQL in terms of type safety.
Further reading
Tutorial from Oracle: Using the Criteria API to Create Queries
Examples on Wikibooks: Java Persistence/Criteria
Javadoc for javax.persistence.criteria

Multi-level subquery with JPA CriteriaBuilder

I have the following JPA entities
#Entity
#Table(name="application_user")
public class ApplicationUser {
#Id
#Column(name="user_id")
private String userid;
#Column(name="last_write_time")
private Instant lastWrite;
//other fields omitted
}
#Entity
#Table(name="demographic")
public class Demographic {
#Id
#Column(name="user_id")
private String userid;
//primary key is a foreign key link
#OneToOne
#PrimaryKeyJoinColumn(name="user_id", referencedColumnName="user_id")
private ApplicationUser user;
//other fields omitted
}
My goal is to retrieve all of the Demographics that contains users where the last write time is the max value in the column. I pretty much want to write the following SQL using the JPA CriteriaBUilder
select * from demographic where
userid in (
select userid from application_user where
last_write in (
select max(last_write) from application_user
)
)
I tried writing the following CriteriaBuilder Code to accomplish this goal and it compiles successfully. Note I am using the generated Metamodel classes.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
root.fetch(Demographic_.user, JoinType.INNER);
Subquery<Instant> sqLatestUsers = c.subquery(Instant.class);
Root<ApplicationUser> subRootLatestUsers = sqLatestUsers.from(ApplicationUser.class);
sqLatestUsers.select(cb.greatest(subRootLatestUsers.<Instant>get(ApplicationUser_.LAST_WRITE)));
Predicate predicateLatestUsers = subRootLatestUsers.get(ApplicationUser_.LAST_WRITE).in(sqLatestUsers);
Subquery<ApplicationUser> sq = c.subquery(ApplicationUser.class);
Root<Demographic> subRoot = sq.from(Demographic.class);
sq.select(subRoot.<ApplicationUser>get(Demographic_.USER)).where(predicateLatestUsers);
Predicate containsUsers = subRoot.get(Demographic_.USER).in(sq);
c.select(root).where(containsUsers);
The code compiles and successfully deploys in Wildfly 14, but when I execute the code, the get the following error (with white space to improve readability):
Invalid path: 'generatedAlias2.user' : Invalid path: 'generatedAlias2.user'
...
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.user' [
select generatedAlias0 from com.company.model.Demographic as generatedAlias0
inner join fetch generatedAlias0.user as generatedAlias1
where generatedAlias2.user in (
select generatedAlias2.user from com.company.model.Demographic as generatedAlias2 where generatedAlias3.lastWrite in (
select max(generatedAlias3.lastWrite) from com.company.model.StarfishUser as generatedAlias3
)
)
]
Is chaining subqueries (nested subqueries) allowed by the JPA spec? Did I find something that is syntactically correctly but not actually allowed?
I figure out how to get the subquery to work. First is my updated Utility method
public static <R, T> Subquery<T> getLatestSubelement(CriteriaBuilder cb, CriteriaQuery<R> c, Class<T> clazz, SingularAttribute<T, Instant> attribute) {
//Get latest timestamp
Subquery<Instant> sq = c.subquery(Instant.class);
Root<T> subRoot = sq.from(clazz);
sq.select(cb.greatest(subRoot.<Instant>get(attribute)));
//Get object with the latest timestamp
Subquery<T> sq2 = c.subquery(clazz);
Root<T> subRoot2 = sq2.from(clazz);
sq2.where(subRoot2.get(attribute).in(sq));
return sq2;
}
Here is the code that uses the utility method
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
joinType = JoinType.INNER;
//use fetch instead of join to prevent duplicates in Lists
root.fetch(Demographic_.user, joinType);
Subquery<ApplicationUser> sq = JpaUtil.getLatestSubelement(cb, c, ApplicationUser.class, ApplicationUser_.lastWrite);
c.where(root.get(Demographic_.user).in(sq));
TypedQuery<Demographic> q = em.createQuery(c);
Stream<Demographic> stream = q.getResultStream();

How to query #ElementCollection HashMap

I have an entity with different fields:
#Entity
public class TestEntity {
private int id;
private String name;
private String description;
#ElementCollection
private Map<String, String> parameter = new HashMap<>();
}
The resulting tables are the following:
TestEntity(id, name, description)
TestEntity_parameter(TestEntity_id, parameter, parameter_KEY)
Now I want to create a named query for this TestEntity that checks if there exists a parameter_KEY of value "test" and with a parameter :parameter.
I tried something like this:
select te from TestEntity te join TestEntity_parameter tep where tep.parameter_KEY = test AND tep.parameter = :parameter
But when I try to deploy, I get an error.
I'm relatively new to hibernate and java ee. Maybe my approach is wrong but I did not find anything how to access the fields of a map with a named query since it creates a new table for that map. So i thought that I need to join those tables.
Hope you guys can help me :)
Thanks a lot :)
Greetings
Simon
You could use the below query.
SELECT te FROM TestEntity te INNER JOIN te.parameter p WHERE KEY(p) = :YOUR_KEY
AND VALUE(p) = :YOUR_VALUE

JPA CriteriaQuery multiselect from several entities

Right now, I am using the method multiselect of CriteriaQuery to put some values from entity Termine in entity Task like this:
CriteriaBuilder builder = getEm().getCriteriaBuilder();
CriteriaQuery<Task> taskCriteria = builder.createQuery(Task.class);
Root<Termin> terminRoot = taskCriteria.from(Termin.class);
taskCriteria.multiselect(terminRoot.get("text"), terminRoot.get("empfaenger"), terminRoot.get("datVon"));
taskCriteria.where(builder.equal(terminRoot.get("empfaenger"), "000"));
List<Task> task = getEm().createQuery(taskCriteria).getResultList();
return task;
This is working fine, but now I am willing to gather the values text, empfaenger and datVon not only from the entity Termine but also from the entity Aufgabe, so that I will have a list of Tasks, that contains every Termin and Aufgabe which are having the same empfaenger.
Is it possible? If yes, how?
Thanks a lot in advance for your help!
I would derive both classes from task.
#Entity(name="Task")
#Inheritance(strategy = InheritanceType.JOINED)
#NamedQuery(name="Task.findAll", query="SELECT t FROM Task t")
public class Task {
#Id
Long id;
String text;
String empfaenger;
}
#Entity
public class Termin extends Task{
...
}
#Entity
public class Aufgabe extends Task{
...
}
And select them with a named query
List<Task> resultList = entityManager.createNamedQuery("Task.findAll",Task.class).getResultList();
or a criteria query with Task as Root.
This is the way I did to collect data from multiple entities (custom Select).
For example, multiple entities:
Root<InflowEntity> rootInflow = criteriaQuery.from(InflowEntity.class);
Root<OutflowEntity> rootOutflow = criteriaQuery.from(OutflowEntity.class);
You select the attributes you need from the above 2:
criteriaQuery.multiselect(rootInflow.get("inflowID"), rootInflow.get("name"),
rootOutflow.get("count"), rootOutflow.get("dateRange"));
Add the predicates (constraints) you need, for example:
Predicate[] predicates = new Predicate[2];
predicates[0] = criteriaBuilder.equal(rootInflow.get("uuid"), loginContext.getUuid());
predicates[1] = criteriaBuilder.equal(rootOutflow.get("uuid"), loginContext.getUuid());
Process the results:
criteriaQuery.where(predicates);
List<ResultsBean> results = session.createQuery(criteriaQuery).getResultList();
This Java bean (this is not the Hibernate entity), ResultsBean, stores the results. That is, it needs to have a constructor to accommodate the input the way the multiselect is arranged.

How to do a search with multiple ElementCollections

Given the following entity:
#Entity
#Table(name = "subscription")
public class Subscription implements Serializable {
private static final long serialVersionUID = 1L;
#ElementCollection
#CollectionTable(joinColumns= #JoinColumn(name="subscription"))
private Set<Code> mainCodes = new HashSet<>();
#ElementCollection
#CollectionTable(joinColumns= #JoinColumn(name="subscription"))
private Set<Code> otherCodes = new HashSet<>();
}
So a Subscription can have zero or more mainCodes or otherCodes it's interested in. I can get hold of the mainCode and otherCode of a certain object that passes by. The codes themselves are embeddables with only single String fields.
How do I create a JPA Query (or CriteriaBuilder) which searches in these collections with an "OR" mechanism?
So basically I'm looking for a query like this:
select s from subscription s where :myMainCode IN s.mainCodes OR :otherCode IN s.otherCodes
Is something like this doable with CriteriaBuilder or do I need to use a more explicit query? If so, what does the query look like?
EDIT: Tried this with CriteriaBuilder:
final CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
final CriteriaQuery<Subscription> cq = cb.createQuery(Subscription.class);
final Root<Subscription> root = cq.from(Subscription.class);
final Expression<Collection<Code>> mainCodes = root.get("mainCodes");
final Predicate containsMainCode = cb.isMember(obj.getClassCode(), mainCodes);
final Expression<Collection<Code>> otherCodes = root.get("otherCodes");
final Predicate containsOtherCode = cb.isMember(obj.getOtherCode(), otherCodes);
final Predicate searchPredicate = cb.or(containsMainCode, containsOtherCode);
cq.select(root).where(searchPredicate);
However, this creates an inner join of both collections involved, meaning that it will return no results if there is a row for mainCode, but not for otherCode in the database, it generates this query:
SELECT t0.ID
FROM Subscription_OTHERCODES t2, Subscription_MAINCODES t1, subscription t0
WHERE ((t1.CODESYSTEM = ?) AND (t1.CODE = ?)) OR ((t2.CODESYSTEM = ?) AND (t2.CODE = ?))) AND ((t1.subscription = t0.ID) AND (t2.subscription = t0.ID))
So even if it finds a matching mainCode, it fails if it doesn't have any otherCode.
It is other way around that in your example.
For example if the code has name property):
select s from Subscription s left join s.mainCodes m left join s.otherCodes o
where m.name IN :myMainCode or o.name IN :myOtherCode