Spring、Hibernate、Blob 延迟加载

发布于 2024-08-28 05:12:54 字数 3237 浏览 8 评论 0原文

我需要有关 Hibernate 中延迟 blob 加载的帮助。 我的 Web 应用程序中有这些服务器和框架:MySQL、Tomcat、Spring 和 Hibernate。

数据库配置部分。

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="driverClass" value="${jdbc.driverClassName}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>

    <property name="initialPoolSize">
        <value>${jdbc.initialPoolSize}</value>
    </property>
    <property name="minPoolSize">
        <value>${jdbc.minPoolSize}</value>
    </property>
    <property name="maxPoolSize">
        <value>${jdbc.maxPoolSize}</value>
    </property>
    <property name="acquireRetryAttempts">
        <value>${jdbc.acquireRetryAttempts}</value>
    </property>
    <property name="acquireIncrement">
        <value>${jdbc.acquireIncrement}</value>
    </property>
    <property name="idleConnectionTestPeriod">
        <value>${jdbc.idleConnectionTestPeriod}</value>
    </property>
    <property name="maxIdleTime">
        <value>${jdbc.maxIdleTime}</value>
    </property>
    <property name="maxConnectionAge">
        <value>${jdbc.maxConnectionAge}</value>
    </property>
    <property name="preferredTestQuery">
        <value>${jdbc.preferredTestQuery}</value>
    </property>
    <property name="testConnectionOnCheckin">
        <value>${jdbc.testConnectionOnCheckin}</value>
    </property>
</bean>


<bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" />

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
        </props>
    </property>
    <property name="lobHandler" ref="lobHandler" />
</bean>

<tx:annotation-driven transaction-manager="txManager" />

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

实体类部分

@Lob
@Basic(fetch=FetchType.LAZY)
@Column(name = "BlobField", columnDefinition = "LONGBLOB")
@Type(type = "org.springframework.orm.hibernate3.support.BlobByteArrayType")
private byte[] blobField;

问题描述。我试图在网页上显示与文件相关的数据库记录,这些记录保存在 MySQL 数据库中。如果数据量很小,一切都可以正常工作。但数据量很大,我收到错误 java.lang.OutOfMemoryError: Java heap space 我尝试在表的每一行上的 blobFields 中写入空值。在这种情况下,应用程序工作正常,内存不会耗尽。我得出的结论是,标记为惰性的 blob 字段 (@Basic(fetch=FetchType.LAZY)) 实际上并不惰性!

I need help with lazy blob loading in Hibernate.
I have in my web application these servers and frameworks: MySQL, Tomcat, Spring and Hibernate.

The part of database config.

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="driverClass" value="${jdbc.driverClassName}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>

    <property name="initialPoolSize">
        <value>${jdbc.initialPoolSize}</value>
    </property>
    <property name="minPoolSize">
        <value>${jdbc.minPoolSize}</value>
    </property>
    <property name="maxPoolSize">
        <value>${jdbc.maxPoolSize}</value>
    </property>
    <property name="acquireRetryAttempts">
        <value>${jdbc.acquireRetryAttempts}</value>
    </property>
    <property name="acquireIncrement">
        <value>${jdbc.acquireIncrement}</value>
    </property>
    <property name="idleConnectionTestPeriod">
        <value>${jdbc.idleConnectionTestPeriod}</value>
    </property>
    <property name="maxIdleTime">
        <value>${jdbc.maxIdleTime}</value>
    </property>
    <property name="maxConnectionAge">
        <value>${jdbc.maxConnectionAge}</value>
    </property>
    <property name="preferredTestQuery">
        <value>${jdbc.preferredTestQuery}</value>
    </property>
    <property name="testConnectionOnCheckin">
        <value>${jdbc.testConnectionOnCheckin}</value>
    </property>
</bean>


<bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" />

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
        </props>
    </property>
    <property name="lobHandler" ref="lobHandler" />
</bean>

<tx:annotation-driven transaction-manager="txManager" />

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

The part of entity class

@Lob
@Basic(fetch=FetchType.LAZY)
@Column(name = "BlobField", columnDefinition = "LONGBLOB")
@Type(type = "org.springframework.orm.hibernate3.support.BlobByteArrayType")
private byte[] blobField;

The problem description. I'm trying to display on a web page database records related to files, which was saved in MySQL database. All works fine if a volume of data is small. But the volume of data is big I'm recieving an error java.lang.OutOfMemoryError: Java heap space
I've tried to write in blobFields null values on each row of table. In this case, application works fine, memory doesn't go out of. I have a conclusion that the blob field which is marked as lazy (@Basic(fetch=FetchType.LAZY)) isn't lazy, actually!

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

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

发布评论

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

评论(8

远山浅 2024-09-04 05:12:54

我很困惑。 Emmanuel Bernard 在 ANN-418 中写道,@Lob 很懒惰默认情况下(即您甚至不需要使用 @Basic(fetch = FetchType.LAZY) 注释)。

一些用户报告 @Lob 的延迟加载不起作用与所有驱动程序/数据库

一些用户报告说,它在使用字节码工具(javassit?cglib?)时有效。

但我在文档中找不到所有这些的明确参考。

最后,推荐的解决方法是使用a "假的”一对一映射而不是属性。从现有类中删除 LOB 字段,创建引用相同表、相同主键且仅将必要的 LOB 字段作为属性的新类。将映射指定为一对一、fetch="select"、lazy="true"。只要你的父对象仍在你的会话中,你就应该得到你想要的。(只需将其转置为注释)。

I'm confused. Emmanuel Bernard wrote in ANN-418 that @Lob are lazy by default (i.e. you don't even need to use the @Basic(fetch = FetchType.LAZY) annotation).

Some users report that lazy loading of a @Lob doesn't work with all drivers/database.

Some users report that it works when using bytecode instrumentation (javassit? cglib?).

But I can't find any clear reference of all this in the documentation.

At the end, the recommended workaround is to use a "fake" one-to-one mappings instead of properties. Remove the LOB fields from your existing class, create new classes referring to the same table, same primary key, and only the necessary LOB fields as properties. Specify the mappings as one-to-one, fetch="select", lazy="true". So long as your parent object is still in your session, you should get exactly what you want. (just transpose this to annotations).

北城挽邺 2024-09-04 05:12:54

延迟属性加载需要构建时字节码检测。

Hibernate 文档:使用惰性属性获取

如果您想避免字节码检测,一种选择是创建两个使用同一表的实体,一个包含 blob,一个不包含 blob。然后仅在需要 blob 时才将实体与 blob 一起使用。

Lazy property loading requires buildtime bytecode instrumentation.

Hibernate docs: Using lazy property fetching

If you want to avoid bytecode instrumentation one option is to to create two entities that use same table, one with the blob one without. Then only use the entity with blob when you need the blob.

他不在意 2024-09-04 05:12:54

我建议您使用继承来处理这种情况。有一个不带 blob 的基类和一个包含字节数组的派生类。仅当需要在 UI 上显示 blob 时,才使用派生类。

I would suggest you to use inheritance to handle this scenario. Have a base class without the blob and a derived class containing the byte array. You would use the derived class only when you need to display the blob on the UI.

风吹雪碎 2024-09-04 05:12:54

当然,您可以提取该值并将其放入具有惰性“@OneToOne”关系的新表中,但是在我们的应用程序中,仅使用此配置按需惰性加载 LOB

@Lob
@Fetch(FetchMode.SELECT)
@Type(type="org.hibernate.type.PrimitiveByteArrayBlobType")
byte[] myBlob;

这在我们的项目中同时在 PostgreSQL 上进行了测试, MySQL、SQLServer 和 Oracle,所以它应该适合你

Of course you could extract that value and put it into a new table with a "@OneToOne" relation that is lazy, however in our application the LOBs are loaded lazily on demand using just this configuration

@Lob
@Fetch(FetchMode.SELECT)
@Type(type="org.hibernate.type.PrimitiveByteArrayBlobType")
byte[] myBlob;

This is tested in our project simultaneously on PostgreSQL, MySQL, SQLServer and Oracle, so it should work for u

暮色兮凉城 2024-09-04 05:12:54

我遇到了同样的问题,这是我的修复:

我的实体:

@Entity
@Table(name = "file")
public class FileEntity {

@Id
@GeneratedValue
private UUID id;

@NotNull
private String filename;

@NotNull
@Lob @Basic(fetch = FetchType.LAZY)
private byte[] content;

...

向 pom.xml 添加了插件:

        <plugin>
            <groupId>org.hibernate.orm.tooling</groupId>
            <artifactId>hibernate-enhance-maven-plugin</artifactId>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <configuration>
                        <failOnError>true</failOnError>
                        <enableLazyInitialization>true</enableLazyInitialization>
                    </configuration>
                    <goals>
                        <goal>enhance</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

I had the same issue and this was my fix:

My Entity:

@Entity
@Table(name = "file")
public class FileEntity {

@Id
@GeneratedValue
private UUID id;

@NotNull
private String filename;

@NotNull
@Lob @Basic(fetch = FetchType.LAZY)
private byte[] content;

...

Added plugin to pom.xml:

        <plugin>
            <groupId>org.hibernate.orm.tooling</groupId>
            <artifactId>hibernate-enhance-maven-plugin</artifactId>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <configuration>
                        <failOnError>true</failOnError>
                        <enableLazyInitialization>true</enableLazyInitialization>
                    </configuration>
                    <goals>
                        <goal>enhance</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
享受孤独 2024-09-04 05:12:54

如果我使用 Blob 类型而不是 byte[] 类型,则延迟加载对我有用。

@Column(name = "BlobField", nullable = false)
@Lob
@Basic(fetch = FetchType.LAZY)
private Blob blobField;

这个是延迟加载的,如果您需要检索其值,请访问此字段:

String value = IOUtils.toByteArray(entity.getBlobField().getBinaryStream());

Lazy loading works for me if I use Blob type instead of byte[].

@Column(name = "BlobField", nullable = false)
@Lob
@Basic(fetch = FetchType.LAZY)
private Blob blobField;

This one gets lazily loaded and if you need to retrieve its value access this field:

String value = IOUtils.toByteArray(entity.getBlobField().getBinaryStream());
归途 2024-09-04 05:12:54

对我来说,延迟加载只能通过编译然后运行它来工作,例如在 eclipse 或 intellij 上不起作用。

我正在使用 gradle 然后我执行了以下操作以使其正常工作

  • 注释实体
  • 设置 Hibernate gradle 插件

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.hibernate:hibernate-gradle-plugin:5.4.0.Final"
    }
}

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'org.hibernate.orm'
hibernate {
    enhance {
        enableLazyInitialization = true
        enableDirtyTracking = true
        enableAssociationManagement = true
    }
}

Entity.java

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Integer id;

    @Lob
    @Basic(fetch = FetchType.LAZY)
    @Column(length = 255, nullable = false)
    private String name;

测试

./gradlew run

完整工作示例

For me lazy load only worked by compiling and then running it, didn't work on eclipse or intellij for example.

I'm using gradle then I did the following to get it working

  • Annotate entity
  • Setup Hibernate gradle plugin

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.hibernate:hibernate-gradle-plugin:5.4.0.Final"
    }
}

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'org.hibernate.orm'
hibernate {
    enhance {
        enableLazyInitialization = true
        enableDirtyTracking = true
        enableAssociationManagement = true
    }
}

Entity.java

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Integer id;

    @Lob
    @Basic(fetch = FetchType.LAZY)
    @Column(length = 255, nullable = false)
    private String name;

Testing

./gradlew run

Full working example

樱&纷飞 2024-09-04 05:12:54

基于 @MohammadReza Alagheband 的响应,使用 @OneTone 表示法的简单解决方法(为什么 @Basic(fetch=lazy) 在我的情况下不起作用?)但不需要为每个所需的惰性属性创建一个新表,如下所示:

@Getter
@Setter
@Entity
@Table(name = "document")
@AllArgsConstructor
@NoArgsConstructor
public class DocumentBody implements java.io.Serializable{
    @Column(name = "id", insertable = false)
    @ReadOnlyProperty
    @Id
    @PrimaryKeyJoinColumn
    private Integer id;

    @Column(name = "body", unique = true, nullable = false, length = 254)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private String content;
}

@Getter
@Entity
@Setter
@Table(name = "document")
@AllArgsConstructor
@NoArgsConstructor
public class DocumentTitle implements java.io.Serializable{
    @Column(name = "id", insertable = false)
    @ReadOnlyProperty
    @Id
    private Integer id;

    @Column(name = "title", unique = true, nullable = false, length = 254)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private String content;
}


public class Document implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private Integer id;

    //Also it is posssible to prove with @ManyToOne
    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private DocumentTitle title;

    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private DocumentBody body;
}

A simple workarround using @OneTone notation based on the response of @MohammadReza Alagheband (Why does @Basic(fetch=lazy) doesn't work in my case?) but without the requirement of create a new table for each required lazy attribute is the following:

@Getter
@Setter
@Entity
@Table(name = "document")
@AllArgsConstructor
@NoArgsConstructor
public class DocumentBody implements java.io.Serializable{
    @Column(name = "id", insertable = false)
    @ReadOnlyProperty
    @Id
    @PrimaryKeyJoinColumn
    private Integer id;

    @Column(name = "body", unique = true, nullable = false, length = 254)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private String content;
}

@Getter
@Entity
@Setter
@Table(name = "document")
@AllArgsConstructor
@NoArgsConstructor
public class DocumentTitle implements java.io.Serializable{
    @Column(name = "id", insertable = false)
    @ReadOnlyProperty
    @Id
    private Integer id;

    @Column(name = "title", unique = true, nullable = false, length = 254)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private String content;
}


public class Document implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private Integer id;

    //Also it is posssible to prove with @ManyToOne
    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private DocumentTitle title;

    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private DocumentBody body;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文