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

发布于 2024-09-07 13:42:11 字数 2442 浏览 11 评论 0原文

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

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

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

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

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

谢谢。

I have a Spring application which uses Hibernate on a PostgreSQL database. I'm trying to store files in a table of the database. It seems it stores the row with the file (I just use persist method on EntityManager), but when the object is loaded from the database I get the following exception:

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

To load the data I'm using a MultipartFile transient atribute and in its setter I'm setting the information I want to persist (byte[], fileName, size).
The entity I'm persisting looks like this one (I've ommitted the rest of getters/setters):

@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());
        }
    }
}

I can see that when it is persisted I have the data in the row but when I call this method it fails:

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();
}

This method only fails when the result has objects with files. I've tried to set in my persistence.xml

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

but it doesn't solve the problem.

In general the application works well it only commit the data when the transaction is finished and it performs a rollback if something fails, so I don't understand why is this happening.

Any idea?

Thanks.

UPDATE

Looking at the link given by Shekhar it is suggested to include the call in a transation, so I've set the service call inside a transaction an it works (I've added @Transactional annotation).

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

the problem is that I don't want to persist any data so I don't understand why it should be include inside a transaction. Does it make any sense to make a commit when I've only loaded some data form the database?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

花辞树 2024-09-14 13:42:11

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

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

A large object can be stored in several records, that's why you have to use a transaction. All records are correct or nothing at all.

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

得不到的就毁灭 2024-09-14 13:42:11

我没有使用@Transactional,而是将 PostgreSQL DB 中的这一列从 oid 类型更新为 bytea 类型,并添加了 < @Lob 字段的 code>@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;

Instead of using @Transactional, all I did for this issue was to update this column in PostgreSQL DB from oid type to bytea type and added @Type for the @Lob field.

i.e.

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

And changed

@Lob
private byte[] image;

To

@Lob
@Type(type = "org.hibernate.type.ImageType")
private byte[] image;
烧了回忆取暖 2024-09-14 13:42:11

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

Use @Transactional on your Repository Interface.

指尖凝香 2024-09-14 13:42:11

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

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

That's an old question but I will share what I've found for those who get here by a google search as I did.

This comment in an issue in JHipster project has a better explanation of the problem and also describe more options to solve it.

I will sumarize it here:

Spring boot app, "out of the box", uses HikariCP connection pool and with autocommit=true by default. As it seems, the PostgreSQL jdbc driver requires that the handling of anything that uses a Lob be done with autocommit=false or inside a proper transaction.

The solutions (and explanations) proposed by Julien Dubois in the link above are very reasonable:

  • Mark the repository @transactional : I don't like it very much, because that gives more work to the service layer (which has to join the transaction, etc). So there's a little performance hit, for nothing much.

  • Mark the REST endpoint with @transactional(readOnly=true) : I don't like having transactions in the view layer. There's one added benefit from the point above: you can mark your transaction as readonly, so you have a performance gain.

  • Configure the connection pool to have autocommit = false. In fact, I thought we were already doing that, as the (older) connections pools I used were doing this. But that's not the case with HikariCP!!

That said, in my opinion, to turn off the autocommit in the connection pool seems as the better approach. It is more secure, it allows for one to work with read operations without the cost of a transaction and it is a global solution.

Just place this on the spring application.properties of your project:

spring.datasource.hikari.auto-commit=false
一江春梦 2024-09-14 13:42:11

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

bytea 基本上与其他数据库(例如 Oracle)中的 BLOB 相同,并且它的处理与 JDBC 更加兼容。

Unless you need to store files large than 1GB I suggest you use bytea as the datatype instead of large object.

bytea is basically what BLOB is in other databases (e.g. Oracle) and it's handling is a lot more compatible with JDBC.

冰魂雪魄 2024-09-14 13:42:11

如果可以的话,例如在 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。这不起作用。你必须有一个中级课程。

If you can, create a intermediate Entity between MyClass and file property for instance.
Something like:

@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;
}

You can't use @Lob and fetch type Lazy. It doesnt work. You must have a a intermediate class.

神也荒唐 2024-09-14 13:42:11

编译器无法将“type”理解为参数

@Type(type = "org.hibernate.type.ImageType")

The compiler does not understand "type" as a parameter

@Type(type = "org.hibernate.type.ImageType")
蛮可爱 2024-09-14 13:42:11

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

在我的例子中

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

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

就我而言,问题出在 JBoss 配置中。我正在使用 JTA 数据源,这导致了问题。使用 XA 数据源,它工作得很好。有关更多详细信息,请参阅这篇文章

You should check if auto-commit is really set false with method getAutoCommit() on your Connection.

In my case

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

did not work, because the type of my db connection required auto-commit to be true.

In my case the problem was in the JBoss Configuration. I was using an JTA-datasource and that caused the problem. With an XA-datasource it worked fine. See this post for more details.

兔小萌 2024-09-14 13:42:11

你可以试试他的:

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

You can try his :

@Column(name = "LONG_TEXT", columnDefinition="TEXT")
private String longText;
瑾兮 2024-09-14 13:42:11

我从实体中删除了@Lob,它正在工作。

//@Lob
private String description;

I removed @Lob from entity and it's working.

//@Lob
private String description;
木緿 2024-09-14 13:42:11

我正在为我解决的实体字段上使用 postgres TEXT 类型和 @Type(PostgreSQLCITextType.class)

实体:

@Lob
@Column(name = "description", columnDefinition="TEXT",  nullable = true)
@Type(PostgreSQLCITextType.class)
private String description;

I'm using postgres TEXT type, and @Type(PostgreSQLCITextType.class) on the entity field solved for me.

Entity:

@Lob
@Column(name = "description", columnDefinition="TEXT",  nullable = true)
@Type(PostgreSQLCITextType.class)
private String description;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文