使用@Lob和@Query时投影问题

时间:2019-01-28 21:25:19

标签: spring-data-jpa projection

实体:

@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)

当我从“ @Lob”中删除“ description”注释时,投影工作正常。 似乎问题是CLOB从数据库返回的内容。当我将投影界面方法更改为“ java.sql.Clob getDesc();”时,似乎又可以开始工作了,但不是最佳解决方案。

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

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

2 个答案:

答案 0 :(得分:0)

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

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

  1. 在数据库中将LOB转换为VARCHAR2或类似内容。 如何完成此操作取决于您的数据库。 This answer seems to work for SQL Server。 我确定您会为正在使用的任何数据库找到替代方法。

  2. 通过使用JPQL查询在游戏中重新获得JPA。 那应该是独立于数据库的,但是我认为您有一个使用本机查询的理由。

答案 1 :(得分:0)

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

这很容易添加到您的现有项目中。我不确定您是否正在使用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> {
}

这就是在/itemsContent上创建REST端点所需的全部。当您的应用程序启动时,Spring Content将查看您的依赖项(请参阅Spring Content JPA / REST),查看您的ItemContentStore接口,并为JPA注入该接口的实现。它还将注入一个@Controller来将http请求转发到该实现。这省去了您自己实现的任何麻烦,而我想这就是您的追求。

所以...

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

或通过REST API访问内容:

curl -X POST /itemsContent/{itemId}

带有多部分/表单数据请求的

会将图像存储在数据库中,并将其与ID为itemId的帐户实体相关联。

curl /itemsContent/{itemId}

将再次获取它,依此类推...支持完整的CRUD。

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

HTH