如何解决如何使用JPQL,复合主/外键和MySQL方言查找缺少的@ManyToOne关系
我正在尝试做一个听起来很简单的事情,即查询雇员存储库中的根管理员,其中根管理员是没有经理的雇员。有两件事使这个简单的问题变得复杂:
- 底层员工表具有复合主键
- 我需要指代“经理”员工的关系必须共享该主键的一个元素,与this question 非常相似
鉴于我的单元测试通过了嵌入式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.MysqL8Dialect
或org.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 举报,一经查实,本站将立刻删除。