Spring Data JPA复合主键删除与存在性查询映射问题求解
定义了使用@IdClass做复合主键的JPA实体,以及基于复合主键TokenId实现exists、delete逻辑的Repository层代码,实体代码如下:
@Entity @IdClass(TokenId.class) @NoArgsConstructor @AllArgsConstructor public class Token { @Id private String setId; @Id private String id; @NotNull private long expiration; private boolean active; private boolean setActive; }
Repository层代码如下:
@Repository public interface TokenRepositorySql extends JpaRepository<Token, TokenId> { @Transactional void deleteByIdIn(List<TokenId> id); boolean existsByIdAndActiveTrueAndExpirationGreaterThan(TokenId tokenId, long currentTimestamp); }
上述delete、exists方法执行时,Hibernate无法将方法名中的Id映射为TokenId复合主键类型,反而要求传入String类型的id参数,抛出异常如下:
Parameter value element [com.abc.security.token.repository.sql.entity.TokenId@458c58e3] did not match expected type [java.lang.String (n/a)]
如果在Repository方法中拆分传入复合主键包含的setId、id两个属性值,方法可以正常运行,但需要实现直接传入复合主键对象即可完成操作的逻辑。
Spring Data JPA派生查询的属性解析规则是优先匹配实体类的直接属性名:方法名中写的ById会直接匹配到Token实体下类型为String的id字段(也就是复合主键的其中一个单属性),不会自动识别为@IdClass声明的整个复合主键类型,所以才会出现参数类型不匹配的错误。
最稳妥无歧义的实现方式是自定义JPQL语句,绕过派生查询的属性解析歧义,直接明确参数映射规则:
- 批量删除方法调整
加上@Modifying注解标识写操作,用JPQL显式匹配复合主键的两个字段,Hibernate 5.4及以上版本支持元组IN语法,写法非常简洁:
如果使用的Hibernate版本较低不支持元组IN,可以改成OR匹配的写法,或者在Service层拆分主键字段后传入。@Transactional @Modifying @Query("DELETE FROM Token t WHERE (t.setId, t.id) IN :ids") void deleteByIdIn(List<TokenId> ids); - exists判断方法调整
用SpEL取传入复合主键对象的两个属性值做匹配,不需要拆分传参:@Query("SELECT COUNT(t) > 0 FROM Token t WHERE t.setId = :#{#tokenId.setId} AND t.id = :#{#tokenId.id} AND t.active = true AND t.expiration > :currentTimestamp") boolean existsByIdAndActiveTrueAndExpirationGreaterThan(@Param("tokenId") TokenId tokenId, @Param("currentTimestamp") long currentTimestamp);
如果不想手写JPQL,也可以直接使用Spring Data JPA内置的主键操作方法:比如批量删除可以直接调用JpaRepository自带的deleteAllByIdInBatch(Iterable<TokenId> ids)方法,这个方法直接读取Repository接口声明的主键泛型TokenId做映射,不会触发派生查询的属性解析歧义。但带额外条件的exists查询没有对应内置方法,还是需要通过自定义JPQL实现。
注:如果把
@IdClass换成@EmbeddedId实现复合主键,可以减少属性名冲突的概率,但涉及额外条件的派生查询依然可能出现解析歧义,自定义JPQL是兼容性最好的方案。
内容的提问来源于stack exchange,提问作者Nipun Thathsara

