【问题标题】:Issue with projection when using @Lob and @Query使用@Lob 和@Query 时的投影问题
【发布时间】:2019-01-28 21:25:18
【问题描述】:

实体:

@Entity
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    
    private Integer price;

    @Lob
    private String description;
}

投影接口:

public interface NameAndDesc {

    String getAlias();
    String getDesc();
}

存储库:

public interface ItemRepository extends JpaRepository<Item, Long> {

    @Query(value = "SELECT NAME AS ALIAS, DESCRIPTION AS DESC FROM ITEM WHERE ID IS :#{#id}",nativeQuery = true)
    NameAndDesc findNameAndDesc(@Param("id") Long id);
}

当我尝试在上面的查询中调用 .getDesc() 时,我得到了这个异常:

java.lang.IllegalArgumentException: Projection type must be an interface!

at org.springframework.util.Assert.isTrue(Assert.java:118)
at org.springframework.data.projection.ProxyProjectionFactory.createProjection(ProxyProjectionFactory.java:100)
at org.springframework.data.projection.SpelAwareProxyProjectionFactory.createProjection(SpelAwareProxyProjectionFactory.java:45)
at org.springframework.data.projection.ProjectingMethodInterceptor.getProjection(ProjectingMethodInterceptor.java:131)
at org.springframework.data.projection.ProjectingMethodInterceptor.invoke(ProjectingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.ProxyProjectionFactory$TargetAwareMethodInterceptor.invoke(ProxyProjectionFactory.java:245)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy105.getDesc(Unknown Source)
at com.example.demo.DemoApplicationTests.contextLoads(DemoApplicationTests.java:18)
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:498)
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.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
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:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

当我从“description”中删除“@Lob”注释时,投影工作没有任何问题。 似乎问题出在从数据库返回的 CLOB 上。当我将投影接口方法更改为 clob "java.sql.Clob getDesc();" 时,它似乎又开始工作了,但不是最好的解决方案。

像这样使用投影时行为是否正确?

当它是ProxyProjectionFactory 中的一个错误时,我发现了一个类似的问题: Issue with projection in SpringDataRest and @Lob attribute

【问题讨论】:

  • 您能添加完整的堆栈跟踪吗?请将其格式化为代码,以便保留格式。
  • 我更新了我的问题。我只收到这一行警告信息。我不知道如何才能看到有关此消息的更多信息。
  • 这似乎是因为您将其作为 Web 应用程序的一部分运行,而 DefaultHandlerExecptionResolver 只记录了一个警告。直接在测试中执行该方法,您将获得一个完整的堆栈跟踪。
  • 谢谢!我更新了问题。是的,现在我可以看到完整的堆栈跟踪。

标签: spring-data-jpa projection


【解决方案1】:

投影背后的想法是限制从数据库返回和(理想情况下请求)的列。 内置的转换支持不多,因为这通常由 JPA 处理,但这不会发生,因为您使用的是本机查询。

因此,我看到了两种解决问题的方法:

  1. 将 LOB 转换为 VARCHAR2 或数据库中的类似值。 如何完成取决于您的数据库。 This answer seems to work for SQL Server。 我相信你会为你使用的任何数据库找到一个替代方案。

  2. 使用 JPQL 查询让 JPA 重新回到游戏中。 这应该是独立于数据库的,但我认为您首先有理由使用本机查询。

【讨论】:

    【解决方案2】:

    解决此问题的一种方法是使用Spring Content 社区项目。该项目允许您将内容与 Spring Data 实体相关联。内容是单独管理的,仅在实体上留下“受管理的”内容相关元数据。这不会弄乱你的预测。想想 Spring Data,但对于内容(或非结构化数据)。

    这很容易添加到您现有的项目中。我不确定您是否正在使用 Spring Boot。我举一个非弹簧靴的例子:

    pom.xml

       <!-- Java API -->
       <dependency>
          <groupId>com.github.paulcwarren</groupId>
          <artifactId>spring-content-jpa</artifactId>
          <version>0.5.0</version>
       </dependency>
       <!-- REST API (if desired)-->
       <dependency>
          <groupId>com.github.paulcwarren</groupId>
          <artifactId>spring-content-rest</artifactId>
          <version>0.5.0</version>
       </dependency>
    

    配置

    @Configuration
    @EnableJpaStores
    @Import("org.springframework.content.rest.config.RestConfiguration.class")
    public class ContentConfig {
    
        // schema management
        // 
        @Value("/org/springframework/content/jpa/schema-drop-mysql.sql")
        private Resource dropContentTables;
    
        @Value("/org/springframework/content/jpa/schema-mysql.sql")
        private Resource createContentTables;
    
        @Bean
        DataSourceInitializer datasourceInitializer() {
            ResourceDatabasePopulator databasePopulator =
                    new ResourceDatabasePopulator();
    
            databasePopulator.addScript(dropContentTables);
            databasePopulator.addScript(createContentTables);
            databasePopulator.setIgnoreFailedDrops(true);
    
            DataSourceInitializer initializer = new DataSourceInitializer();
            initializer.setDataSource(dataSource());
            initializer.setDatabasePopulator(databasePopulator);
    
            return initializer;
        }
    }
    

    要关联内容,请将 Spring Content 注释添加到您的帐户实体。

    Item.java

    @Entity
    public class Item {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        private Integer price;
    
        // replace @Lob field with
        @ContentId
        private String contentId;
    
        @ContentLength
        private long contentLength = 0L;
    
        // if you have rest endpoints
        @MimeType
        private String mimeType = "text/plain";
    }
    

    创建一个“商店”:

    ItemContentStore.java

    @StoreRestResource(path="itemsContent)
    public interface ItemContentStore extends ContentStore<Item, String> {
    }
    

    这就是创建 REST 端点 @/itemsContent 所需的全部内容。当您的应用程序启动时,Spring Content 将查看您的依赖项(查看 Spring Content JPA/REST),查看您的 ItemContentStore 接口并为 JPA 注入该接口的实现。它还将注入一个@Controller,将http请求转发到该实现。这使您不必自己实施任何这些,我认为这就是您所追求的。

    所以...

    要通过 Java API 访问内容,请自动连接 ItemContentStore 并使用其方法。

    或通过 REST API 访问内容:

    curl -X POST /itemsContent/{itemId}

    使用 multipart/form-data 请求将图像存储在数据库中,并将其与 id 为 itemId 的帐户实体相关联。

    curl /itemsContent/{itemId}

    将再次获取它等等...支持完整的 CRUD。

    有一些入门指南here。参考指南是here。还有一个教程视频here。编码位从大约 1/2 处开始。

    HTH

    【讨论】:

      猜你喜欢
      • 2015-09-11
      • 2018-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-30
      • 2019-02-08
      • 2019-09-14
      • 2020-02-01
      相关资源
      最近更新 更多