Критерии JPA: исключение QuerySyntaxException для левого соединения при обработке объекта

#java #hibernate #jpa #criteria-api

#java #спящий режим #jpa #критерии-api

Вопрос:

Модель:

 @Entity
public class User {

    @Id
    private Integer id;

    @JoinColumn(name = "user_id")
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Project> projects;
}
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Type")
public abstract class Project {

    @Id
    private Integer id;

    private String name;
}
@Entity
@DiscriminatorValue("Administrative")
public class AdminProject extends Project {

    private String departmentName;
}
@Entity
@DiscriminatorValue("Design")
public class DesignProject extends Project {

    private String companyName;
}
 

Я пытаюсь использовать api критериев JPA для запроса User объектов на основе атрибута реализации Project . Например, запросите всех пользователей, у которых есть проект с отделом «SOME_NAME» (это поле не существует DesignProject ).

Я вижу, что есть способ сделать это путем понижения значения Project объекта для запроса. Я пытаюсь сделать что-то похожее на:

 CriteriaBuilder cb...
Root<User> userRoot...
root = ((From) root).join("projects", JoinType.LEFT);
root = cb.treat(root, AdminProject.class);
root = root.get("departmentName");
 

Исключение:

исключение org.springframework.dao.InvalidDataAccessApiUsageException: исключение org.hibernate.hql.internal.ast.QuerySyntaxException: недопустимый путь: ‘generatedAlias2.DepartmentName’ [выберите generatedAlias0 из io.github.perplexhub.rsql.model.Пользователь как сгенерированный alias0 оставил соединение сгенерированным alias0.projects как сгенерированный alias1, где treat(сгенерироВанный alias2 как io.github.perplexhub.rsql.model.AdminProject).DepartmentName=:param0]; вложенным исключением является java.lang.Исключение IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Недопустимый путь: ‘generatedAlias2.DepartmentName’ [выберите generatedAlias0 из io.github.perplexhub.rsql.model.Пользователь как сгенерированный alias0 оставил соединение сгенерированным alias0.projects как сгенерированный alias1, где treat(сгенерироВанный alias2 как io.github.perplexhub.rsql.model.AdminProject).DepartmentName=:param0]

Чего мне не хватает? Связано ли это с объединением или с тем, как впоследствии происходит понижение?

Редактировать

После ответа @K.Николас, мне удалось заставить запрос работать в изолированном сценарии, но не в моем приложении. Но я заметил, что entityManager.createQuery(query) вызов вызывает исключение, указанное выше, при первом вызове, и оно работает, если я вызываю его снова без изменения объекта запроса. Вот запрос, сгенерированный при втором вызове (этот запрос находит нужные мне объекты из базы данных):

выберите generatedAlias0 от пользователя как generatedAlias0 левое соединение generatedAlias0.projects как generatedAlias2, где treat(generatedAlias2 как io.github.perplexhub.rsql.model.AdminProject).DepartmentName=:param0

Почему диспетчер объектов создает два разных запроса при вызове два раза подряд?

Ответ №1:

Я бы сделал Entitys это немного по-другому, как вы увидите. Основная проблема заключается в том, что вы используете User в качестве своего корня соединение со списком Projects . Это вызывает беспокойство, потому что у вас должен быть внешний ключ в Project классе и использовать projects поле как поле только для запроса. Это то, что я сделал. Так работает лучше. Это также вызывает беспокойство, потому что вам нужно сделать a join fetch вместо a join , чтобы projects получить выборку вместе с users .

Итак, во-первых, объекты выглядят так:

 @Entity
public class User { 
    @Id
    private Integer id; 
    @OneToMany(mappedBy="user")
    private List<Project> projects;
}

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Type")
public abstract class Project {
    @Id
    private Integer id;
    private String name;    
    @ManyToOne
    private User user;
}

@Entity
@DiscriminatorValue("Administrative")
public class AdminProject extends Project {
    private String departmentName;
}

@Entity
@DiscriminatorValue("Design")
public class DesignProject extends Project {    
    private String companyName;
}
 

Немного покопавшись, я нашел запрос JPQL, который делает трюк. Это было отправной точкой:

 List<User> users = entityManager.createQuery("select distinct(u) from User u join fetch u.projects p where TYPE(p) = 'Administrative' and p.departmentName = 'dept1'", User.class).getResultList();
 

После еще немного копания я обнаружил, что все treat работает нормально, если вы делаете это правильно, и что с JPA 2.1 вы должны использовать EntityGraph do get join для выполнения a fetch .

 CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> root = query.from(User.class);
Join<User, Project> join = root.join("projects");
query.select(root).where(builder.equal(builder.treat(join, AdminProject.class).get("departmentName"), "dept1"));
EntityGraph<User> fetchGraph = entityManager.createEntityGraph(User.class);
fetchGraph.addSubgraph("projects");
users = entityManager.createQuery(query.distinct(true)).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
 

В качестве примечания запросы, сгенерированные как немного отличающиеся, но я не смотрел на них так внимательно. Вы должны.

Комментарии:

1. Спасибо за ответ. Я протестировал двунаправленную связь, и график выборки на самом деле не нужен. Ваше решение работает даже без него. У меня есть то, что исключение по моему вопросу возникает при первом вызове entityManager.createQuery(query) , но не во второй раз. Я отредактирую свой вопрос с учетом достижений, которые я сделал с вашим ответом .