Hibernate et verrouillage de la base de données 1. Pourquoi utiliser des verrous?
Pour comprendre pourquoi le mécanisme de verrouillage existe, vous devez d'abord comprendre le concept des transactions.
Une transaction est une série d'opérations connexes sur une base de données, et elle doit avoir des caractéristiques acides:
Notre RDBM de base de données relationnelle couramment utilisée met en œuvre ces caractéristiques des transactions. Parmi eux, atomicité,
La cohérence et la persistance sont garanties par l'exploitation forestière. L'isolement est obtenu par le mécanisme de verrouillage qui nous préoccupe aujourd'hui, c'est pourquoi nous avons besoin du mécanisme de verrouillage.
S'il n'y a pas de verrouillage et aucun contrôle sur l'isolement, quelles conséquences peuvent être causées?
Jetons un coup d'œil à l'exemple d'hibernate. Deux threads commencent respectivement deux opérations de transaction. La même ligne de données dans la table tb_account est col_id = 1.
package com.cdai.orm.hibernate.annotation; import java.io.serializable; import javax.persistence.column; import javax.persistence.entity; import javax.persistence.id; import javax.persistence.Table; @Entity @Table (name = "tb_account") Le compte de classe publique implémente Serializable {private static final long SerialVersionUID = 5018821760412231859l; @Id @Column (name = "Col_id") ID long privé; @Column (name = "col_balance") Balance longue privée; Public Account () {} Public Account (Long Id, Long Balance) {this.id = id; this.balance = équilibre; } public long getID () {return id; } public void setid (long id) {this.id = id; } public long getBalance () {return Balance; } public void setBalance (long équilibre) {this.balance = équilibre; } @Override public String toString () {return "Compte [id =" + id + ", balance =" + bancal + "]"; }} package com.cdai.orm.hibernate.transaction; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.transaction; import org.hibernate.cfg.annotationConfiguration; import com.cdai.orm.hibernate.annotation.account; classe publique DirtyRead {public static void main (String [] args) {final sessionfactory sessionfactory = new AnnotationConfiguration (). addFile ("hibernate / hibernate.cfg.xml"). configure (). addPackage ("com.cdai.orm.hibernate.annotation"). AddannotedClass (account.class). BuildSessionFactory (); Thread t1 = new Thread () {@Override public void run () {session session1 = sessionfactory.OpenSession (); Transaction tx1 = null; try {tx1 = session1.begintransaction (); System.out.println ("T1 - Begin Trasaction"); Thread.Sleep (500); Compte compte = (compte) session1.get (compte.class, new long (1)); System.out.println ("T1 - Balance =" + compte.GetBalance ()); Thread.Sleep (500); account.setBalance (account.getBalance () + 100); System.out.println ("T1 - Changement Balance:" + Account.getBalance ()); tx1.Commit (); System.out.println ("T1 - Commit Transaction"); Thread.Sleep (500); } catch (exception e) {e.printStackTrace (); if (tx1! = null) tx1.rollback (); } enfin {session1.close (); }}}}; // 3.Run Transaction 2 Thread T2 = nouveau thread () {@Override public void run () {session session2 = sessionfactory.opencession (); Transaction tx2 = null; try {tx2 = session2.begintransaction (); System.out.println ("T2 - Begin Trasaction"); Thread.Sleep (500); Compte compte = (compte) session2.get (compte.class, new long (1)); System.out.println ("T2 - Balance =" + compte.GetBalance ()); Thread.Sleep (500); account.setBalance (account.getBalance () - 100); System.out.println ("T2 - Changement Balance:" + Account.getBalance ()); tx2.Commit (); System.out.println ("T2 - Commit Transaction"); Thread.Sleep (500); } catch (exception e) {e.printStackTrace (); if (tx2! = null) tx2.rollback (); } enfin {session2.close (); }}}}; t1.start (); t2.start (); while (t1.isalive () || t2.isalive ()) {try {thread.sleep (2000l); } catch (InterruptedException e) {}} System.out.println ("T1 et T2 sont morts."); sessionfactory.close (); }} La transaction 1 réduit COL_BALANCE de 100, tandis que la transaction 2 la réduit de 100, le résultat final peut être de 0 ou 200 et la mise à jour de la transaction 1 ou 2 peut être perdue. La sortie du journal le confirme également, les transactions 1 et 2
journal croisé.
T1 - Begin TrasActionT2 - Begin TrasactionHiberNate: Sélectionnez Account0_.Col_id as Col1_0_0_, compte 0_.Col_Balance as Col2_0_0_ From TB_Account Account0_ WHERE COUTE0_.COL_ID =? Hibernate: Sélectionnez Account0_.Col_id As Col1_0_. tb_account compte0_ où compte0_.col_id =? t1 - bilan = 100t2 - bancal = 100t2 - Change Balance: 0t1 - Changer Balance: 200Hibernate: Update TB_Account set col_balance =? Où Col_id =? HiberNate: Mettez à jour TB_ACTUNT SET COL_BALANCE =? Où COL_ID =? T1 - Commit TransactionT2 - Commit TransactionBoth T1 et T2 sont morts.
On peut voir que l'isolement est une question qui nécessite une attention particulière et il est nécessaire de comprendre les serrures.
2. Combien de types de verrous y a-t-il?
Les verrous communs sont des verrous partagés, des verrous à jour et des verrous exclusifs.
1. Verrouillage partagé: utilisé pour la lecture des opérations de données, permettant à d'autres transactions d'être lues simultanément. Lorsqu'une transaction exécute une instruction SELECT,
La base de données attribue automatiquement un verrou partagé à la transaction pour verrouiller les données de lecture.
2. Lock exclusif: utilisé pour modifier les données, d'autres transactions ne peuvent pas être lues ou modifiées. Lorsque la transaction exécute l'insert,
Lorsque la mise à jour et la suppression sont mises à jour, la base de données sera automatiquement allouée.
3. Verrouillage de mise à jour: Utilisé pour éviter les blocages causés par les verrous partagés lors des opérations de mise à jour, telles que les transactions 1 et 2 en tenant des verrous partagés en même temps et en attendant d'obtenir des verrous exclusifs. Lors de la mise à jour, la transaction acquiert d'abord le verrouillage de mise à jour, puis met à niveau le verrouillage de mise à jour vers un verrou exclusif, évitant ainsi l'impasse.
De plus, ces serrures peuvent toutes être appliquées à différents objets dans la base de données, c'est-à-dire que ces serrures peuvent avoir des granularités différentes.
Tels que les verrous au niveau de la base de données, les verrous au niveau de la table, les verrous au niveau de la page, les verrous au niveau des touches et les verrous au niveau des lignes.
Il existe donc de nombreux types de serrures. Il est trop difficile de maîtriser complètement et d'utiliser tant de verrous de manière flexible. Nous ne sommes pas des DBA.
ce qu'il faut faire? Heureusement, le mécanisme de verrouillage est transparent pour les utilisateurs ordinaires. La base de données ajoutera automatiquement les verrous appropriés et mettra automatiquement à mettre à niveau et à rétrograder divers verrous au bon moment. C'est tellement réfléchi! Tout ce que nous devons faire est d'apprendre à définir le niveau d'isolement en fonction des différents besoins commerciaux.
3. Comment régler le niveau d'isolement?
D'une manière générale, le système de base de données fournit quatre niveaux d'isolement de transaction pour les utilisateurs parmi lesquels:
1. SERIALISABLE: Lorsque deux transactions manipulent les mêmes données en même temps, la transaction 2 ne peut que s'arrêter et attendre.
2. Readable Readable (reproductible): La transaction 1 peut voir les données nouvellement insérées de la transaction 2 et ne peut pas voir les mises à jour des données existantes.
3. Rectez-vous (lire les données engagées): la transaction 1 peut voir les données nouvellement insérées et mises à jour de la transaction 2.
4. Lisez non engagé (lire les données non engagées): la transaction 1 peut voir l'insertion et la mise à jour des données que la transaction 2 n'a pas commise.
4. verrouille la demande
Lorsque la base de données adopte le niveau d'isolement de la commission de lecture, des verrous pessimistes ou des verrous optimistes peuvent être utilisés dans l'application.
1. Verrouillage pessimiste: Supposons que les données de l'opération de transaction actuelle auront certainement un autre accès aux transactions, alors spécifiez de manière pessimiste que le verrouillage exclusif est utilisé dans l'application pour verrouiller les ressources de données. Soutenez les formulaires suivants dans MySQL et Oracle:
Sélectionnez ... pour la mise à jour
Laissez explicitement le SELECT utiliser le verrouillage exclusif pour verrouiller les enregistrements de la requête. Pour que d'autres transactions interrogent, mettent à jour ou suppriment ces données verrouillées, elles doivent attendre la fin de la transaction.
Dans Hibernate, vous pouvez passer dans LockMode.upgrade lors du chargement pour adopter un verrou pessimiste. Modifier l'exemple précédent,
Aux appels de méthode GET des transactions 1 et 2, un paramètre supplémentaire de verrouillage est passé. Comme on peut le voir dans le journal, les transactions 1 et 2
Il n'est plus croisé, la transaction 2 peut attendre que la transaction 1 se termine avant de lire les données, de sorte que la valeur finale COL_BALANCE est correcte 100.
package com.cdai.orm.hibernate.transaction; import org.hibernate.lockmode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.transaction; import com.cdai.orm.hibernate.annotation.account; import com.cdai.orm.hibernate.annotation.annotationhibernate; classe publique UpragradeLock {@SuppressWarnings ("Deprécation") public static void main (String [] args) {final SessionFactory SessionFactory = annotationHiberNate.CreateSessionFactory (); // Exécuter la transaction 1 thread t1 = nouveau thread () {@Override public void run () {session session1 = sessionfactory.opencession (); Transaction tx1 = null; try {tx1 = session1.begintransaction (); System.out.println ("T1 - Begin Trasaction"); Thread.Sleep (500); Compte compte = (compte) session1.get (compte.class, new Long (1), LockMode.upgrade); System.out.println ("T1 - Balance =" + compte.GetBalance ()); Thread.Sleep (500); account.setBalance (account.getBalance () + 100); System.out.println ("T1 - Changement Balance:" + Account.getBalance ()); tx1.Commit (); System.out.println ("T1 - Commit Transaction"); Thread.Sleep (500); } catch (exception e) {e.printStackTrace (); if (tx1! = null) tx1.rollback (); } enfin {session1.close (); }}}; // Exécuter la transaction 2 thread t2 = nouveau thread () {@Override public void run () {session session2 = sessionfactory.opencession (); Transaction tx2 = null; try {tx2 = session2.begintransaction (); System.out.println ("T2 - Begin Trasaction"); Thread.Sleep (500); Compte compte = (compte) session2.get (compte.class, new Long (1), LockMode.upgrade); System.out.println ("T2 - Balance =" + compte.GetBalance ()); Thread.Sleep (500); account.setBalance (account.getBalance () - 100); System.out.println ("T2 - Changement Balance:" + Account.getBalance ()); tx2.Commit (); System.out.println ("T2 - Commit Transaction"); Thread.Sleep (500); } catch (exception e) {e.printStackTrace (); if (tx2! = null) tx2.rollback (); } enfin {session2.close (); }}}}; t1.start (); t2.start (); while (t1.isalive () || t2.isalive ()) {try {thread.sleep (2000l); } catch (InterruptedException e) {}} System.out.println ("T1 et T2 sont morts."); sessionfactory.close (); }}T1 - Begin TrasActionT2 - Begin TrasactionHiberNate: Sélectionnez Account0_.Col_Id as Col1_0_0_, compte 0_.col_balance en tant que Col2_0_0_ à partir de TB_ACTUCT COUTEL0_ avec (UPDLOCK, ROWLOCK) WHERE COUTE0_ COL2_0_0_ DE TB_ACTUNT COUTEL0_ avec (UPDLOCK, ROWLOCK) WHERE COUTE0_.COL_ID =? T2 - Balance = 100T2 - Changer Balance: 0HiberNate: Update TB_ACTUNT SET COL_BALANCE =? Où COL_ID =? T2 - Commit TransactionT1 - Balance = 0T1 - Modifier Balance: 100Hibernate: Update TB_ACTUNT SET COL_BALANCE =? Où COL_ID =? T1 - Commit TransactionBoth T1 et T2 sont morts.
HiberNate exécute SQL pour SQLServer 2005:
La copie de code est la suivante:
Sélectionnez Account0_.COL_ID AS COL1_0_0_, COMPTE0_.COL_BALANCE AS COL2_0_0_ DE TB_ACTUNT COUTEL0_ avec (UPDLOCK, ROWLOCK) WHERE COUTE0_.COL_ID =?
2. Verrouillage optimiste: supposons que les données de l'opération de transaction actuelle ne seront pas accessibles en même temps par d'autres transactions, de sorte que le niveau d'isolement de la base de données est complètement invoqué sur la base de données pour gérer automatiquement le travail du verrouillage. Adoptez le contrôle de la version dans les applications pour éviter les problèmes de concurrence qui peuvent survenir à faible probabilité.
Dans Hibernate, utilisez des annotations de version pour définir le champ Numéro de version.
Remplacez l'objet de compte dans Dirtylock par AccountSversion, et l'autre code reste inchangé, et une exception se produit lorsque l'exécution se produit.
package com.cdai.orm.hibernate.transaction; import javax.persistence.column; import javax.persistence.entity; import javax.persistence.id; import javax.persistence.Table; import javax.persistence.version; @Entity @Table (name = "tb_account_version") Class public AccountVersion {@id @column (name = "col_id") private long id; @Column (name = "col_balance") Balance longue privée; @Version @Column (name = "Col_version") Version int privé; Public AccountVersion () {} public AccountVersion (Long ID, long bilan) {this.id = id; this.balance = équilibre; } public long getID () {return id; } public void setid (long id) {this.id = id; } public long getBalance () {return Balance; } public void setBalance (long équilibre) {this.balance = équilibre; } public int getVersion () {return version; } public void setVersion (int version) {this.version = version; }}Le journal est le suivant:
T1 - Begin TrasActionT2 - Begin TrasactionHiberNate: Select Accountver0_.Col_id as Col1_0_0_, Accountver0_.Col_Balance as Col2_0_0_, Accountver0_.Col_version As Col3_0_0_ à partir de TB_ACTUPT_VERSION CONCEETVER0_ WHOR COUNTER0_.COL_ID =? COL1_0_0_, accountver0_.col_balance as Col2_0_0_, comptactver0_.col_version as Col3_0_0_ from tb_account_version comptactever0_ where accountver0_.col_id =? T1 - bancal = 1000t2 - balandi col_balance =?, col_version =? où col_id =? et col_version =? HiberNate: mise à jour tb_account_version set col_balance =?, col_version =? où col_id =? et col_version =? T1 - Commit Transaction2264 [thread-2] error org.hibernate.event.def.abstractflushingEventListener - n'a pas pu synchroniser l'état de base de données avec sessionorg.hibernate.staleobjectStateException: la ligne a été mise à jour ou supprimée par une autre transaction (ou une cartographie de valeur non sauveuse a été incorrecte): [com.cdai.orm.Hibernate.transaction.accountversion # 1] sur org.hibernate.persister.entity.absutrauntityPersister.Check (abstractntityPersister.java:1934) à org.hibernate.persister.entity.absuntityPerseter.update (AbstracTentityPersister.java:2578) ATTRAC org.hibernate.persister.entity.abstractntityPersister.updateorinsert (abstractntityPersister.java:2478) at org.hibernate.persister.entity.abstractAntityPersister.update (abstraitntitypersister.java:2805) at org.hibernate.action.entityupdatealection.excute (entityupdatealection.java:114) sur org.hibernate.engine.actionqueue.execute (actionqueue.java:268) à org.hibernate.ngine.actionqueue.execureActions (ActionQueue.java:260) à la org.hibernate.engine.actionqueue.execUteActions (actionQueue.java:180) sur org.hibernate.event.def.abstrusfrflushingEventListener.Perpexexécutions (AbstractFlushingEventListener.java:321) org.hibernate.event.def.defaultflushEventListener.onflush (defaulfushEventListener.java:51) sur org.hibernate.impl.sessionImpl.flush (sessionImpl.Java:1206) sur org.hibernate.impl org.hibernate.transaction.jdbctransaction.commit (jdbctransaction.java:137) sur com.cdai.orm.hibernate.transaction.versionlock 2 $ Rec (versionlock.java:93) T1h et T2 sont morts.
Étant donné que le verrouillage optimiste laisse complètement l'isolement des transactions à la base de données pour le contrôle, les transactions 1 et 2 exécutent, la transaction 1 est engagée avec succès et Col_version est modifiée à 1. Cependant, lorsque la transaction 2 engage, les données avec Col_version de 0 ne peuvent plus être trouvées, une exception a donc été jetée.
Comparaison des méthodes de requête Hibernate
Il existe trois méthodes de requête principales pour l'hibernate:
1.HQL (langue de requête Hibernate)
Il est très similaire à SQL et prend en charge des fonctionnalités telles que la pagination, la connexion, le regroupement, les fonctions d'agrégation et la sous-requête.
Mais HQL est orienté objet, pas des tables dans les bases de données relationnelles. Étant donné que les instructions de requête sont orientées vers des objets de domaine, l'utilisation de HQL peut obtenir des avantages multiplateformes. Hiberner
Il nous aidera automatiquement à se traduire par différentes instructions SQL en fonction de différentes bases de données. Ceci est très pratique dans les applications qui doivent prendre en charge plusieurs bases de données ou migrations de bases de données.
Mais tout en le faisant pratique, puisque les instructions SQL sont automatiquement générées par Hibernate, cela n'est pas propice à l'optimisation et à la débogage des instructions SQL. Lorsque la quantité de données est importante, il peut y avoir des problèmes d'efficacité.
S'il y a un problème, il n'est pas pratique de l'étudier et de le résoudre.
2.QBC / QBE (requête par critères / exemple)
QBC / QBE effectue une requête en assemblant des conditions de requête ou des objets de modèle. Cela est pratique dans les applications qui nécessitent un support flexible pour de nombreuses combinaisons libres de conditions de requête. Le même problème est que puisque l'instruction de requête est librement assemblée, le code pour créer une instruction peut être long et contient de nombreuses conditions de branchement, ce qui est très gênant pour l'optimisation et le débogage.
3.SQL
HiberNate prend également en charge les méthodes de requête qui exécutent directement SQL. Cette méthode sacrifie les avantages de l'hibernate en matière de database croisée et écrit manuellement les instructions SQL sous-jacentes pour réaliser la meilleure efficacité d'exécution.
Par rapport aux deux premières méthodes, l'optimisation et le débogage sont plus pratiques.
Jetons un coup d'œil à un ensemble d'exemples simples.
package com.cdai.orm.hibernate.query; import java.util.arrays; Importer java.util.list; import org.hibernate.critria; import org.hibernate.query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.annotationConfiguration; import org.hibernate.criterion.critrion; import org.hibernate.criterion.example; import org.hibernate.criterion.expression; import com.cdai.orm.hibernate.annotation.account; classe publique BasicQuery {public static void main (String [] args) {sessionfactory sessionfactory = new AnnotationConfiguration (). addFile ("hibernate / hibernate.cfg.xml"). configure (). addPackage ("com.cdai.orm.hibernate.annotation"). AddannotedClass (account.class). BuildSessionFactory (); Session Session = SessionFactory.OpenSession (); // 1.HQL Query Query = Session.CreateeQuery ("From Compte as a Where A.Id =: Id"); query.setLong ("id", 1); Liste Résultat = query.list (); for (object row: result) {System.out.println (row); } // 2.QBC Critères Critères = Session.CreateCriteria (compte.class); critères.add (expression.eq ("id", new long (2))); résultat = critères.List (); for (object row: result) {System.out.println (row); } // 3.QBE Compte Exemple = nouveau compte (); Exemple.setBalance (100); result = session.createCriteria (account.class). ajouter (exemple.Create (exemple)). liste(); for (object row: result) {System.out.println (row); } // 4.sql query = session.cretesqlQuery ("Sélectionnez Top 10 * dans l'ordre tb_account par col_id desc"); result = query.list (); for (objet Row: result) {System.out.println (arrays.tostring ((objet []) row)); } session.close (); }}Hibernate: sélectionnez Account0_.Col_id en tant que col1_0_, compte0_.col_balance comme col2_0_ à partir de TB_ACTUNT compte0_ où le compte0_.col_id =? Compte [id = 1, Balance = 100] HiberNate: Sélectionnez This_.Col_id comme col1_0_0_ this_.col_id =? Compte [id = 2, Balance = 100] hibernate: sélectionnez this_.col_id comme col1_0_0_, this_.col_balance as Col2_0_0_ from tb_account this_ where (this_.col_balance =?) Compte [id = 1, Balance = 100] compte [id = 2, bancal = 100] hibern desc [2, 100] [1, 100]
Dans le journal, vous pouvez clairement voir le contrôle d'Hibernate sur les instructions SQL générées. La méthode de requête spécifique à choisir dépend de l'application spécifique.