微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何使用JPQL,复合主/外键和MySQL方言查找缺少的@ManyToOne关系

如何解决如何使用JPQL,复合主/外键和MySQL方言查找缺少的@ManyToOne关系

我正在尝试做一个听起来很简单的事情,即查询雇员存储库中的根管理员,其中根管理员是没有经理的雇员。有两件事使这个简单的问题变得复杂:

  1. 底层员工表具有复合主键
  2. 我需要指代“经理”员工的关系必须共享该主键的一个元素,与this question
  3. 非常相似

鉴于我的单元测试通过了嵌入式H2数据库和相应的H2方言,因此我认为我的答案是正确的。但是,当我使用MysqL或MariaDB方言进行生产时,(至少在我看来)我得到的是无效的sql,并且MysqL数据库引发了异常。

这是(简化的)employees表,很遗憾,我无法更改其设计:

+-------------------------------+
|           employees           |
+------------+-------------+----+
| id         | varchar(36) | PK |
| tenant_id  | char(15)    | PK |
| manager_id | varchar(36) |    |
+------------+-------------+----+

这是实体:

@Entity
@Table(name = "employees")
@IdClass(EmployeeTenantKey.class)
public class Employee {

    static final String ID_DEF = "varchar(36)";
    static final String TID_DEF = "char(15) default 'default'";

    @Id
    @Column(name = "id",nullable = false,updatable = false,columnDeFinition = ID_DEF)
    private String id;

    @Id
    @Column(name = "tenant_id",columnDeFinition = TID_DEF)
    private String tenantId;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumnsOrFormulas(value = {
            @JoinColumnorFormula(formula = @JoinFormula(value = "tenant_id",referencedColumnName = "tenant_id")),@JoinColumnorFormula(column = @JoinColumn(name = "manager_id",referencedColumnName = "id",columnDeFinition = ID_DEF,foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)))
    })
    private Employee manager;

    ...
}

最后,使用JPQL的spring数据存储库查找根管理器:

public interface EmployeeRepository extends JpaRepository<Employee,EmployeeTenantKey> {

    @Query("select rootManager from Employee rootManager " +
            "where not exists (" +
            "   select employee from Employee employee " +
            "   where rootManager.manager = employee" +
            ")")
    List<Employee> findRootManagers();

}

当方言为org.hibernate.dialect.H2Dialect时,将生成以下sql

select * from employees employee0_ 
where not exists (
  select (employee1_.id,employee1_.tenant_id) from employees employee1_ 
  where employee0_.manager_id=employee1_.id and employee0_.tenant_id=employee1_.tenant_id
)

如果我将方言更改为org.hibernate.dialect.MysqL8Dialectorg.hibernate.dialect.MariaDB103Dialect,请注意第二个where子句中的更改:

select * from employees employee0_ 
where not exists (
  select (employee1_.id,employee1_.tenant_id) from employees employee1_ 
  where (employee0_.manager_id,employee0_.tenant_id)=(employee1_.id,employee1_.tenant_id)
)

尽管H2数据库引擎将在我的单元测试中接受此sql(尽管使用了MysqL方言),但MysqL数据库引擎(通过v8.0.21 Connector / J)会引发以下错误

java.sql.sqlException: Operand should contain 1 column(s)
    at com.MysqL.cj.jdbc.exceptions.sqlError.createsqlException(sqlError.java:129) ~[mysql-connector-java-8.0.21.jar:8.0.21]
    at com.MysqL.cj.jdbc.exceptions.sqlError.createsqlException(sqlError.java:97) ~[mysql-connector-java-8.0.21.jar:8.0.21]
    at com.MysqL.cj.jdbc.exceptions.sqlExceptionsMapping.translateException(sqlExceptionsMapping.java:122) ~[mysql-connector-java-8.0.21.jar:8.0.21]
    at com.MysqL.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) ~[mysql-connector-java-8.0.21.jar:8.0.21]
    at com.MysqL.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1003) ~[mysql-connector-java-8.0.21.jar:8.0.21]

这是休眠方言中的错误吗?有解决这个问题的更好方法吗?

如果您想查看一个有效的示例,请在此处签到简化的源代码

https://github.com/chaserb/foreign-key-dereference

在使用Maven 3进行克隆和构建之后,您将在构建中唯一的单元测试上方立即看到生成的“ select ...” sql。在外面,它使用H2方言。要更改此设置,请更新src/main/resources/application-test.properties,然后重新运行Maven构建。您会在生成sql中看到差异。

注意:我可以通过本机查询获得所需的结果。原生查询的逻辑更直接地反映了问题的本质,因为我能够直接处理外键元素-在JPQL中我不知道如何做:

public interface EmployeeRepository extends JpaRepository<Employee,EmployeeTenantKey> {

    @Query(value = "select * from employees emp " +
            "where emp.manager_id is null or emp.manager_id = ''",nativeQuery = true)
    List<Employee> findRootManagers();

}

但是,我试图尽可能避免使用本机查询

解决方法

这是休眠方言中的错误吗?

肯定是那样。例如,如果您将内部查询的select employee替换为select employee.id是否有效?

是否有更好的方法来解决此问题?

好吧,您可以将manager_id的映射重复为一个简单的列:

@Column(name = "manager_id",insertable = false,updatable = false)
private String managerId;

随后,您将可以在JPQL查询中使用managerId

注释#1 :最初我以为SELECT e FROM Employee e WHERE e.manager IS NULL是您问题的答案,但后来我对其进行了测试,然后将其翻译为where (employee.manager_id is null) AND (employee.tenant_id is null),这听起来很愚蠢。的休眠状态-在我看来应该改为使用OR

注释#2 :您可能想看看this approach to multitenancy,它可能使您能够使用更自然的映射方案。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。