大对象不能在自动提交模式下使用

Posted

技术标签:

【中文标题】大对象不能在自动提交模式下使用【英文标题】:Large Objects may not be used in auto-commit mode 【发布时间】:2011-03-10 23:52:55 【问题描述】:

我有一个在 PostgreSQL 数据库上使用 Hibernate 的 Spring 应用程序。我正在尝试将文件存储在数据库的表中。它似乎将行与文件一起存储(我只是在 EntityManager 上使用 persist 方法),但是当从数据库加载对象时,我得到以下异常:

org.postgresql.util.PSQLException: Large Objects may not be used in auto-commit mode.

为了加载数据,我使用了 MultipartFile 瞬态属性,并在其设置器中设置了我想要保留的信息(字节 []、文件名、大小)。 我坚持的实体看起来像这样(我省略了其余的 getter/setter):

@Entity
@Table(name="myobjects")
public class MyClass 

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sequence")
    @SequenceGenerator(name="sequence", sequenceName="myobjects_pk_seq", allocationSize=1)
    @Column(name="id")
    private Integer id;

    @Lob
    private String description;

    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDate;

    @Transient
    private MultipartFile multipartFile;

    @Lob
    @Basic(fetch=FetchType.LAZY, optional=true)
    byte[] file;

    String fileName;

    String fileContentType;

    Integer fileSize;

    public void setMultipartFile(MultipartFile multipartFile) 
        this.multipartFile = multipartFile;
        try 
            this.file = this.multipartFile.getBytes();
            this.fileName = this.multipartFile.getOriginalFilename();
            this.fileContentType = this.multipartFile.getContentType();
            this.fileSize = ((Long) this.multipartFile.getSize()).intValue();
         catch (IOException e) 
            logger.error(e.getStackTrace());
        
    

我可以看到,当它被持久化时,我在行中有数据,但是当我调用这个方法时它失败了:

public List<MyClass> findByDescription(String text) 
    Query query = getEntityManager().createQuery("from MyClass WHERE UPPER(description) like :query ORDER BY creationDate DESC");
    query.setParameter("query", "%" + text.toUpperCase() + "%");
    return query.getResultList();

此方法仅在结果包含带有文件的对象时才会失败。我试图在我的 persistence.xml 中设置

<property name="hibernate.connection.autocommit" value="false" />

但这并不能解决问题。

一般来说,应用程序运行良好,它只在事务完成时提交数据,如果出现故障,它会执行回滚,所以我不明白为什么会发生这种情况。

有什么想法吗?

谢谢。

更新

查看 Shekhar 提供的链接,建议将调用包含在事务中,因此我在事务中设置了服务调用,它可以正常工作(我添加了 @Transactional 注释)。

@Transactional
public List<myClass> find(String text) 
    return myClassDAO.findByDescription(text);

问题是我不想保留任何数据,所以我不明白为什么它应该包含在事务中。当我只从数据库中加载了一些数据时,提交是否有意义?

谢谢。

【问题讨论】:

如果您不想保留数据,为什么要映射files?不应该是@Transient吗? @matt b 我想保留文件,但只在保存操作中而不是在读取操作中。这就是为什么我不明白读取应该在事务中。 当你想保存对象时,不要只使用@Transactional。访问数据库通常需要事务 - 无论是读取还是写入。 出现此异常后,hibernate 无法连接数据库抛出:Caused by: org.hibernate.HibernateException: Unable to access lob stream,有没有什么数据库命令或者什么东西可以恢复这个数据库? 【参考方案1】:

一个大对象可以存储在多个记录中,这就是您必须使用事务的原因。所有记录都正确或根本没有。

https://www.postgresql.org/docs/current/static/largeobjects.html

【讨论】:

我不太确定这是对原始问题的回答吗?原始发布者不想写入没有事务的 LOB,他想在没有事务的情况下读取它们。 默认情况下 LOB 是惰性加载的,并且加载应该在加载父对象的同一事务中进行。 这是怎么回答的?【参考方案2】:

我没有使用@Transactional,而是将PostgreSQL DB 中的这一列从oid 类型更新为bytea 类型,并为@Lob 字段添加@Type

ALTER TABLE person DROP COLUMN image;
ALTER TABLE person ADD COLUMN image bytea;

又变了

@Lob
private byte[] image;

@Lob
@Type(type = "org.hibernate.type.ImageType")
private byte[] image;

【讨论】:

你是一个救生员! 添加@Type注解对我来说已经足够了,谢谢! 这应该是正确的答案!您需要做的就是定义 LOB 的休眠类型,然后 Lazy fetch 就可以正常工作了。我打算存储一个非常大的字符串并改用 org.hibernate.type.StringType 解决了这个问题。 这个解决方案在我的情况下返回了一个空结果。【参考方案3】:

如果可以,例如在 MyClass 和文件属性之间创建一个中间实体。 比如:

@Entity
@Table(name="myobjects")
public class MyClass 
    @OneToOne(cascade = ALL, fetch = LAZY)
    private File file;


@Entity
@Table(name="file")
public class File 
     @Lob
     byte[] file;

您不能使用 @Lob 并获取 Lazy 类型。它不起作用。你必须有一个中间类。

【讨论】:

如果您不需要在每个查询中检索 Lob,那么这是最好的方法。这会产生一些开销,因为在访问对象时需要调度额外的查询,但在某些情况下,这是理想的。【参考方案4】:

除非您需要存储大于 1GB 的文件,否则我建议您使用 bytea 作为数据类型而不是大对象。

bytea 基本上是其他数据库(例如 Oracle)中的 BLOB,它的处理方式与 JDBC 更兼容。

【讨论】:

【参考方案5】:

在您的存储库接口上使用@Transactional

【讨论】:

【参考方案6】:

这是一个老问题,但我将分享我为那些像我一样通过谷歌搜索到达这里的人发现的内容。

This comment 在 JHipster 项目的一个问题中对问题有更好的解释,并描述了更多的解决方案。

我会在这里总结一下:

Spring boot 应用程序,“开箱即用”,使用 HikariCP 连接池,默认情况下 autocommit=true。看起来,PostgreSQL jdbc 驱动程序要求处理使用 Lob 的任何事情都必须使用 autocommit=false 或在适当的事务中完成。

Julien Dubois 在上述链接中提出的解决方案(和解释)非常合理:

标记存储库@transactional:我不太喜欢它,因为这给服务层(必须加入事务等)提供了更多工作。所以对性能有一点影响,没什么大不了的。

用 @transactional(readOnly=true) 标记 REST 端点:我不喜欢在视图层中有事务。上述观点还有一个额外的好处:您可以将您的事务标记为只读,因此您可以获得性能提升。

将连接池配置为 autocommit = false。事实上,我认为我们已经这样做了,因为我使用的(较旧的)连接池正在这样做。但是HikariCP不是这样的!!

也就是说,在我看来,关闭连接池中的自动提交似乎是更好的方法。它更安全,它允许人们在没有事务成本的情况下进行读取操作,并且它是一种全局解决方案。

只需将其放在项目的 spring application.properties 上即可:

spring.datasource.hikari.auto-commit=false

【讨论】:

谢谢!添加spring.datasource.hikari.auto-commit=false 对我有用。【参考方案7】:

您应该使用您的 Connection 上的方法 getAutoCommit() 检查自动提交是否真的设置为 false。

就我而言

<property name="hibernate.connection.autocommit" value="false" />

没有用,因为我的数据库连接类型要求自动提交为真。

就我而言,问题出在 JBoss 配置中。我使用的是 JTA 数据源,这导致了问题。使用 XA 数据源,它工作得很好。详情请见this post。

【讨论】:

【参考方案8】:

你可以试试他的:

@Column(name = "LONG_TEXT", columnDefinition="TEXT")
private String longText;

【讨论】:

以上是关于大对象不能在自动提交模式下使用的主要内容,如果未能解决你的问题,请参考以下文章

仅在不处于自动提交模式时回滚和提交

设计模式-行为型

如何在 mariadb 中关闭自动提交?

数据库连接的自动提交操作模式的优点和缺点是啥?

jdbc 5.0

MySQL事务autocommit自动提交