I have an entity (Person) which is a OneToOne to another entity (User). I need to find all Person entities which match User.name using CriteriaQuery.
I can do simple CriteriaQuery for direct attributes of Person just fine:
builder.like(builder.lower(root.get(column)), "%" + pattern.toLowerCase() + "%")
I'm a bit lost on how to do CriteriaQuery queries in this more complex case. From my searches here and elsewhere I think I have to use some kind of Join but I can't get my head wrapped around it.
#Entity()
public class Person extends ModelCore {
#Basic()
private String iD = null;
#OneToOne(cascade = { CascadeType.ALL })
#JoinColumns({ #JoinColumn(name = "T_User") })
private User user = null;
}
#Entity()
public class User extends ModelCore {
#Basic()
private String iD = null;
#Basic()
private String name = null;
}
#Entity()
public class ModelCore{
#Basic()
private Long dbID = null;
}
SOLVED
Nikos's solution works great (thank you!):
String username = ... // the criterion
EntityManager em = ...
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
Join<Person, User> joinUser = root.join("user");
query.where(cb.like(cb.lower(joinUser.get("name")), "%" + username.toLowerCase() + "%"));
Edit 1: Added ModelCore as base class.
Edit 2: Add working solution
Criteria API can be confusing as complexity grows. The first step I always follow is to write down the JPQL query. In this case:
SELECT p
FROM Person p
JOIN User u
WHERE LOWER(u.name) LIKE :username
Translating this to Criteria API is:
// These are initializations
String username = ... // the criterion
EntityManager em = ...
CriteriaBuilder cb = em.getCriteriaBuilder();
// Next line means "the result of the query is Person"
CriteriaQuery<Person> query = cb.createQuery(Person.class);
// This matches the "FROM Person p" part of the JPQL
Root<Person> root = query.from(Person.class);
// This matches the "JOIN User u" part of the JPQL
Join<Person, User> joinUser = root.join("user"); // if you have created the metamodel, adjust accordingly
// This is the "WHERE..." part
query.where(cb.like(cb.lower(joinUser.get("name")), "%" + username.toLowerCase() + "%"));
The WHERE part is confusing because you have to convert the infix SQL/JPQL operators to prefix (i.e. x LIKE y becomes cb.like(x, y)), but the mapping is straightforward.
Related
I am using Java 8 with JPA (Hibernate 5.2.1). I have a like clause which works perfectly, until I introduce the like clause to make use the foreign key table values too.
I am getting the following error:
BasicPathUsageException: Cannot join to attribute of basic type
I think the problem is related to the fact that I have a join table, and I am not sure how to create the LIKE Join using the join table.
Employee - employee_category - category
model (Employee.java):
#ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable
(
name="employee_category",
joinColumns={ #JoinColumn(name="EMP_ID", referencedColumnName="ID") },
inverseJoinColumns={ #JoinColumn(name="CAT_ID", referencedColumnName="ID") }
)
private Set<Category> categories;
model (Category.java):
#Id
private String id;
JPA
When I introduce the following code, it fails with the error above (I think the problem is with this new piece of code):
Join<T, Category> category = from.join("id");
predicates.add(criteriaBuilder.like(category.<String>get("name"), "%" + searchQuery + "%"));
This is the entire method:
protected List<T> findAllCriteria(String[] orderByAsc, String[] orderByDesc, Class<T> typeParameterClass,
int firstResult, int maxResults, String searchQuery) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(typeParameterClass);
// from
Root<T> from = criteriaQuery.from(typeParameterClass);
criteriaQuery.select(from);
// like
if (searchQuery != null && searchQuery.trim().length() > 0) {
List<Predicate> predicates = new ArrayList<Predicate>();
for (String name : getColumnNames(typeParameterClass)) {
Predicate condition = criteriaBuilder.like(from.<String>get(name), "%" + searchQuery + "%");
predicates.add(condition);
}
Join<T, Category> category = from.join("id");
predicates.add(criteriaBuilder.like(category.<String>get("name"), "%" + searchQuery + "%"));
criteriaQuery.where(criteriaBuilder.or(predicates.toArray(new Predicate[] {})));
}
List<T> results = (List<T>) entityManager.createQuery(criteriaQuery).setFirstResult(firstResult)
.setMaxResults(maxResults).getResultList();
return results;
}
Any help will be appreciated.
SOLVED:Join category = from.join("categories");
I have a table that have few rows of variable. One of this row link to another table that have more rows.
To put it clear, 1 table is called Connection. The variable is:
name, groupname, etc.
The groupname should link to a second table called ConnectionGroup. The variable is:
name, id.
My idea is to query to the ConnectionGroup table by name. The abstract class for Connection and ConnectionGroup is like this:
public abstract class Connection_ {
public static volatile SingularAttribute<Connection, String>name;
public static volatile SingularAttribute<Connection, String>host;
public static volatile SetAttribute<Connection, ConnectionGroup>connectionGroups;
}
public abstract class ConnectionGroup_ {
public static volatile SingularAttribute<ConnectionGroup, String> name;
public static volatile SingularAttribute<ConnectionGroup, Long> id;
}
To query this, I assume I have to join these 2 table and then only query them. This is the code that I have tried:
#PersistenceContext
private EntityManager em;
public List<Connection> retrieveAll( String groupFilter, int start, int length) {
ServiceUtil.requireAdmin();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Connection> q = cb.createQuery(Connection.class);
Root<Connection> c = q.from(Connection.class);
Join<Connection, ConnectionGroup> join = c.join(Connection_.connectionGroups);
q.select(c);
c.fetch(Connection_.connectionGroups).fetch(ConnectionGroup_.id);
Predicate groupPredicate = cb.equal(
c.get(Connection_.connectionGroups), "%" + groupFilter + "%");
q.where(groupPredicate);
List<Connection> results = em.createQuery(q).setFirstResult(start)
.setMaxResults(length).getResultList();
for (Connection conn : results) {
logger.info( "getconnectionGroups =["+ conn.getConnectionGroups() + "]");
for (ConnectionGroup conngroup : conn.getConnectionGroups()) {
logger.info("connectiongroups = [" + conngroup.getName() + "]");
}
}
}
Things that I have tried, changing this:
Predicate groupPredicate = cb.equal(
c.get(Connection_.connectionGroups), "%" + groupFilter + "%");
to this:
Predicate groupPredicate = cb.equal(join.get(ConnectionGroup_.name),
"%" + groupFilter + "%");
Changing this:
Join<Connection, ConnectionGroup> join = c
.join(Connection_.connectionGroups);
To this:
Join<Connection, ConnectionGroup> join = c.join("connectionGroups");
When I tried these method, I keep getting an exception Cannot join to attribute of basic type
I also tried changing the code into this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Connection> q = cb.createQuery(Connection.class);
Root<Connection> c = q.from(Connection.class);
Join<Connection, ConnectionGroup> join = (Join<Connection, ConnectionGroup>) c.fetch(Connection_.connectionGroups);
q.select(c);
Predicate groupPredicate = cb.equal(join.get(ConnectionGroup_.name), "%" + groupFilter + "%");
q.where(groupPredicate);
List<Connection> results = em.createQuery(q).setFirstResult(start)
.setMaxResults(length).getResultList();
Which return an exception:
query specified join fetching, but the owner of the fetched association was not present in the select list
I'm using these site as reference to write the code:
JPA CriteriaBuilder using fetch joins and result objects
JPA 2 Criteria Fetch Path Navigation
wiki.eclipse.org
developer.com
How can I query the name from ConnectionGroup table? Is my approach wrong?
If you're trying to use filtering by name through string matching, CriteriaBuilder.like() is more appropriate to use than CriteriaBuilder.equal().
To achieve what you're trying to query, you can use the ff. JP QL query:
SELECT DISTINCT conn FROM Connection conn JOIN conn.connectionGroups connGrp
WHERE connGrp.name LIKE :groupFilter
Translating JP QL to CriteriaQuery, you'll have:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Connection> q = cb.createQuery(Connection.class);
Root<Connection> conn = q.from(Connection.class);
Join<Connection, ConnectionGroup> connGrp = conn.join(Connection_.connectionGroups);
q.select(conn).distinct(true);
ParameterExpression<String> param = cb.parameter(String.class, "%"+ groupFilter + "%");
q.where(cb.like(connGrp.get(ConnectionGroup_name), param));
How to join with extra ON clause parameters?
I have an SQL:
select * from Address address left outer join AddressLine line
on line.id = address.lineId AND line.type = 'MA'
where address.id = 1;
I have code:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<AddressResult> query = cb.createQuery(AddressResult.class);
Root<Address> r = query.from(Address.class);
Join<Address, AddressLineMA >linesMA= r.join(Address_.linesMajor, JoinType.LEFT);
To get data from data base.
This query is not working as expected I get SQL like this:
select * from Address address left outer join AddressLine line
on line.id = address.lineId
where address.id = 1;
AND line.type = 'MA' is missing. Does some one knows how to fix this?
My AddressLineMA.class looks like this:
#Entity
#DiscriminatorValue(value = "MA")
public class AddressLineMA extends AddressLine {
}
#Entity
#Table(name = "AddressLine")
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class AddressLine {
private Long id;
private String type;
private String line;
}
JPA queries always return all subclasses by default. So if you want only instances of AddressLineMA, you must change your query and relationshipto be on AddressLineMA instead of the inheritance root class, AddressLine.
If there are other subclasses of AddressLineMA that you want to exclude, then you can use the TYPE operator added to JPA 2.0:
query.where(cb.equal(linesMA.type(), package.AddressLineMA.class));
Unfortunately, adding it into the ON clause is only supported in the yet to be released JPA 2.1 spec:
linesMA.on(cb.equal(linesMA.type(), package.AddressLineMA.class));
I try to convert a sql query to Criteria API without success so far. I can create two separate queries which return the values I need, but I don't know how to combine them in a single query.
Here is the sql statement which works:
select company.*, ticketcount.counter from company
join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
This Criteria query returns the inner query results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<intCompany> qTicket = cb.createQuery(intCompany.class);
Root<Ticket> from = qTicket.from(Ticket.class);
Path groupBy = from.get("company");
Predicate state = cb.notEqual(from.<State>get("state"), getStateById(16));
qTicket.select(cb.construct(
intCompany.class, cb.count(from),from.<Company>get("company")))
.where(state).groupBy(groupBy);
em.createQuery(qTicket).getResultList();
In the application I defined a small wrapper/helper class:
public class intCompany{
public Company comp;
public Long opentickets;
public intCompany(Long opentickets,Company comp){
this.comp = comp;
this.opentickets = opentickets;
}
public intCompany(){
}
}
So does anyone has an idea how to get this working?
Update
Thank you. I changed my criteria query as you suggested. I just had to add a loop at the end to get the information I wanted.
List<intCompany> result = em.createQuery(cq).getResultList();
List<Company> cresult = new ArrayList();
for(intCompany ic: result){
ic.comp.setOpentickets(ic.opentickets.intValue());
cresult.add(ic.comp);
}
return cresult;
Maybe it is just not possible to convert the original sql to Criteria API.
Another update
I figured out I had to change the original sql expression to
select company.*, ticketcount.counter from company
left join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
Otherwise I do not get companies with no entries in the ticket table.
So are there any other suggestions?
You have almost everything done.
//---//
CriteriaBuilder cb = em.getCriteriaBuilder();
//Your Wrapper class constructor must match with multiselect arguments
CriteriaQuery<IntCompany> cq = cb.createQuery(IntCompany.class);
//Main table
final Root<Ticket> fromTicket= cq.from(Ticket.class);
//Join defined in Ticket Entity
final Path company = fromTicket.get("company");
//Data to select
cq.multiselect(cb.count(from), company);
//Grouping
cq.groupBy(company);
//Restrictions (I don't really understand what you're querying)
Predicate p = cb.lessThan(fromTicket.get("state"), 16);
//You can add more restrictions
// p = cb.and/or(p, ...);
cq.where(p);
List<IntCompany> results = entityManager.createQuery(cq).getResultList();
This should work as expected.
I had similar problem. My solution was to use left outer joins.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> query = cb.createQuery(Entity.class);
Root<Entity> root = query.from(Entity.class);
Join<Entity,ChildEntity> join = root.join(Entity_.children, JoinType.LEFT);
query.groupBy(root.get( Entity_.id ));
query.select(
cb.construct(
EntityDTO.class,
root.get( Entity_.id ),
root.get( Entity_.name ),
cb.count(join)
));
This JoinType.LEFT guarantees that you will get Entity records (companies) even if it doesn't have any child entities (tickets).
Entity class:
#Entity
public class Entity {
...
#OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent", fetch = FetchType.LAZY, orphanRemoval = false)
private Set<ChildEntity> objects;
...
}
Static model:
#StaticMetamodel( Entity.class )
public class Entity_ {
public static volatile SingularAttribute<Entity, Long> id;
public static volatile SingularAttribute<Entity, String> name;
...
public static volatile SetAttribute<Entity, ChildEntity> objects;
}
I am stuck with a problem concerning JPA-2.0 queries with relationships. How would it be possible to select any Dataset with at least one Event with type = B?
#Entity
class Dataset {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "dataset")
public List<Event> events;
}
#Entity
class Event {
#ManyToOne
#JoinColumn
public Dataset dataset;
public Type type;
}
enum Type {
A, B, C
}
My starting point is
CriteriaBuilder _builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> _criteria = _builder.createQuery(Dataset.class);
// select from
Root<Dataset> _root = _criteria.from(Dataset.class);
_criteria.select(_root);
// apply some filter as where-clause (visitor)
getFilter().apply(
_root, _criteria, _builder, em.getMetamodel()
);
// how to add a clause as defined before?
...
Any ideas on this. I tried to create a subqueries as well as a join, but I somehow did it wrong and always got all datasets as result.
Try
SELECT d FROM DataSet d WHERE EXISTS
(SELECT e FROM Event e WHERE e.dataSet = d and e.type = :type)
EDIT: As Pascal pointed out it looks like you are using the Criteria API. Not as familiar with this, but I'll have a stab.
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> criteria = builder.createQuery(Dataset.class);
Root<Dataset> root = criteria.from(Dataset.class);
criteria.select(root);
// build the subquery
SubQuery<Event> subQuery = criteria.subQuery(Event.class);
Root<Event> eventRoot = subQuery.from(Event.class);
subQuery.select(eventRoot);
ParameterExpression<String> typeParameter = builder.parameter(String.class);
Predicate typePredicate = builder.equal(eventRoot.get(Event_.type), typeParameter));
// i have not tried this before but I assume this will correlate the subquery with the parent root entity
Predicate correlatePredicate = builder.equal(eventRoot.get(Event_.dataSet), root);
subQuery.where(builder.and(typePredicate, correlatePredicate);
criteria.where(builder.exists(subQuery)));
List<DataSet> dataSets = em.createQuery(criteria).getResultList();
Phew that was hard work. I'm going back to Linq now.