Spring,Hibernate,Blob懒加载

32
我需要在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的null值。在这种情况下,应用程序可以正常工作,内存不会耗尽。我得出结论,标记为lazy(@Basic(fetch=FetchType.LAZY))的blob字段实际上并不是lazy!


请参见 https://hibernate.onjira.com/browse/HHH-5255 - Vadzim
8个回答

39

我有些困惑。Emmanuel Bernard在ANN-418中写道,@Lob默认是lazy的(即你甚至不需要使用@Basic(fetch = FetchType.LAZY)注释)。

一些用户报告说,@Lob的lazy加载对于所有驱动程序/数据库都不起作用

有些用户报告说,在使用字节码注入(javassit?cglib?)时,它可以工作。

但我在文档中找不到任何明确的参考资料。

最后,推荐的解决方法是使用“虚假”的一对一映射而不是属性。从现有类中删除LOB字段,创建引用相同表格、相同主键和仅包含必要LOB字段的新类作为属性。指定映射为one-to-one,fetch="select",lazy="true"。只要您的父对象仍然在会话中,您应该得到正好所需的结果。(将此转置为注释即可)。


3
父类: @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY) private FileBlobBean fileBlobBean;子类: @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "Uid", nullable = false) private Long uid; @Lob @Basic(fetch = FetchType.LAZY) @Column(name = "BlobField", columnDefinition = "LONGBLOB") private byte[] blobField;@OneToOne(mappedBy = "fileBlobBean") @JoinColumn(name = "Uid", referencedColumnName = "Uid", nullable = false) public FileBean fileBean; - Alexey Khudyakov
6
在我的情况下,这有所帮助: 父类: @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY) @PrimaryKeyJoinColumn private FileBlobBean...子类: 仅有id,无对父类的引用。 - broundee
你能展示如何实现Blob的一对一关系吗? - emeraldhieu
如何注释子(二进制)实体?我尝试过,但它自动增加了一行,其中只包含二进制数据! - emeraldhieu
可以在此处找到使用Maven的“字节码检测”示例参考:https://dev59.com/F3M_5IYBdhLWcg3w-4hg - Paweł Dulęba
显示剩余3条评论

6
Lazy属性加载需要在构建时进行字节码注入。 Hibernate文档:使用延迟属性获取 如果您想避免字节码注入,一个选择是创建两个实体,它们使用相同的表,一个带有blob,另一个没有。然后只在需要blob时使用带有blob的实体。

5
当然,您可以提取该值并将其放入具有"@OneToOne"关系的新表中,但是在我们的应用程序中,LOB是使用此配置按需惰性加载的。
@Lob
@Fetch(FetchMode.SELECT)
@Type(type="org.hibernate.type.PrimitiveByteArrayBlobType")
byte[] myBlob;

这在我们的项目中同时在PostgreSQL、MySQL、SQLServer和Oracle上进行了测试,因此应该可以适用于您。


1
我也遇到了同样的问题。使用的是Hibernate 4.3.1和Oracle 10g XE。你可能已经开启了字节码注入吗? - Ben George
PrimitiveByteArrayBlobType已被弃用,替换为MaterializedBlobType,并且在我的环境中也无法工作(hibernate 5.0.7.Final,postgresql)。 - jack jin

5
我建议您使用继承来处理这种情况。创建一个没有“blob”的基类和一个包含字节数组的派生类。只有在需要在用户界面上显示“blob”时,才使用派生类。

谢谢你,Darin。但我想知道为什么这个“懒惰”字段不像我想象的那样懒惰 :) 当然,如果我找不到另一种解决问题的方法,我会使用你的建议。 - Alexey Khudyakov

4

我遇到了同样的问题,这是我的解决方案:

我的实体(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;

...

在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>

pom.xml 的条目具体是做什么的? - Klesun

2

如果我使用 Blob 类型而不是 byte[],那么惰性加载对我有用。

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

这个字段是懒加载的,如果需要检索其值,请访问此字段:
String value = IOUtils.toByteArray(entity.getBlobField().getBinaryStream());

我的理解是,阅读文档(例如,对于Hibernate 5.5),FetchType.LAZY仅仅是一个提示,而不是确保实例属性将被延迟加载。如果您想要保证延迟加载,您需要在构建时配置字节码增强。 - Kode Charlie

2

对我来说,只有通过编译并运行才能使“懒加载”起作用,例如在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

完整工作示例


0
一个简单的解决方案是使用@OneTone符号,基于@MohammadReza Alagheband的回答(为什么@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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接