Весна, Спящий, Blob ленивый loading

Мне нужна помощь с отложенной загрузкой BLOB-объектов в Hibernate. В моем веб-приложении есть такие серверы и платформы: 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)) на самом деле не ленивый!

8 ответов

Я не совсем понимаю. Эммануэль Бернард писал в ANN-418, что @Lob по умолчанию ленивы (т.е. вам даже не нужно использовать @Basic(fetch = FetchType.LAZY) аннотация).

Некоторые пользователи сообщают, что ленивая загрузка @Lob не работает со всеми драйверами / базой данных.

Некоторые пользователи сообщают, что это работает при использовании инструментария байт-кода (javassit? Cglib?).

Но я не могу найти четкую ссылку на все это в документации.

В конце, рекомендуемый обходной путь - использовать "поддельные" взаимно-однозначные сопоставления вместо свойств. Удалите поля больших объектов из существующего класса, создайте новые классы, ссылаясь на ту же таблицу, тот же первичный ключ и только необходимые поля больших объектов в качестве свойств. Укажите сопоставления как один-к-одному, fetch="select", lazy="true". Пока ваш родительский объект все еще находится в вашем сеансе, вы должны получать именно то, что вы хотите. (просто перенести это в аннотации).

Ленивая загрузка свойств требует инструментирования байт-кода во время сборки.

Документы Hibernate: использование отложенной выборки свойств

Если вы хотите избежать инструментирования байт-кодом, одним из вариантов является создание двух сущностей, которые используют одну и ту же таблицу, одна с блобом, другая без. Тогда используйте объект с BLOB-объектами только тогда, когда вам нужен BLOB-объект.

Конечно, вы можете извлечь это значение и поместить его в новую таблицу с ленивым отношением "@OneToOne", однако в нашем приложении большие объекты загружаются по запросу с использованием только этой конфигурации.

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

Это тестируется в нашем проекте одновременно на PostgreSQL, MySQL, SQLServer и Oracle, поэтому он должен работать для вас

Я бы предложил вам использовать наследование для обработки этого сценария. Иметь базовый класс без BLOB-объектов и производный класс, содержащий байтовый массив. Вы должны использовать производный класс только тогда, когда вам нужно отобразить BLOB-объект в пользовательском интерфейсе.

У меня была та же проблема, и это было мое исправление:

Моя сущность:

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

Ленивая загрузка у меня работает, если я использую Blob тип вместо byte[].

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

Он загружается лениво, и если вам нужно получить его значение, обратитесь к этому полю:

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

Для меня ленивая загрузка работала только путем компиляции и последующего запуска, но не работала, например, на 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

Полный рабочий пример

Простой обходной путь с использованием нотации @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;
}
Другие вопросы по тегам