我遇到了一个问题,似乎很多人遇到了它,可能无法解决它.
我有以下MysqL存储过程.这只是一个示例,看看一切是否正常,然后我会开始添加非活动参数.
CREATE DEFINER=`root`@`localhost` PROCEDURE `get_resource_types`()
BEGIN
SELECT *
FROM resource_types
WHERE inactive = 0;
END
映射resource_types表和命名存储过程查询的实体.
@NamedStoredProcedureQuery(
name="getResourceTypes",procedureName="get_resource_types",resultClasses = ResourceType.class,parameters = {}
)
@Entity
@Table(name = "resource_types")
public class ResourceType {
... fields with annotations used for validation + getters and setters ...
}
这是我的JpaRepository,我从中调用存储过程
@Repository
public interface ResourceTypeRepository extends JpaRepository
驻留在@Service中的getAll()方法
public List
当我尝试运行它时,我得到以下堆栈跟踪:
org.springframework.web.util.NestedServletException: Request processing Failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Type cannot be null; nested exception is java.lang.IllegalArgumentException: Type cannot be null
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:144)
at com.test.ihbs.controller.ResourceTypeControllerTest.test_getAll(ResourceTypeControllerTest.java:111)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:64)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:50)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:106)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)
at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Type cannot be null; nested exception is java.lang.IllegalArgumentException: Type cannot be null
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:381)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
at org.springframework.orm.jpa.AbstractEntityManagerfactorybean.translateExceptionIfPossible(AbstractEntityManagerfactorybean.java:417)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy87.getResourceTypes(Unknown Source)
at com.ihbs.service.ResourceTypeService.getAll(ResourceTypeService.java:34)
at com.ihbs.controller.ResourceTypeController.getAllResourceTypes(ResourceTypeController.java:44)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
... 58 more
Caused by: java.lang.IllegalArgumentException: Type cannot be null
at org.hibernate.procedure.internal.AbstractParameterRegistrationImpl.setHibernateType(AbstractParameterRegistrationImpl.java:182)
at org.hibernate.procedure.internal.AbstractParameterRegistrationImpl.
任何想法为什么会发生这种情况以及如何解决它?
InvalidDataAccessApiUsageException的文档说:
Exception thrown on incorrect usage of the API,such as failing to
“compile” a query object that needed compilation before execution.
This represents a problem in our Java data access framework,not the
underlying data access infrastructure.
更新1
如果我将存储库更改为以下代码,它将起作用,这意味着名称过程查询存在问题.我仍然需要使存储过程的方式工作.
@Repository
public interface ResourceTypeRepository extends JpaRepository
更新2
我没有使用EclipseLink(或者至少我不知道他的存在)
更新3
即使我通过代码调试,我也找不到这个问题的原因.但我找到了一些可能有助于找到问题的东西.
我在org / springframework / data / jpa / repository / query / StoredProcedureJpaQuery.java中找到了这段代码,如果您注意到在最后一行有一个函数调用,它会获取参数总数并添加一个额外的一个.这可能是问题,额外的参数.尽管如此,这可能不是问题,因为我认为它可以在不同情况下沿着另一条道路前进.
/**
* Extracts the output value from the given {@link StoredProcedureQuery}.
*
* @param storedProcedureQuery must not be {@literal null}.
* @return
*/
Object extractOutputValue(StoredProcedureQuery storedProcedureQuery) {
Assert.notNull(storedProcedureQuery,"StoredProcedureQuery must not be null!");
if (!procedureAttributes.hasReturnValue()) {
return null;
}
if (StringUtils.hasText(procedureAttributes.getOutputParameterName())) {
return storedProcedureQuery.getOutputParameterValue(procedureAttributes.getOutputParameterName());
}
return storedProcedureQuery.getOutputParameterValue(getQueryMethod().getParameters().getNumberOfParameters() + 1);
}
解决方案是降低API级别并使用JPA.这是我编写的与MS sql SP一起使用的泛型类.
import com.google.common.base.Strings;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.ParameterMode;
import javax.persistence.Query;
import javax.persistence.StoredProcedureQuery;
import lombok.requiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@requiredArgsConstructor(onConstructor = @__(@Autowired))
public class StoredProcRepository {
//region Injected beans (via a requiredArgsConstructor)
private final EntityManager em;
//endregion
/**
* Calls a stored procedure via JPA and retrieves a single implicit result set (in DBs that
* support them e.g. MS sql or MysqL). The call is not dependent on a DB dialect. Be
* aware that large result sets should be paginated and not entirely read to memory. Recreates
* StoredProcedureQuery instance and its parameters on each call.
* To execute MS sql SPs performing multiple queries,SET NOCOUNT ON.
*
* @param procedureName stored procedure name,optionally qualified per DB Syntax
* @param resultClass converts (maps) each result set row into instances of resultClass via JPA
* @param spArgs stored procedure arguments,supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param sql or MysqL).
* Assumes that result set has at least one row.
* The call is not dependent on a DB dialect.
* Be aware that large result sets should be paginated and not entirely read to memory.
* Recreates StoredProcedureQuery instance and its parameters on each call.
* To execute MS sql SPs performing multiple queries,supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param
对于MS sql SP,附加要求是对执行多个查询的所有SP具有SET NOCOUNT ON.这可以通过以下三种方式之一设置:
>在使用JPA的通用Java包装器中(参见下面的代码).此方法仅适用于jTDS JDBC驱动程序. A corresponding issue已经提交了MS JDBC驱动程序项目.
>在每个SP的开头.
> Globally in your database.
她是#1的代码:相同StoredProcRepository类的相应方法.
/**
* Calls an MS sql stored procedure via JPA and retrieves a single implicit result set.
* Protects against lack of SET NOCOUNT in stored procedures.
* This works with jTDS JDBC driver,but not with MS JDBC driver.
* Be aware that large result sets should be paginated and not entirely read to memory.
*
* @param procedureName stored procedure name,supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param sqlStoredProc(String procedureName,Object... spArgs) {
String spBindParams = (spArgs.length == 0) ? "" : "?" + Strings.repeat(",?",spArgs.length - 1);
// The following works with jTDS driver,but not with MS driver
String spQuery = String.format("EXEC %s %s",procedureName,spBindParams);
// The following works with jTDS driver,but not with MS driver
/*
String spQuery = String.format("{call %s(%s)}",spBindParams);
Query q = em.createNativeQuery("SET NOCOUNT ON; " + spQuery,resultClass)
.setHint("org.hibernate.readOnly",true);
*/
Query q = em.createNativeQuery(spQuery,resultClass);
int pos = 0;
for (Object arg : spArgs) {
q.setParameter(++pos,arg);
}
return q.getResultList();
}
/**
* Calls an MS sql stored procedure via JPA and retrieves only the top row of a single implicit
* result set.
* Assumes that result set has at least one row.
* The call sets the "NOCOUNT ON" MS sql batch option.
* Be aware that large result sets should be paginated and not entirely read to memory.
*
* @param procedureName stored procedure name,supplied positionally (optional SP arguments at the
* end of the list could be omitted)
* @param sqlStoredProc(String procedureName,Object... spArgs) {
return queryViaMssqlStoredProc(procedureName,spArgs).get(0);
}