尝试使用 Spring Data JPA 运行存储过程时出现“类型不能为空”异常

Posted

技术标签:

【中文标题】尝试使用 Spring Data JPA 运行存储过程时出现“类型不能为空”异常【英文标题】:`Type cannot be null` exception when trying to run out Stored Procedure using Spring Data JPA 【发布时间】:2016-06-24 20:53:32 【问题描述】:

我正在尝试调用其签名如下所示的存储过程:

CREATE OR REPLACE PROCEDURE FIND_FIRST_BOOKMARK_GT(bookmark IN NUMBER, cur OUT SYS_REFCURSOR)

我正在使用 Spring-Data JPA 并尝试了许多不同的变体,但它们都或多或少地遵循以下模式。我的实体模型装饰如下:

@NamedStoredProcedureQuery(name = "Response.findFirstBookmarkGreaterThan", procedureName = "FIND_FIRST_BOOKMARK_GT",
    resultClasses = Response.class,
    parameters = 
        @StoredProcedureParameter(name = "bookmark", mode = ParameterMode.IN, type = Long.class),
        @StoredProcedureParameter(name = "cur", mode = ParameterMode.REF_CURSOR, type = void.class)
    )
)

然后我的存储库如下所示:

@Repository
public interface ResponseRepository extends CrudRepository<Response, Long>
    @Procedure("Response.findFirstBookmarkGreaterThan")
    Response findFirstBookmarkGreaterThan(@Param("bookmark") Long bookmark);

我已经按照很多示例的说明进行操作,但我总是遇到同样的错误,即:

java.lang.IllegalArgumentException: Type cannot be null
at org.hibernate.procedure.internal.AbstractParameterRegistrationImpl.setHibernateType(AbstractParameterRegistrationImpl.java:182) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.procedure.internal.AbstractParameterRegistrationImpl.<init>(AbstractParameterRegistrationImpl.java:131) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.procedure.internal.AbstractParameterRegistrationImpl.<init>(AbstractParameterRegistrationImpl.java:140) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.procedure.internal.AbstractParameterRegistrationImpl.<init>(AbstractParameterRegistrationImpl.java:97) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.procedure.internal.NamedParameterRegistration.<init>(NamedParameterRegistration.java:41) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.procedure.internal.ProcedureCallImpl.registerParameter(ProcedureCallImpl.java:344) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.internal.StoredProcedureQueryImpl.registerStoredProcedureParameter(StoredProcedureQueryImpl.java:152) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.springframework.data.jpa.repository.query.StoredProcedureJpaQuery.newAdhocStoredProcedureQuery(StoredProcedureJpaQuery.java:175) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.StoredProcedureJpaQuery.createStoredProcedure(StoredProcedureJpaQuery.java:130) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.StoredProcedureJpaQuery.doCreateQuery(StoredProcedureJpaQuery.java:89) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.StoredProcedureJpaQuery.createQuery(StoredProcedureJpaQuery.java:80) ~[spring-data-jpa-1.9.4.RELEASE.jar:na]
......

有什么想法吗?

【问题讨论】:

能不能试试不带cur参数引用声明的代码,看看错误是否依然存在 【参考方案1】:

使用@Procedure 遇到了同样的问题。作为一种解决方法,您可以参考服务类中的 entityManager 并从中调用过程。

@Service
public interface ResponseService 

    @PersistenceContext
    EntityManager entityManager;

    public Response findFirstBookmarkGreaterThan(Long bookmark)
          Query query  = entityManager.createNamedStoredProcedureQuery("Response.findFirstBookmarkGreaterThan");
          query.setParameter("bookmark", bookmark);
          return query.getFirstResult();
    ;

请注意,响应必须是 @Entity

【讨论】:

这行不通!这必须是类而不是接口。【参考方案2】:

使用原生查询我们也可以调用存储过程。 如果您使用的是 mysql,那么以下将是 systex:

 @Query(nativeQuery = true,value = "call getEmployeeList")
 List<Employee> getEmployeeList();

【讨论】:

什么是存储过程有参数?【参考方案3】:

我无法使这种语法正常工作,但是,从存储过程中检索的概念实际上是从(可能是视图)中选择的简写,并混合了应用一些基于输入的函数。为此,我已将存储过程替换为产生相同效果的函数/表。

@Query("SELECT r FROM RESPONSES r WHERE BOOKMARK = FIND_FIRST_BOOKMARK_GT(:bookmark)")
Response findFirstBookmarkGreaterThan(@Param("bookmark") Long bookmark);

这将@Query 语法与表和函数结合使用,其整体逻辑与调用我想要的存储过程相同。虽然可能有点难以理解,但语法至少比使用 @NamedStoredProcedureQuery 短得多。

除了最后一部分是 SELECT INTO 之外,该函数与存储过程最初所做的相同,现在它被封装为 @Query 的一部分。

【讨论】:

【参考方案4】:

这是一个老问题,但我遇到了同样的问题,经过几个小时的“阅读文档尝试错误”后终于得到解决。也许这对某人有用。您的存储库必须如下所示:

@Repository
public interface ResponseRepository extends CrudRepository<Response, Long>
    @Procedure(name = "Response.findFirstBookmarkGreaterThan")
    Response findFirstBookmarkGreaterThan(@Param("bookmark") Long bookmark);

关键在@Procedure 注解处。编码此注释时使用“名称”而不是“值”(默认情况下)。 Baeldung 博客上的这篇文章为我提供了解决此错误的最终方法:https://www.baeldung.com/spring-data-jpa-stored-procedures。

【讨论】:

以上是关于尝试使用 Spring Data JPA 运行存储过程时出现“类型不能为空”异常的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data JPA Custom Repository

Spring Data Jpa:保存方法仅返回选择,但不执行插入

在 Spring Data JPA 中,如何在运行时添加实体?

如何在 Spring Boot 应用程序的同一个域类上同时使用 Spring Data JPA 和 Spring Data Elasticsearch 存储库?

使用 Spring Data Jpa 在 Oracle 中调用存储过程时参数的数量或类型错误

为什么Spring-Data-JPA Async无法正常工作?