web-dev-qa-db-fra.com

org.hibernate.Session.clear () considéré comme Nocif?

Ceci est une question de conception, un code concret non soumis pour protéger mes fesses.

Lorsque vous travaillez avec Hibernate, le flux de travail standard est le suivant:

  1. Session ouverte
  2. Lancer la transaction
  3. Faire des affaires (lire et modifier des données)
  4. Valider la transaction
  5. Fermer la session

avec éventuellement des itérations de 2 à 4.

Quels sont les cas d'utilisation raisonnables pour Session.clear ()?

R: Le problème concret que j'ai est un (gros) morceau de code qui charge et modifie les entités, puis efface () la session, jetant essentiellement les modifications qui ont été apportées. (La tâche commerciale à accomplir n'inclut pas la modification des entités, donc le code "fonctionne").

Il me semble que la bonne conception serait de s'assurer que le (gros) morceau de code n'apporte pas de modifications qu'il ne souhaite pas enregistrer?

B: Je suppose que Session.clear () existe pour plus de commodité/flexibilité, pas parce que c'est une bonne idée de l'utiliser.

Ai-je mal compris la philosophie Hibernate?

C: Sous-question: Est-ce une mauvaise idée que le code framework efface inconditionnellement () la session lorsqu'une tâche se termine? À mon humble avis, le cadre devrait se plaindre si la session est sale à la fin d'une tâche! La session devrait être fermée, vu que la tâche est terminée ... (Ne tenant pas compte des performances pour la minute)

(Étiquettes A, B et C pour que vous puissiez indiquer à quelle partie vous répondez).

Annonce. R : Il semble que vous sachiez ce que fait clear(). La raison de l'appeler explicitement est de supprimer toutes les entités gérées du cache L1, afin qu'il ne croisse pas indéfiniment lors du traitement de grands ensembles de données dans une transaction.

Il supprime toutes les modifications apportées aux entités gérées qui ne sont pas explicitement persistées . Cela signifie que vous pouvez modifier une entité en toute sécurité, la mettre à jour explicitement et effacer la session. C'est la conception à droite. Évidemment, si aucune modification n'est effectuée (session longue mais en lecture seule), clear() est toujours sûr.

Vous pouvez également utiliser sessions sans état .

Annonce. B : Non, il existe pour les raisons ci-dessus: pour s'assurer que L1 (cache de session) ne se développe pas trop. Bien sûr, le maintenir manuellement est une mauvaise idée et une indication qu'un autre outil devrait être utilisé pour les grands ensembles de données, mais parfois c'est un must.

Notez que dans la spécification JPA, il existe également la méthode clear() et flush(). Dans ce cas, vous devez toujours appeler flush() en premier pour pousser les modifications dans la base de données (mise à jour explicite) avant d'appeler clear().

Annonce. C : C'est en fait une bonne idée d'avertir l'utilisateur (peut-être en émettant un message d'avertissement plutôt qu'en lançant une exception) quand il/elle efface la session avec des changements sales. De plus, je ne pense pas qu'un code framework devrait appeler clear() sans condition, à moins qu'il ne soit sûr que le code utilisateur qu'il exécute videra ou n'apportera aucune modification.

29
Tomasz Nurkiewicz

Voici une autre raison que je viens de rencontrer: la mise en cache des résultats précédents lors de l'appel de plusieurs fois une procédure stockée dans la même transaction. Code simplifié comme suit.

//Begin transaction
SessionFactory sf = HibernateSessionFactory.getFactory();
Session dbSession = sf.getCurrentSession();
dbSession.beginTransaction();

//First call to stored procedure
Query query = dbSession.getNamedQuery("RR_CUST_OPP_DATA");
query.setString("custName", "A");
List<ShipSummaryRow> shipSummaryRows = query.list();

//Second call to stored procedure
Query query = dbSession.getNamedQuery("RR_CUST_OPP_DATA");
query.setString("custName", "B");
List<ShipSummaryRow> shipSummaryRows = query.list();

//Commit both    
dbSession.getTransaction().commit();

Sans clear () après le premier appel, les lignes de l'ensemble de résultats du premier appel sont répliquées dans l'ensemble de résultats du deuxième appel. J'utilise Oracle 11gR2.

La clé de la réplication de ce bogue consiste à effectuer les deux appels au cours de la même transaction. Puisque j'utilise une session ouverte dans le modèle de vue, les deux appels se produisent automatiquement dans la même transaction (car le code d'origine appelle le proc dans une boucle stockant les résultats de chacun). C'est pourquoi j'appelle cela un bug; else pourrait être considéré comme une fonctionnalité mais même dans ce cas, clear () n'est pas appelé dans les exemples de code indiquant qu'il doit être appelé. session.flush () n'a rien fait. Fichier de mappage comme ci-dessous. Par conséquent, j'ai ajouté clear () à la fin de tous mes appels de procédure. Je n'ai pas encore testé avec mes appels SQL personnalisés. C'est un truc trivial; surpris que le bug existe.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.jfx.rr.model.ShipSummaryRow">
        <id name="id" type="integer"/>
        <property name="shipQtrString" not-null="true" type="string"/>
        <property name="shipAmount" not-null="true" type="double"/>
    </class>
    <sql-query callable="true" name="RR_CUST_OPP_DATA">
        <return class="com.jfx.rr.model.ShipSummaryRow">
            <return-property column="SHIPPED_ID" name="id"/>
            <return-property column="SHIP_QTR" name="shipQtrString"/>
            <return-property column="SHIPPED_AMOUNT" name="shipAmount"/>
        </return>
        { call RR_DASHBOARD_REPORTS_PKG.RR_CUST_OPP_DATA(?, :custName) }
    </sql-query>
</hibernate-mapping>
3
Anand