La différence entre le cache de premier niveau et le cache de deuxième niveau en veille prolongée
Le cache se situe entre l'application et la source de données physique. Sa fonction est de réduire la fréquence d'accès de l'application à la source de données physique, améliorant ainsi. le fonctionnement de l’application. Les données du cache sont une copie des données de la source de données physique. L'application lit et écrit les données du cache au moment de l'exécution et synchronise les données du cache et la source de données physique à un moment ou un événement spécifique.
Le support de cache est généralement de la mémoire, la vitesse de lecture et d'écriture est donc très rapide. Mais si la quantité de données stockées dans le cache est très importante, le disque dur sera également utilisé comme support de cache. La mise en œuvre du cache doit non seulement prendre en compte le support de stockage, mais également considérer la gestion des accès concurrents au cache et le cycle de vie des données mises en cache.
Le cache d'Hibernate comprend le cache de session et le cache SessionFactory qui peut être divisé en deux catégories : le cache intégré et le cache externe. Le cache de session est intégré et ne peut pas être déchargé. Il est également appelé cache de premier niveau d'Hibernate. Le cache intégré de SessionFactory et le cache de Session sont similaires dans leur implémentation. Le premier fait référence aux données contenues dans certains attributs de collection de l'objet SessionFactory, et le second fait référence aux données contenues dans certains attributs de collection de Session. Le cache intégré de SessionFactory stocke les métadonnées de mappage et les instructions SQL prédéfinies. Les métadonnées de mappage sont une copie des données du fichier de mappage, et l'instruction SQL prédéfinie est dérivée des métadonnées de mappage pendant la phase d'initialisation d'Hibernate. Le cache de SessionFactory est uniquement destiné à la lecture, l'application ne peut pas modifier les métadonnées de mappage et les instructions SQL prédéfinies dans le cache, SessionFactory n'a donc pas besoin de synchroniser le cache intégré avec le fichier de mappage. Le cache externe de SessionFactory est un plug-in configurable. Par défaut, SessionFactory n'active pas ce plug-in. Les données du cache externe sont une copie des données de la base de données et le support du cache externe peut être la mémoire ou le disque dur. Le cache externe de SessionFactory est également appelé cache de deuxième niveau d'Hibernate.
Les deux niveaux de cache d'Hibernate sont tous deux situés dans la couche de persistance et stockent des copies des données de la base de données. Alors, quelle est la différence entre eux ? Afin de comprendre la différence entre les deux, il est nécessaire d'avoir une compréhension approfondie des deux caractéristiques du cache de couche de persistance : la portée du cache et la politique d'accès concurrent du cache.
La portée du cache dans la couche de persistance
La portée du cache détermine le cycle de vie du cache et qui peut y accéder. La portée du cache est divisée en trois catégories.
1 Portée de la transaction : le cache n'est accessible que par la transaction en cours. Le cycle de vie du cache dépend du cycle de vie de la transaction. Lorsque la transaction se termine, le cache termine également son cycle de vie. Dans ce cadre, le support de cache est la mémoire. Les transactions peuvent être des transactions de base de données ou des transactions d'application. Chaque transaction possède son propre cache. Les données contenues dans le cache se présentent généralement sous la forme d'objets interdépendants.
2 Portée du processus : le cache est partagé par toutes les transactions au sein du processus. Ces transactions peuvent accéder au cache simultanément, les mécanismes d'isolation des transactions nécessaires doivent donc être adoptés pour le cache. Le cycle de vie du cache dépend du cycle de vie du processus. Lorsque le processus se termine, le cache termine également son cycle de vie. Le cache à l'échelle du processus peut stocker une grande quantité de données, le support de stockage peut donc être une mémoire ou un disque dur. Les données dans le cache peuvent être sous la forme d'objets associés ou de données d'objets en vrac. La forme des données d'objets libres est quelque peu similaire aux données sérialisées de l'objet, mais l'algorithme de décomposition des objets en données libres est plus rapide que l'algorithme requis pour la sérialisation des objets.
3 Portée du cluster : dans un environnement de cluster, le cache est partagé par les processus sur une ou plusieurs machines. Les données du cache sont copiées sur chaque nœud de processus dans l'environnement de cluster et la communication à distance est utilisée entre les processus pour garantir la cohérence des données dans le cache. Les données du cache prennent généralement la forme de données d'objets en vrac.
Pour la plupart des applications, vous devez soigneusement réfléchir à l'opportunité d'utiliser un cache à l'échelle du cluster, car la vitesse d'accès n'est pas nécessairement beaucoup plus rapide que l'accès direct aux données de la base de données.
La couche de persistance peut fournir plusieurs plages de cache. Si les données correspondantes ne sont pas trouvées dans le cache de la portée des transactions, vous pouvez également les interroger dans le cache de la portée du processus ou du cluster. Si elles ne sont toujours pas trouvées, vous pouvez uniquement les interroger dans la base de données. Le cache à l'échelle de la transaction est le cache de premier niveau de la couche de persistance et est généralement requis ; le cache à l'échelle du processus ou du cluster est le cache de deuxième niveau de la couche de persistance et est généralement facultatif.
Stratégie d'accès simultané pour le cache de la couche de persistance
Lorsque plusieurs transactions simultanées accèdent simultanément aux mêmes données mises en cache dans la couche de persistance, des problèmes de concurrence se produiront et les mesures d'isolation des transactions nécessaires doivent être prises.
Des problèmes de concurrence peuvent survenir dans les caches à l'échelle du processus ou du cluster, c'est-à-dire les caches de deuxième niveau. Par conséquent, les quatre types suivants de stratégies d'accès simultané peuvent être définis, chaque stratégie correspondant à un niveau d'isolement des transactions.
Transactionnel : applicable uniquement dans les environnements gérés. Il fournit un niveau d’isolement des transactions de lecture répétable. Pour les données fréquemment lues mais rarement modifiées, ce type d'isolation peut être utilisé car il peut éviter les problèmes de concurrence tels que les lectures incorrectes et les lectures non répétables.
Lecture-écriture : fournit un niveau d'isolement des transactions en lecture validée. Applicable uniquement dans un environnement non clusterisé. Pour les données fréquemment lues mais rarement modifiées, ce type d'isolation peut être utilisé car il peut éviter les problèmes de concurrence tels que les lectures incorrectes.
Lecture-écriture non stricte : la cohérence des données dans le cache et la base de données n'est pas garantie. S'il est possible que deux transactions accèdent aux mêmes données dans le cache en même temps, un court délai d'expiration des données doit être configuré pour les données afin d'éviter les lectures sales. Cette stratégie d'accès simultané peut être utilisée pour des données rarement modifiées et permet des lectures sales occasionnelles. Lecture seule : cette stratégie d'accès simultané peut être utilisée pour des données qui ne seront jamais modifiées, telles que les données de référence.
La stratégie d'accès transactionnel simultané a le niveau d'isolement des transactions le plus élevé et le niveau d'isolement en lecture seule est le plus bas. Plus le niveau d’isolation des transactions est élevé, plus les performances de concurrence sont faibles.
Quels types de données peuvent être stockés dans le cache de deuxième niveau ?
1. Données rarement modifiées
2. Données peu importantes, des données simultanées occasionnelles sont autorisées
3. Données qui ne seront pas accessibles simultanément
4. Données de référence
Données qui ne convient pas au stockage dans le cache de deuxième niveau ?
1. Données fréquemment modifiées
2. Données financières, la concurrence n'est absolument pas autorisée
3. Données partagées avec d'autres applications.
Cache de deuxième niveau d'Hibernate
Comme mentionné précédemment, Hibernate fournit un cache à deux niveaux, le premier niveau est le cache de session. Le cycle de vie de l'objet Session correspondant généralement à une transaction de base de données ou à une transaction d'application, son cache est un cache de portée transaction. La mise en cache de premier niveau est requise, n'est pas autorisée et ne peut en fait pas être supprimée. Dans le cache de premier niveau, chaque instance d'une classe persistante possède un OID unique.
Le cache de deuxième niveau est un plug-in de cache enfichable, géré par SessionFactory. Puisque le cycle de vie de l'objet SessionFactory correspond à l'ensemble du processus de l'application, le cache de deuxième niveau est un cache à l'échelle du processus ou du cluster. Données lâches des objets stockés dans ce cache. Les objets de deuxième niveau peuvent poser des problèmes de concurrence et nécessitent une stratégie d'accès simultané appropriée qui fournit un niveau d'isolation des transactions pour les données mises en cache. L'adaptateur de cache est utilisé pour intégrer un logiciel d'implémentation de cache spécifique avec Hibernate. La mise en cache de deuxième niveau est facultative et peut être configurée selon une granularité par classe ou par collection.
Le processus général de la stratégie de cache de deuxième niveau d'Hibernate est le suivant :
1) Lors de l'interrogation des conditions, émettez toujours une instruction SQL telle que select * from table_name Where .... (sélectionnez tous les champs) pour interroger le base de données et obtenir tout à la fois un objet de données.
2) Mettez tous les objets de données obtenus dans le cache de deuxième niveau en fonction de leurs identifiants.
3) Lorsqu'Hibernate accède à l'objet de données en fonction de l'ID, il le recherche d'abord dans le cache de premier niveau de la session ; s'il ne peut pas être trouvé, si le cache de deuxième niveau est configuré, il le vérifie ensuite à partir du deuxième ; -niveau cache ; s'il ne peut pas être trouvé, il interroge à nouveau la base de données et met le résultat dans le cache en fonction de l'ID.
4) Lors de la suppression, de la mise à jour ou de l'ajout de données, le cache est mis à jour en même temps.
La stratégie de cache de deuxième niveau d'Hibernate est une stratégie de cache pour les requêtes d'ID, mais n'a aucun effet sur les requêtes conditionnelles. À cette fin, Hibernate fournit une mise en cache des requêtes pour les requêtes conditionnelles.
Le processus de la stratégie de mise en cache des requêtes d'Hibernate est le suivant :
1) Hibernate forme d'abord une clé de requête basée sur ces informations. La clé de requête comprend les informations générales demandées par la requête conditionnelle : SQL, les paramètres requis par SQL, plage d'enregistrements (position de départ) rowStart, le nombre maximum d'enregistrements (maxRows), etc.
2) Hibernate recherche dans le cache de requêtes la liste de résultats correspondante en fonction de cette clé de requête. Si elle existe, renvoyez la liste des résultats ; si elle n’existe pas, interrogez la base de données, obtenez la liste des résultats et placez la liste complète des résultats dans le cache de requête en fonction de la clé de requête.
3) Le SQL dans la clé de requête implique certains noms de table. Si des données de ces tables sont modifiées, supprimées, ajoutées, etc., ces clés de requête associées seront effacées du cache.
Mécanisme de chargement paresseux d'Hibernate
Chargement paresseux :
Le mécanisme de chargement paresseux est proposé pour éviter une surcharge de performances inutile. Le soi-disant chargement paresseux signifie que lorsque les données sont vraiment nécessaires, Ce n'est qu'alors que l'opération de chargement des données est réellement effectuée. Hibernate fournit un chargement paresseux des objets d'entité et un chargement paresseux des collections. De plus, Hibernate3 fournit également un chargement paresseux des propriétés. Ci-dessous, nous présentons respectivement les détails de ces types de chargement paresseux.
A. Chargement différé des objets entité :
Si vous souhaitez utiliser le chargement différé pour les objets entité, vous devez effectuer la configuration correspondante dans le fichier de configuration de mappage de l'entité, comme indiqué ci-dessous :
<hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user” lazy=”true”> …… </class> </hibernate-mapping>
Activez la fonctionnalité de chargement différé de l'entité en définissant l'attribut paresseux de la classe sur true. Si on exécute le code suivant :
User user=(User)session.load(User.class,”1”);
(1)
System.out.println(user.getName());
(2)
当运行到(1)处时,Hibernate并没有发起对数据的查询,如果我们此时通过一些调试工具(比如JBuilder2005的Debug工具),观察此时user对象的内存快照,我们会惊奇的发现,此时返回的可能是User$EnhancerByCGLIB$$bede8986类型的对象,而且其属性为null,这是怎么回事?还记得前面我曾讲过session.load()方法,会返回实体对象的代理类对象,这里所返回的对象类型就是User对象的代理类对象。在Hibernate中通过使用CGLIB,来实现动态构造一个目标对象的代理类对象,并且在代理类对象中包含目标对象的所有属性和方法,而且所有属性均被赋值为null。通过调试器显示的内存快照,我们可以看出此时真正的User对象,是包含在代理对象的CGLIB$CALBACK_0.target属性中,当代码运行到(2)处时,此时调用user.getName()方法,这时通过CGLIB赋予的回调机制,实际上调用CGLIB$CALBACK_0.getName()方法,当调用该方法时,Hibernate会首先检查CGLIB$CALBACK_0.target属性是否为null,如果不为空,则调用目标对象的getName方法,如果为空,则会发起数据库查询,生成类似这样的SQL语句:select * from user where id='1';来查询数据,并构造目标对象,并且将它赋值到CGLIB$CALBACK_0.target属性中。
这样,通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。
B、 集合类型的延迟加载:
在Hibernate的延迟加载机制中,针对集合类型的应用,意义是最为重大的,因为这有可能使性能得到大幅度的提高,为此Hibernate进行了大量的努力,其中包括对JDK Collection的独立实现,我们在一对多关联中,定义的用来容纳关联对象的Set集合,并不是java.util.Set类型或其子类型,而是net.sf.hibernate.collection.Set类型,通过使用自定义集合类的实现,Hibernate实现了集合类型的延迟加载。为了对集合类型使用延迟加载,我们必须如下配置我们的实体类的关于关联的部分:
<hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> ….. <set name=”addresses” table=”address” lazy=”true” inverse=”true”> <key column=”user_id”/> <one-to-many class=”com.neusoft.entity.Arrderss”/> </set> </class> </hibernate-mapping>
通过将
User user=(User)session.load(User.class,”1”); Collection addset=user.getAddresses();
(1)
Iterator it=addset.iterator();
(2)
while(it.hasNext()){ Address address=(Address)it.next(); System.out.println(address.getAddress()); }
当程序执行到(1)处时,这时并不会发起对关联数据的查询来加载关联数据,只有运行到(2)处时,真正的数据读取操作才会开始,这时Hibernate会根据缓存中符合条件的数据索引,来查找符合条件的实体对象。
这里我们引入了一个全新的概念——数据索引,下面我们首先将接一下什么是数据索引。在Hibernate中对集合类型进行缓存时,是分两部分进行缓存的,首先缓存集合中所有实体的id列表,然后缓存实体对象,这些实体对象的id列表,就是所谓的数据索引。当查找数据索引时,如果没有找到对应的数据索引,这时就会一条select SQL的执行,获得符合条件的数据,并构造实体对象集合和数据索引,然后返回实体对象的集合,并且将实体对象和数据索引纳入Hibernate的缓存之中。另一方面,如果找到对应的数据索引,则从数据索引中取出id列表,然后根据id在缓存中查找对应的实体,如果找到就从缓存中返回,如果没有找到,在发起select SQL查询。在这里我们看出了另外一个问题,这个问题可能会对性能产生影响,这就是集合类型的缓存策略。如果我们如下配置集合类型:
<hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> ….. <set name=”addresses” table=”address” lazy=”true” inverse=”true”> <cache usage=”read-only”/> <key column=”user_id”/> <one-to-many class=”com.neusoft.entity.Arrderss”/> </set> </class> </hibernate-mapping>
这里我们应用了
User user=(User)session.load(User.class,”1”); Collection addset=user.getAddresses(); Iterator it=addset.iterator(); while(it.hasNext()){ Address address=(Address)it.next(); System.out.println(address.getAddress()); } System.out.println(“Second query……”); User user2=(User)session.load(User.class,”1”); Collection it2=user2.getAddresses(); while(it2.hasNext()){ Address address2=(Address)it2.next(); System.out.println(address2.getAddress()); }
运行这段代码,会得到类似下面的输出:
Select * from user where id='1'; Select * from address where user_id='1'; Tianjin Dalian Second query…… Select * from address where id='1'; Select * from address where id='2'; Tianjin Dalian
我们看到,当第二次执行查询时,执行了两条对address表的查询操作,为什么会这样?这是因为当第一次加载实体后,根据集合类型缓存策略的配置,只对集合数据索引进行了缓存,而并没有对集合中的实体对象进行缓存,所以在第二次再次加载实体时,Hibernate找到了对应实体的数据索引,但是根据数据索引,却无法在缓存中找到对应的实体,所以Hibernate根据找到的数据索引发起了两条select SQL的查询操作,这里造成了对性能的浪费,怎样才能避免这种情况呢?我们必须对集合类型中的实体也指定缓存策略,所以我们要如下对集合类型进行配置:
<hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> ….. <set name=”addresses” table=”address” lazy=”true” inverse=”true”> <cache usage=”read-write”/> <key column=”user_id”/> <one-to-many class=”com.neusoft.entity.Arrderss”/> </set> </class> </hibernate-mapping>
此时Hibernate会对集合类型中的实体也进行缓存,如果根据这个配置再次运行上面的代码,将会得到类似如下的输出:
Select * from user where id='1'; Select * from address where user_id='1'; Tianjin Dalian Second query…… Tianjin Dalian
这时将不会再有根据数据索引进行查询的SQL语句,因为此时可以直接从缓存中获得集合类型中存放的实体对象。
C、 属性延迟加载:
在Hibernate3中,引入了一种新的特性——属性的延迟加载,这个机制又为获取高性能查询提供了有力的工具。在前面我们讲大数据对象读取时,在User对象中有一个resume字段,该字段是一个java.sql.Clob类型,包含了用户的简历信息,当我们加载该对象时,我们不得不每一次都要加载这个字段,而不论我们是否真的需要它,而且这种大数据对象的读取本身会带来很大的性能开销。在Hibernate2中,我们只有通过我们前面讲过的面性能的粒度细分,来分解User类,来解决这个问题(请参照那一节的论述),但是在Hibernate3中,我们可以通过属性延迟加载机制,来使我们获得只有当我们真正需要操作这个字段时,才去读取这个字段数据的能力,为此我们必须如下配置我们的实体类:
<hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”> …… <property name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/> </class> </hibernate-mapping>
通过对
String sql=”from User user where user.name='zx' ”; Query query=session.createQuery(sql);
(1)
List list=query.list(); for(int i=0;i<list.size();i++){ User user=(User)list.get(i); System.out.println(user.getName()); System.out.println(user.getResume()); }
(2)
当执行到(1)处时,会生成类似如下的SQL语句:
Select id,age,name from user where name='zx';
这时Hibernate会检索User实体中所有非延迟加载属性对应的字段数据,当执行到(2)处时,会生成类似如下的SQL语句:
Select resume from user where id='1';
这时会发起对resume字段数据真正的读取操作。
更多浅析Java的Hibernate框架中的缓存和延迟加载机制相关文章请关注PHP中文网!