web-dev-qa-db-fra.com

Printemps, Hibernate, chargement paresseux

J'ai besoin d'aide pour le chargement de blob paresseux dans Hibernate. J'ai dans mon application Web ces serveurs et infrastructures: MySQL, Tomcat, Spring et Hibernate.

La partie de la base de données 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>

La partie de la classe d'entité

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

La description du problème. J'essaie d'afficher sur une page Web les enregistrements liés aux fichiers, qui ont été enregistrés dans la base de données MySQL. Tout fonctionne bien si un volume de données est petit. Mais le volume de données est important. Je reçois une erreur Java.lang.OutOfMemoryError: Java heap space J'ai essayé d'écrire dans les valeurs NULL de blobFields sur chaque ligne de la table. Dans ce cas, l'application fonctionne bien, la mémoire ne disparaît pas. J'ai une conclusion que le champ blob qui est marqué comme paresseux (@Basic(fetch=FetchType.LAZY)) n'est pas paresseux, en fait!

23
Alexey Khudyakov

Je suis confus. Emmanuel Bernard a écrit dans ANN-418 que le @Lob est paresseux (c'est-à-dire que vous n'avez même pas besoin d'utiliser l'annotation @Basic(fetch = FetchType.LAZY)). 

Certains utilisateurs signalent que le chargement paresseux d'un @Lobne fonctionne pas avec tous les pilotes/bases de données .

Certains utilisateurs signalent que cela fonctionne lors de l’utilisation de bytecode instrumentation (javassit? Cglib?).

Mais je ne trouve pas de référence claire à tout cela dans la documentation. 

À la fin, la solution de contournement recommandée consiste à utiliser un "faux" mappages un à un au lieu de propriétés. Supprimez les champs LOB de votre classe existante, créez de nouvelles classes faisant référence à la même table, à la même clé primaire et uniquement aux champs LOB nécessaires en tant que propriétés. Spécifiez les mappages comme un à un, fetch = "select", lazy = "true". Tant que votre objet parent est toujours dans votre session, vous devriez obtenir exactement ce que vous voulez. (il suffit de transposer ceci aux annotations).

30
Pascal Thivent

Bien sûr, vous pouvez extraire cette valeur et la placer dans une nouvelle table avec une relation "@OneToOne" paresseuse. Toutefois, dans notre application, les LOB sont chargés paresseusement à la demande en utilisant uniquement cette configuration.

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

Ceci est testé simultanément dans notre projet sur PostgreSQL, MySQL, SQLServer et Oracle, il devrait donc fonctionner pour

5
Hons

Je vous suggère d'utiliser l'héritage pour gérer ce scénario. Avoir une classe de base sans le blob et une classe dérivée contenant le tableau d'octets. Vous utiliseriez la classe dérivée uniquement lorsque vous devez afficher le blob sur l'interface utilisateur.

3
Darin Dimitrov

Le chargement de propriété paresseux nécessite une instrumentation en bytecode à la compilation.

Hibernate docs: Utilisation de l'extraction de propriété paresseuse

Si vous voulez éviter l’instrumentation en bytecode, une option consiste à créer deux entités qui utilisent le même tableau, une avec le blob sans l’autre. Ensuite, utilisez uniquement l'entité avec blob lorsque vous en avez besoin.

3
Ben George

J'ai eu le même problème et c'était ma solution:

Mon entité:

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

...

Ajout du plugin dans 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>
0
Gina De Beukelaer

Pour moi, la charge paresseuse ne fonctionnait qu'en compilant puis en l'exécutant, ne fonctionnait pas sur Eclipse ou intellij par exemple. 

J'utilise gradle puis j'ai fait ce qui suit pour le faire fonctionner

  • Annoter l'entité
  • Configurer le plugin 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;

Essai

./gradlew run

Exemple de travail complet

0
deFreitas