【问题标题】:Spring, Hibernate, Blob lazy loadingSpring,Hibernate,Blob 延迟加载
【发布时间】:2010-04-09 06:26:38
【问题描述】:

我需要有关 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 空值。在这种情况下,应用程序工作正常,内存不会用完。我有一个结论,标记为惰性 (@Basic(fetch=FetchType.LAZY)) 的 blob 字段实际上并不惰性!

【问题讨论】:

标签: java mysql hibernate lazy-loading blob


【解决方案1】:

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

一些用户报告 @Lob doesn't work with all drivers/database 的延迟加载。

一些用户报告说它在使用bytecode instrumentation(javassit?cglib?)时有效。

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

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

【讨论】:

  • 谢谢你,帕斯卡。我不明白如何指定与父类一对一的映射。你介意帮我解决吗?再次感谢您。
  • 父类@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;
  • Hibernate 在“文件”表中创建了一个新字段。并且该领域是空的。我究竟做错了什么?附:对于糟糕的代码格式,我深表歉意。
  • 在我的情况下,这有帮助:父:@OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY) @PrimaryKeyJoinColumn private FileBlobBean... 子:只是 id,不引用父
  • 你能展示如何实现blob的OneToOne关系吗?
【解决方案2】:

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

【讨论】:

  • 谢谢你,达林。但我想知道为什么惰性字段不像我想要的那样惰性:) 但是,当然,如果我找不到解决问题的另一种方法,我会使用你的建议。
【解决方案3】:

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

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

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

【讨论】:

  • 对我也不起作用。休眠 4.3.1。甲骨文 10g XE。您是否已经实现了字节码检测?
  • PrimitiveByteArrayBlobType 已弃用,替换为 MaterializedBlobType,并且对我也不起作用(hibernate 5.0.7.Final,postgresql)
【解决方案4】:

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

Hibernate docs: Using lazy property fetching

如果您想避免字节码检测,一种选择是创建两个使用同一个表的实体,一个带有 blob,一个没有。然后只在需要 blob 时使用带有 blob 的实体。

【讨论】:

    【解决方案5】:

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

    我的实体:

    @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 entry具体是做什么的?
    【解决方案6】:

    对我来说,延迟加载只能通过编译然后运行它来工作,例如在 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
    

    Full working example

    【讨论】:

      【解决方案7】:

      如果我使用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 只是一个提示,但不能保证实例属性将被延迟加载。如果你想要保证,你需要在构建时在字节码增强中进行配置。
      【解决方案8】:

      基于@MohammadReza Alagheband (Why does @Basic(fetch=lazy) doesn't work in my case?) 的响应使用@OneTone 表示法但不需要为每个必需的惰性属性创建新表的简单解决方法如下:

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

      【讨论】: