JPA 标准:处理实体上左连接的 QuerySyntaxException

问题描述

型号:

@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;
}

我正在尝试使用 JPA 的标准 API 根据 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: Invalid path: 'generatedalias2.departmentName' [selectgeneratedalias0 from io.github.perplexhub.rsql.model.User asgeneratedalias0 left join generatealias0.projects asgeneratedalias1 wheretreat(generatedalias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0];嵌套异常是 java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedalias2.departmentName' [selectgeneratedalias0 from io.github.perplexhub.rsql.model.User asgeneratedalias0 left join generatedalias0。项目作为generatedalias1 wheretreat(generatedalias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0]

我错过了什么?是否与加入有关,或者之后向下转换是如何发生的?

编辑

在@K.Nicholas 的回答之后,我设法使查询在孤立的场景中起作用,但在我的应用程序中不起作用。 但是,我注意到 entityManager.createquery(query) 调用在第一次调用时抛出了上面的异常,如果我再次调用它而不更改查询对象,它会起作用。这是第二次调用生成查询(此查询数据库中找到我想要的对象):

selectgeneratedalias0 from User asgeneratedalias0 left joingeneratedalias0.projects asgeneratedalias2 wheretreat(generatedalias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0

为什么实体管理器在连续调用两次时会创建两个不同的查询

解决方法

如您所见,我会对 Entitys 做一些不同的处理。主要问题是您使用 User 作为根并连接到 Projects 列表。这是一个问题,因为您应该在 Project 类上拥有外键并将 projects 字段用作仅查询字段。这就是我所做的。这样效果更好。这也是一个问题,因为您必须执行 join fetch 而不是 join,以便 projectsusers 一起获取。

所以,首先,实体是这样的:

@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 确实让 join 执行 fetch {1}}。

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();

顺便说一下,生成的查询略有不同,但我并没有仔细研究它们。你应该。