3

Im trying to write query, which returns me list of Drivers wich is not assigned to route.

My database a set up as following.

Route:
route_id
user_id//specified as driver

User:
user_id
role // need to select user, which is Driver role

Only route sees user(driver), user(driver) doesn`t see route.

This is my try to write such query.

public List<User> getUnsignedDrivers(){
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

    CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
    Root<User> user = query.from(User.class);
    query.select(user);

    Subquery<Route> subquery = query.subquery(Route.class);
    Root<Route> subRootEntity = subquery.from(Route.class);
    Predicate correlatePredicate = criteriaBuilder.equal(subRootEntity.get("Route_.User"), user);
    subquery.where(correlatePredicate);
    query.where(criteriaBuilder.not(criteriaBuilder.exists(subquery)));

    TypedQuery<User> typedQuery = entityManager.createQuery(query);
    return typedQuery.getResultList();
}

I`m new to jpa, so that is the problem.

To be more specific, i need to select user with role driver, which are not set to any route

My entities are set up as following:

    @Entity
public class Route {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

@OneToOne(fetch = FetchType.EAGER, cascade = {})
@JoinColumn(name = "user_id", nullable = true)
private User driver;
.....
@Entity
public class User {

public static enum Role {
    ADMIN, MANAGER, DRIVER;
}

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;

UPDATE: current query

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

    CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
    Root<User> user = query.from(User.class);
    Predicate predicateRole = criteriaBuilder.equal(user.get("role"), User.Role.DRIVER);
    query.where(predicateRole);
    query.select(user);


    Subquery<Route> subquery = query.subquery(Route.class);
    Root<Route> subRootEntity = subquery.from(Route.class);
    Predicate correlatePredicate = criteriaBuilder.equal(subRootEntity.get("driver"), user);
    subquery.where(correlatePredicate);
    query.where(criteriaBuilder.not(criteriaBuilder.exists(subquery)));

    TypedQuery<User> typedQuery = entityManager.createQuery(query);
    return typedQuery.getResultList();

problem still exist

i get this exception:

java.lang.IllegalStateException: No explicit selection and an implicit one cold not be determined
at org.hibernate.ejb.criteria.QueryStructure.locateImplicitSelection(QueryStructure.java:296)
at org.hibernate.ejb.criteria.QueryStructure.render(QueryStructure.java:249)
at org.hibernate.ejb.criteria.CriteriaSubqueryImpl.render(CriteriaSubqueryImpl.java:282)
at org.hibernate.ejb.criteria.predicate.ExistsPredicate.render(ExistsPredicate.java:58)
at org.hibernate.ejb.criteria.QueryStructure.render(QueryStructure.java:258)
at org.hibernate.ejb.criteria.CriteriaQueryImpl.render(CriteriaQueryImpl.java:340)
at org.hibernate.ejb.criteria.CriteriaQueryCompiler.compile(CriteriaQueryCompiler.java:217)
at org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:587)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
at $Proxy25.createQuery(Unknown Source)

throws at this line TypedQuery<User> typedQuery = entityManager.createQuery(query);

Work around this works for me quite well. I wrote this, because i can`t use inverse relationship.

public List<User> getUnsignedDrivers(){
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

    CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
    Root<User> user = query.from(User.class);
    Predicate predicateRole = criteriaBuilder.equal(user.get("role"), User.Role.DRIVER);
    query.where(predicateRole);
    query.select(user);

    TypedQuery<User> typedQuery = entityManager.createQuery(query);
    List<User> allDrivers = typedQuery.getResultList();
    List<User> notAssignedDrivers = new ArrayList<User>();
    List<Route> haveDriverRoutes = getRouteWithNoDrives();
    for (User driver : allDrivers){
        if (!isDriverAssigned(haveDriverRoutes,driver.getId())){
            notAssignedDrivers.add(driver);
        }
    }
    return notAssignedDrivers;
}

private boolean isDriverAssigned(List<Route> haveDriverRoutes, long driverId){
    for(Route route : haveDriverRoutes){
        if (route.getDriver().getId() == driverId){
            return true;
        }
    }
    return false;
}

@SuppressWarnings("unchecked")
public List<Route> getRouteWithNoDrives() {
    Query query = entityManager.createQuery("SELECT o FROM " + type.getSimpleName() + " o WHERE o.driver != null");
    return  query.getResultList();
}
kuldarim
  • 1,096
  • 8
  • 21
  • 44

2 Answers2

1

You are missing the inverse OneToOne relationship in User entity:

@OneToOne(mappedBy="driver")
private Route route;

See this link about how to map OneToOne relationships.

And you have an error in this part: subRootEntity.get("Route_.User"). This is not a valid syntax, and you don't have a property called User in Route entity: the property is called driver (after reading your latest edit).

You have 2 ways to get that Path expression, either using:

Path<User> path = subRootEntity.get("driver");
// in a compact way: 
Predicate correlatePredicate = criteriaBuilder.equal(subRootEntity.get("driver"), user);

or by use of Metamodel:

Path<User> path = subRootEntity.get(Route_.driver);
// in a compact way: 
Predicate correlatePredicate = criteriaBuilder.equal(subRootEntity.get(Route_.driver), user);

You seem to have mixed the two approaches. See this article for further info about the use of Metamodel.

The rest of the query looks correct.

perissf
  • 15,979
  • 14
  • 80
  • 117
  • 1
    thanks for your post. Yes, i have an error in (subRootEntity.get("Route_.User"), user), it is because i don`t have and idea, how to define correct path, for User in criteriaBuilder.equal(subRootEntity.get("Route_.User"), user); I tried to use Path path = subRootEntity.get("user"); as you said, but it also throws me an exception, that "user" path doesn`t exist. How can i get the correct path? – kuldarim Dec 07 '12 at 09:31
  • `subRootEntity.get("driver");` path worked, know i changed to `Predicate correlatePredicate = criteriaBuilder.equal(subRootEntity.get("driver").get("id"), user.get("id"));` and it throws `java.lang.IllegalStateException: No explicit selection and an implicit one cold not be determinedat org.hibernate.ejb.criteria` exception. Any suggestion? – kuldarim Dec 07 '12 at 11:02
  • i also tried `Predicate correlatePredicate = criteriaBuilder.equal(subRootEntity.get("driver"), user);` but it throws `java.lang.IllegalStateException: No explicit selection and an implicit one cold not be determined` exception – kuldarim Dec 07 '12 at 11:05
  • it should be inverse `@OneToOne` to achieve what i`m trying to do? cause i need unidirectional `@OneToOne` , because Driver should not care about route, only route should know driver. Driver is user. User have 3 roles, manager, driver and admin, so it would be sad if i must use inverse `@OneToOne` – kuldarim Dec 08 '12 at 08:26
  • Does the query work with the inverse relationship? Yes. Does it work without? No. Have you read the link I put in my answer? It says it's necessary. So, where is the problem? – perissf Dec 09 '12 at 14:57
1

The line where you add the subquery should return something. So you should change: query.where(criteriaBuilder.not(criteriaBuilder.exists(subquery)));

to:

query.where(criteriaBuilder.not(criteriaBuilder.exists(subquery.select(subRootEntity))));