使用 Hibernate 更新 HSQLDB 上的 LOB/BLOB 值会产生数据异常

Posted

技术标签:

【中文标题】使用 Hibernate 更新 HSQLDB 上的 LOB/BLOB 值会产生数据异常【英文标题】:Updating a LOB/BLOB value on a HSQLDB with Hibernate produces a data exception 【发布时间】:2018-05-15 12:05:03 【问题描述】:

我的更新代码有一个奇怪的行为:

实体:

@Entity
public class Data 

   @Id
   @GeneratedValue
   private long id;

   @Lob
   private byte[] data;

   //Getters and Setters...

Spring 数据存储库:

@Repository
public interface DataRepository extends JpaRepository<Data, Long> 

测试配置

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages="de.ehscheidt.bugs.data")
public class TestConfiguration 
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() 
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setPersistenceUnitName("data");
        em.setDataSource(dataSource());
        em.setPackagesToScan(new String[] "de.ehscheidt.bugs.data");

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());

        return em;
    

    @Bean
    public DataSource dataSource()
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder
                .setType(EmbeddedDatabaseType.HSQL)
                .setName("database")
                .build();
    

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf)
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);

        return transactionManager;
    

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation()
        return new PersistenceExceptionTranslationPostProcessor();
    

    private Properties additionalProperties() 
        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "update");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        properties.setProperty("hibernate.show_sql", "true");
        return properties;
    

测试:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes=TestConfiguration.class)
@Transactional
@SpringBootTest
public class DataTest 

    @Autowired
    private DataRepository repository;

    @Test
    public void fail() 

        Data d = new Data();
        d.setData(new byte[255]);

        d = repository.save(d);
        repository.flush();

        d.setData(new byte[256]);
        repository.save(d);
        repository.flush();
    

    @Test
    public void fail2() 

      Data d = new Data();
      d.setData(new byte[1025]);

      d = repository.save(d);
      repository.flush();

      d.setData(new byte[1026]);
      repository.save(d);
      repository.flush(); // <- fails here
    

    @Test
    public void ok() 

        Data d = new Data();
        d.setData(new byte[254]);

        d = repository.save(d);
        repository.flush();

        d.setData(new byte[255]);
        repository.save(d);
        repository.flush();
    

fail方法是抛出:

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:263)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy45.flush(Unknown Source)
    at de.ehscheidt.bugs.data.DataTest.fail(DataTest.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    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:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    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.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: org.hibernate.exception.DataException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:52)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3217)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3090)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3491)
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:145)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:600)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:474)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1437)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:304)
    at com.sun.proxy.$Proxy42.flush(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:534)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:377)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:629)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:593)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:578)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    ... 41 more
Caused by: java.sql.SQLDataException: data exception: string data, right truncation
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCPreparedStatement.fetchResult(Unknown Source)
    at org.hsqldb.jdbc.JDBCPreparedStatement.executeUpdate(Unknown Source)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
    ... 75 more
Caused by: org.hsqldb.HsqlException: data exception: string data, right truncation
    at org.hsqldb.error.Error.error(Unknown Source)
    at org.hsqldb.error.Error.error(Unknown Source)
    at org.hsqldb.types.BlobType.convertToType(Unknown Source)
    at org.hsqldb.StatementDML.getUpdatedData(Unknown Source)
    at org.hsqldb.StatementDML.executeUpdateStatement(Unknown Source)
    at org.hsqldb.StatementDML.getResult(Unknown Source)
    at org.hsqldb.StatementDMQL.execute(Unknown Source)
    at org.hsqldb.Session.executeCompiledStatement(Unknown Source)
    at org.hsqldb.Session.execute(Unknown Source)
    ... 78 more

重要提示:在第二次刷新时抛出异常,导致更新。

使用的库:

spring-boot-starter-data-jpa: 2.0.2.RELEASE 休眠核心:5.2.17.Final hsqldb: 2.4.0

我猜它与 hibernate 或/和 hsqldb 有关系。有趣的是,hibernate 将表生成为 'data blob(255)'

那么,超过 255 字节的 INSERT 是可以的,但超过 255 字节的 UPDATE 就不行了?

我错过了什么?

【问题讨论】:

正如保罗所说,测试没有显示超过 255 个字节的插入。现在我添加了第二个失败的测试,显示插入 1025 个字节。稍后更新 1026 字节将失败。 【参考方案1】:

我认为这与使用 byte[] 作为数据类型有关。似乎一旦您创建了一条包含 255 个(或 1025 个或实际上任何长度)字符的记录,您就可以使用这些字符了。我的猜测是 HSQLDB 将 byte[] LOB 视为原始类型并将它们内联存储在可变长度记录中。

如果您改用 Java 流,则 HSQLDB 会将其视为适当的可变长度 LOB。 Spring Content 在幕后使用流,因此您不会遇到此限制。

修改您的应用以使用它非常容易:

将 Spring Content 依赖项添加到您的 pom。

pom.xml

   <dependency
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-jpa-boot-starter</artifactId>
      <version>0.0.11</version>
   </dependency>
   <!-- and if you want the REST endpoints too -->
   <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-rest-boot-starter</artifactId>
      <version>0.0.11</version>
   </dependency>

更新您的实体,以便您可以关联内容。

数据.java

@Entity
public class Data 

   @Id
   @GeneratedValue
   private long id;

   @ContentId
   private String contentId;

   @ContentLength
   private long contentLength = 0L;

   // if you have rest endpoints
   @MimeType
   private String mimeType = "text/plain";

   ...

定义一个 ContentStore 接口:

DataContentStore.java

@StoreRestResource(path="dataContent")
public interface DataContentStore extends ContentStore<Data, String> 

就是这样。在运行时 Spring Content 将为您管理数据库模式。它将注入DataContentStore 的JPA 实现,该实现使用java 流而不是byte[]。如果您添加了其余依赖项,它还将在“/dataContent”添加一个提供 REST 端点的控制器。

除了能够随着时间的推移插入更大的 LOB 之外,使用 Spring Content 的其他优点是:

您不必自己编写任何代码来执行此操作 非常大的文件处理(因为它使用流,而不是字节数组) 支持视频流 可以进行全文搜索(Spring Content Solr) 其他格式的再现也是可能的 最后,您可以轻松地将您的应用程序切换到不同的 只需更改一项依赖项的存储

有一个入门指南几乎准确地描述了您正在尝试做什么here 和here。 Spring Content JPA 的参考指南是here。

HTH

【讨论】:

嗨,Paul,感谢您对 Spring Content 的提示。但我在我的用例中看不到它。在这里使用 SpringData 只是为了方便设置。在我的真实项目中,我只在 HSQLDB 之上使用 Hibernate 和 JPA (EntityManager)。 您可能应该将此问题的标题更改为“HSQLDB 在插入时修复 LOB 字段的长度”之类的内容,因为这将使其他人将来更容易找到此答案。 如果你不使用 Spring Content REST 那么就没有对 Spring Data 的依赖。它可以单独使用。

以上是关于使用 Hibernate 更新 HSQLDB 上的 LOB/BLOB 值会产生数据异常的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate更新HSQLDB数据库的问题

Hibernate Update 在 HSQLDB 中转换删除/插入

Hibernate HSQLDB 两个会话

如何为 Hibernate 设置 HSQLDB?

使用本机 SQL 创建表适用于 Hibernate/HSQLDB,插入失败。为啥?

使用 HSQLDB 作为 Spring、Hibernate 的可移植数据库