`

使用hibernate二级缓存优化你的应用

阅读更多

原文:http://www.devx.com/dbzone/article/29685/1954

作者:John Ferguson Smart

翻译及加工: 魏超

 

 

因为对hibernate缓存的不了解,新接触hiberante开发的人往往无法很好的使用它。然而,合理的使用缓存将成为加速hibernate程序的最有效途径。


 

 

频繁的数据库读写会影响web项目的性能表现。作为一个高性能的对象/关系持久化查询技术,单纯的使用hibernate可能还不能解决你所有的性能问题。很多时候,开启二级缓存将会很好的改变这种境况。下面的文章会让你对缓存有个初步的了解,同时告诉你怎么用缓存来提升性能。

 

 

什么是缓存(Cache)?

 

缓存被广泛应用的用于优化数据库。当一些数据被从数据库中读取出来的时候,我们可以把它们放到缓存里.这样,在再次用到这些数据的时候,我们就可以直接从缓存把他们取出来,而不是去连接数据库。当然,当数据库的记录被修改更新的时候,我们就需要把缓存清空掉。因为我们无从得知在数据库记录更新时,缓存中的记录是否还和数据库里的相同

 

 

Hibernate的缓存

Hibernate有一级和二级两种缓存对象。一级缓存关联session对象,而二级缓存关联着session工厂对象。默认情况下,一级缓存是在单个事务中使用的。举例来说,在同一个事务中,当一个对象在事务提交前,被修改了很多次,那么同过一级缓存,在事务提交的时候,我们就会把所有这些修改写在同一条SQL语句中传递给数据库,而不是每次修改都有一条语句。当然,我们这篇文章关注的是二级缓存。可以这么说,相比一级缓存,二级缓存是跨事务的,它将一个事务中产生的查询对象保存下来,在其他事务执行相同的查询的时候,这些被保存的对象就可以被直接拿出来使用,这样就能最大化的减少数据库操作。因此,对于同一个服务,只要有一个用户执行了某个查询,那么其他将要执行相同查询的用户都将从二级缓存中受益。

 

此外,相对于上面说的缓存持久化对象,你可以使用query-level的缓存来存储真正的查询结果集

 

 

缓存的实现

市场上提供了相当多的缓存技术的选择,既有开源的也有收费的。 Hibernate支持下面的开源缓存:

  • EHCache (org.hibernate.cache.EhCacheProvider)
  • OSCache (org.hibernate.cache.OSCacheProvider)
  • SwarmCache (org.hibernate.cache.SwarmCacheProvider)
  • JBoss TreeCache(org.hibernate.cache.TreeCacheProvider)

以下是不同的缓存产品的特点:

  • EHCache  是一个快速,轻巧,易于使用的线程内的高速缓存。它支持只读和读/写缓存,内存和基于磁盘的缓存。但是,它不支持群集。
  • OSCache 是另一个开源缓存解决方案,也提供对JSP页面和任意对象的缓存功能。它是一个强大和灵活的方案,如同EHCache,支持只读和读/写缓存,内存和基于磁盘的缓存。它还提供对JavaGroup或JMS集群的基础支持。
  • SwarmCache 是一个基于JavaGroup的简单集群缓存解决方案。它支持只读或不严格的读/写缓存(接下来的部分解释这个词)。此缓存的适用于读操作远多于写操作的数据库应用。
  • JBoss TreeCache的 是一个强大的复制(同步或异步)事务缓存。如果你需要一个真正有事务能力的缓存架构,选择这个缓存。

另一个值得一提的是商业缓存Tangosol: Tangosol的高速缓存的一致性.

 

 

缓存策略

一旦选择了缓存实现,你还需要指定你的访问策略。以下四个缓存策略可供选择:

  • 只读:用于只读取而不写入的访问策略。这是迄今为止最简单,效果最好的缓存策略。
  • 读/写:如果你有数据需要写入(更新),可以用这个缓存。当然,读/写缓存的开销要比只读缓存大。在非JTA的环境中,每次操作应在Session.close()或者Session.disconnect()被结束。
  • 非严格的读/写:这种策略并不能保证两个操作修改同一个数据的安全。因此,它适用于经常查询,而只是偶尔的修改的数据。
  • 事务性:这是一个完全的事务缓存,可用于JTA环境。

下表显示了可供选择的不同的缓存实现:

 

缓存 只读 非严格的读/写 读/写 事务
EHCache 是的 是的 是的 没有
OSCache 是的 是的 是的 没有
SwarmCache 是的 是的 没有 没有
JBoss TreeCache 是的 没有 没有 是的


下面的部分将展示在JVM中使用EHCache缓存。

 

 

缓存配置

要激活二级缓存,首先你需要定义hibernate.cfg.xml文件的hibernate.cache.provider_class:

 

 

<hibernate-configuration>
	<session-factory>
		...
		<property name="hibernate.cache.provider_class">
			org.hibernate.cache.EHCacheProvider
		</property>
		...
	</session-factory>
</hibernate-configuration>

 

 

当你Hibernate版本是3以上时,你可能还需要使用hibernate.cache.use_second_level_cache属性。这个属性让你可以激活(或停止)二级缓存。默认情况下,二级缓存使用的是EHCache并且已经激活。

 

一个例子

这个例子由几个简单的表组成:Airport, Employee, Language,Country. 每个employee属于一个country,会说多国的language. 而每个country有许多的airport. 下面2幅图分别是这4个类的类图和数据库表图。

 




 
 

设置一个只读缓存

首先设置国家(Country)的Hibernate映射类:

 

<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
    <class name="Country" table="COUNTRY" dynamic-update="true">
		<meta attribute="implement-equals">true</meta>    
 		<cache usage="read-only"/>

        <id name="id" type="long" unsaved-value="null" >
            <column name="cn_id" not-null="true"/>
            <generator class="increment"/>
        </id>

	   <property column="cn_code" name="code" type="string"/>
	   <property column="cn_name" name="name" type="string"/>

	  <set name="airports">
	   <key column="cn_id"/>
	   <one-to-many class="Airport"/>
	  </set>
    </class>
</hibernate-mapping>

 

 

如果这时候你要取所有的country列表,你可以在CountryDao中添加如下方法:

 

public class CountryDAO {
	...	
	public List getCountries() {
		return SessionManager.currentSession()
					   .createQuery(
					      "from Country as c order by c.name")
					   .list();
	}
}

 

 

 

因为上面的方法可能会被频繁的调用,所以我们需要了解它在压力下的性能表现。这里我们写一个单元测试来模拟5次成功的调用:

 

public void testGetCountries() {
		CountryDAO dao = new CountryDAO();
		for(int i = 1; i <= 5; i++) {
  		    Transaction tx = SessionManager.getSession().beginTransaction();
		    TestTimer timer = new TestTimer("testGetCountries");
		    List countries = dao.getCountries();
		    tx.commit();
		    SessionManager.closeSession();
		    timer.done();
		    assertNotNull(countries);
		    assertEquals(countries.size(),229);
		}
	}

 

 

你可以用你熟悉的IDE或者用Maven2的命令行来运行上面的代码。在这里我们连接了一个本地的MySQL数据库。当这段代码被成功的运行,你应该会看到下面的输出:

 

testGetCountries: 521 ms.
testGetCountries: 357 ms.
testGetCountries: 249 ms.
testGetCountries: 257 ms.
testGetCountries: 355 ms.

 

 

每次操作基本上要用4分之1秒。在多数情况下,上面我们取得的国家列表不会被频繁的修改。因此这是一个很好的例子来引入只读缓存(read-only Cache).

 

有2种方法可以激活二级缓存的类:

 

1. 在要使用二级缓存的那个类对应的hbm.xml文件中添加下面的属性:

<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
         <class name="Country" table="COUNTRY" dynamic-update="true">
		<meta attribute="implement-equals">true</meta>
		<cache usage="read-only"/>
            ...			        
        </class>
    </hibernate-mapping>

2. 或者把所有的缓存信息都记录在hibernate.cfg.xml文件中:

 

<hibernate-configuration>
	<session-factory>
		...
		<property name="hibernate.cache.provider_class">
			org.hibernate.cache.EHCacheProvider
		</property>
		...
		<class-cache 
class="com.wakaleo.articles.caching.businessobjects.Country"
usage="read-only"
		/>
	</session-factory>
</hibernate-configuration>

 

接下来,你需要配置这个类的缓存规则。这些规则会决定缓存的一些细节。我们在这个例子中用的是EHChe缓存。当然,在其他不同的缓存中,配置规则的方式是不一样的。

EHCache需要一个配置文件(在类路径的根目录,一般称为ehcache.xml)。在下面这个站点有这个文件的模板: EHCache模板 。基本上,你要为每一个你想做二级缓存的类在这个文件中配置规则。如果你没有配置,程序将调用一个默认的规则。

对于这个例子,我们可以使用下面的简单EHCache配置文件:

<ehcache>

    <diskStore path="java.io.tmpdir"/>

    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU"
        />
        
    <cache name="com.wakaleo.articles.caching.businessobjects.Country"
        maxElementsInMemory="300"
        eternal="true"
        overflowToDisk="false"
        />

</ehcache>

 

这个文件基本上建立了一个国家基于内存的缓存,最多300个元素(国家清单包含229个国家)。请注意,缓存永不过期(即'永恒=真正的财产)。

这个配置建立了一个基于内存的country缓存。里面最多可以保存300个元素(在我们这个例子中,查询出的结果集包含229个country).(注:这个缓存被配置为永远不过期。eternal=true)

现在,重新运行测试,结果如下:

 

testGetCountries: 412 ms.
testGetCountries: 98 ms.
testGetCountries: 92 ms.
testGetCountries: 82 ms.
testGetCountries: 93 ms.

 

可以明显的看出,操作时间提高到了10分之1秒左右(第一次用了400多ms是因为第一次真正和数据库交互了)

 

缓存内部的存储

在继续下去之前,我们有必要先来了解在上面的代码背后发生了什么。值得注意的是,hibernate缓存并不存储对象的实例(Instances).实际上它存储的是对象的“dehydrated”格式(hibernate术语),这种格式其实就是一系列的属性值。下面是country缓存中内容的一个例子:

 

{ 
  30  => [bw,Botswana,30], 
  214 => [uy,Uruguay,214], 
  158 => [pa,Panama,158],
  31  => [by,Belarus,31]
  95  => [in,India,95]
  ...
}

 注意,每一个Id映射到一系列的属性上。这里你可能会发现,只有原始的属性被缓存下来了,具体来说,airport属性没有被缓存。实际上,这是因为airport属性是一个association:是一系列指向其他持久化对象的引用。

 

默认情况下,hibernate不会对associations进行缓存。当然,你可以设置是否缓存association,同时可以设置当二级缓存中的对象被重新加载出来的时候哪些associations要被检索。

 

Association缓存是一个非常强大的功能。下面我们将对它进行更详细的研究。

 

 

使用Associations缓存

假设你需要获得一个country对应的所有employees的列表(包括employee名字, language等),你需要添加下面的映射配置:

 

<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
    <class name="Employee" table="EMPLOYEE" dynamic-update="true">
		<meta attribute="implement-equals">true</meta>    

       <id name="id" type="long" unsaved-value="null" >
            <column name="emp_id" not-null="true"/>
            <generator class="increment"/>
       </id>

	 <property column="emp_surname" name="surname" type="string"/>
	 <property column="emp_firstname" name="firstname" type="string"/>
	   
	 <many-to-one name="country"
 	              column="cn_id"
	              class="com.wakaleo.articles.caching.businessobjects.Country"  
			  not-null="true" />
			    
	 <!-- Lazy-loading is deactivated to demonstrate caching behavior -->    
	 <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false">
    	 <key column="emp_id"/>
      	    	<many-to-many column="lan_id" class="Language"/>
	 </set>				        
    </class>
</hibernate-mapping>

 假如你真的需要在每次取得employee对象的时候都加载其对应的language信息,你需要把language属性的lazy设置为false.(不延迟加载)(这里只是为了举例的需要,在真实情况下,不建议取消延迟加载,因为这会影响性能)。同时,你需要添加下面的方法来获得employee:

 

public class EmployeeDAO {

	public List getEmployeesByCountry(Country country) {
		return SessionManager.currentSession()
		 .createQuery(
		      "from Employee as e where e.country = :country "
                + " order by e.surname, e.firstname")
		 .setParameter("country",country)
		 .list();
	}
}

 

接着,写一个单元测试:

 

public class EmployeeDAOTest extends TestCase {

	CountryDAO countryDao = new CountryDAO();
	EmployeeDAO employeeDao = new EmployeeDAO();

	/**
	 * Ensure that the Hibernate session is available
	 * to avoid the Hibernate initialisation interfering with
	 * the benchmarks
	 */
	protected void setUp() throws Exception {		
		super.setUp();
		SessionManager.getSession();
	}

	public void testGetNZEmployees() {
		TestTimer timer = new TestTimer("testGetNZEmployees");
		Transaction tx = SessionManager.getSession().beginTransaction();
		Country nz = countryDao.findCountryByCode("nz");
		List kiwis = employeeDao.getEmployeesByCountry(nz);
		tx.commit();
		SessionManager.closeSession();
		timer.done();
	}

	public void testGetAUEmployees() {
		TestTimer timer = new TestTimer("testGetAUEmployees");
		Transaction tx = SessionManager.getSession().beginTransaction();
		Country au = countryDao.findCountryByCode("au");
		List aussis = employeeDao.getEmployeesByCountry(au);	
		tx.commit();
		SessionManager.closeSession();
		timer.done();
	}

	public void testRepeatedGetEmployees() {
		testGetNZEmployees();
		testGetAUEmployees();
		testGetNZEmployees();
		testGetAUEmployees();
	}
}

 

运行这个单元测试,可以看到下面的结果:

 

testGetNZEmployees: 1227 ms.
testGetAUEmployees: 883 ms.
testGetNZEmployees: 907 ms.
testGetAUEmployees: 873 ms.
testGetNZEmployees: 987 ms.
testGetAUEmployees: 916 ms.

可以看出,每次为一个country取得50几个employee都需要差不多1秒钟 。这样的速度太慢了。这是一个传统的"N+1"问题。如果启用SQL日志,我们可以看到,每次执行一个employee的查询语句后面,都跟着数百条language表的查询。每次我们从缓存中加载employee对象,其关联的全部language都会被重新检索。我们应该怎么改善这里的性能呢?首先,激活employee类的读写缓存(read/write cache):

 

<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
        <class name="Employee" table="EMPLOYEE" dynamic-update="true">
		<meta attribute="implement-equals">true</meta>
		<cache usage="read-write"/>
            ...			        
        </class>
    </hibernate-mapping>

同时,我们要激活language上的只读缓存(read-only cache):

 

<class name="Language" table="SPOKEN_LANGUAGE" dynamic-update="true">
		<meta attribute="implement-equals">true</meta>    
		<cache usage="read-only"/>
            ...			        
        </class>
    </hibernate-mapping>

 之后,在ehcache.xml文件中添加下面2个缓存规则:

 

<cache name="com.wakaleo.articles.caching.businessobjects.Employee"
        maxElementsInMemory="5000"
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
    />
    <cache name="com.wakaleo.articles.caching.businessobjects.Language"
        maxElementsInMemory="100"
        eternal="true"
        overflowToDisk="false"
    />

 到这里,"N+1"问题依然没有被解决:每次加载employee,50条以上的查询依旧会被执行。因为,我们还需要激活employee.hbm.xml文件中的language的association.

 

<hibernate-mapping package="com.wakaleo.articles.caching.businessobjects">
    <class name="Employee" table="EMPLOYEE" dynamic-update="true">
		<meta attribute="implement-equals">true</meta>    

      <id name="id" type="long" unsaved-value="null" >
            <column name="emp_id" not-null="true"/>
            <generator class="increment"/>
      </id>

	<property column="emp_surname" name="surname" type="string"/>
	<property column="emp_firstname" name="firstname" type="string"/>
	   
	<many-to-one name="country"
 	  		 column="cn_id"
 		       class="com.wakaleo.articles.caching.businessobjects.Country"  
			not-null="true" />
			    
	<!-- Lazy-loading is deactivated to demonstrate caching behavior -->    
      <set name="languages" table="EMPLOYEE_SPEAKS_LANGUAGE" lazy="false">
	    <cache usage="read-write"/>
   	    <key column="emp_id"/>
    	    <many-to-many column="lan_id" class="Language"/>
	</set>    					        
    </class>
</hibernate-mapping>

 这样配置之后,我们可以看到下面的结果(速度提高了10倍左右):

 

testGetNZEmployees: 1477 ms.
testGetAUEmployees: 940 ms.
testGetNZEmployees: 65 ms.
testGetAUEmployees: 65 ms.
testGetNZEmployees: 76 ms.
testGetAUEmployees: 52 ms.

 

 

使用查询缓存(Query Caches)

在某些情况下,我们需要缓存的是确切的查询结果集,而不是某些对象。例如,每次调用getCountries()方法的时候,我们都会得到相同的country列表。因此,除了缓存country类,我们还需要缓存查询结果集本身。

 

为了实现这个,我们需要启用hibernate.cfg.xml文件中的hibernate.cache.use_query_cache属性:

 

<property name="hibernate.cache.use_query_cache">true</property>

接着,在你需要缓存查询结果的地方使用setCacheable()方法:

 

public class CountryDAO {

    public List getCountries() {
        return SessionManager.currentSession()
                             .createQuery("from Country as c order by c.name")
				     .setCacheable(true)
                             .list();
    }
}

 为了保证缓存结果的正确性,每当被缓存的数据在应用中被修改的时候,这些查询缓存的结果在Hibernate中就过期了(hibernate会重新刷新这部分缓存)。然而,hibernate却无法获悉本身应用之外的,其他应用直接去修改数据库数据的操作。因此,如果你使用的数据会频繁的处于提交更新的状态下,你就不应该使用任何的二级缓存。如果非要使用的话,至少,你应该将二级缓存的超时时间设置的足够短。  

 

正确的Hibernate缓存

缓存是一个强大的技术,Hibernate提供了一个有效,灵活且过度缓和的方式来实现它。即使是默认的配置也可以在许多简单的应用中有效的提高性能。然而,如同其他强大的工具一样,hibernate需要更深入的思考和微调,来取得最佳的效果。而缓存——如同其他的优化技术——应该遵循增量的,测试驱动的方法。当合理使用的时候,少量的缓存就可以使你程序发挥出最大的效能。

 

 

  • 大小: 24.9 KB
  • 大小: 54 KB
分享到:
评论
2 楼 nneverwei 2011-01-05  
tangwenchao86 写道
写得真好,你都是在哪看到的啊?

是我翻译及修改的,原文英文地址在本文开头的那个链接里
1 楼 tangwenchao86 2010-12-30  
写得真好,你都是在哪看到的啊?

相关推荐

    Hibernate3性能优化 Hibernate_regerence3.12

    有很多人认为Hibernate天生效率比较低,确实,在...特别是应用二级缓存之后,甚至可以获得比较不使用缓存的JDBC更好的性能,下面介绍一些通常的 Hibernate的优化策略: 1.抓取优化 2.二级缓存 3.批量数据操作 4.杂项

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     22.4.3 在应用程序中管理第二级缓存  22.4.4 Session与第二级缓存的交互模式  22.5 运行本章的范例程序  22.6 小结  22.7 思考题 第23章 管理Session和实现对话  23.1 管理Session对象的生命周期  23.1.1 ...

    Secode_level_cache.zip

    早在2008年开始,我们就借鉴了Java强大的ORM 框架Hibernate的二级对象缓存编写了这个Rails的AR对象缓存插件,并且一直作为JavaEye网站缓存优化的秘密武器来使用,取得了非常理 想的效果。 现在我们将这个插件从...

    hibernate优化

    确实,在普遍情况下,需要将执行转换为SQL语句的Hibernate的效率低于直接JDBC 存取,然而,在经过比较好的性能优化之后,Hibernate的性能还是让人相当满意的,特别是应用二级缓存之后,甚至可以获得比较不使用缓存的...

    Hibernate实战(第2版 中文高清版)

     13.4.5 控制二级高速缓存   13.5 小结   第14章 利用HQL和JPA QL查询   14.1 创建和运行查询   14.1.1 准备查询   14.1.2 执行查询   14.1.3 使用具名查询   14.2 基本的HQL和JPA QL查询   14.2.1...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     22.4.3 在应用程序中管理第二级缓存  22.4.4 Session与第二级缓存的交互模式  22.5 运行本章的范例程序  22.6 小结  22.7 思考题 第23章 管理Session和实现对话  23.1 管理Session对象的生命周期  23.1.1 ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     22.4.3 在应用程序中管理第二级缓存  22.4.4 Session与第二级缓存的交互模式  22.5 运行本章的范例程序  22.6 小结  22.7 思考题 第23章 管理Session和实现对话  23.1 管理Session对象的生命周期  23.1.1 ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     22.4.3 在应用程序中管理第二级缓存  22.4.4 Session与第二级缓存的交互模式  22.5 运行本章的范例程序  22.6 小结  22.7 思考题 第23章 管理Session和实现对话  23.1 管理Session对象的生命周期  23.1.1 ...

    mysql数据优化详细教程

    因此可以部分数据从数据库中抽取出来放到应用端以文本方式存储, 或者使用框架(Mybatis, Hibernate)提供的一级缓存/二级缓存,或者使用redis数据库来缓存数据 。负载均衡是应用中使用非常普遍的一种优化方法,它的...

    低清版 大型门户网站是这样炼成的.pdf

    5.8.4 hibernate二级缓存的并发访问策略 333 5.8.5 hibernate的二级缓存配置 334 5.9 hibernate应用的性能优化 336 5.10 多数据源的应用 338 5.11 jdbc的应用 343 5.12 hibernate调用存储过程 343 5.13 xml...

    Java Web程序设计教程

    10.2.3应用二级缓存 214 10.2.4应用第三方缓存 216 10.3项目实战——借还图书 217 本章小结 224 课后练习 224 第11章spring框架基础 226 11.1spring框架概述 226 11.1.1认识spring框架 226 11.1.2spring框架...

    lamp-cloud微服务脚手架

    采用J2Cache操作缓存,第一级缓存使用内存(Caffeine),第二级缓存使用 Redis。 由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 该缓存框架主要用于集群环境中。...

    hibernate_reference中文文档.pdf

    3.4.4. 二级缓存与查询缓存 ............................................. 43 3.4.5. 查询语言中的替换 ............................................... 43 3.4.6. Hibernate 的统计(statistics)机制 ...........

    Spring面试题

    7. 表字段要少,表关联不要怕多,有二级缓存撑腰 7. Struts工作机制?为什么要使用Struts? 工作机制: Struts的工作流程: 在web应用启动时就会加载初始化ActionServlet,ActionServlet从 struts-config.xml文件中...

    网站设计方案(完整版).doc

    Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在 Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架 构中取代CMP,完成数据持久化的重任。...

    java面试题库2021.pdf

    ④二级缓存与查询缓存 3、 Struts ①MVC 模式与 Struts 体系 4、 mybatis 5、 MVC 框架 6、 各框架对比与项目优化 7、 JPA ①EJB 三、 Java web 开发核心内容 1、 web 编程基础 ①Tomcat 服务器NOWCODER.COM 牛客网...

    java必了解的六大问题

    *第十四阶段:Hibernate框架学习,三大框架之一,包括检索映射技术,多表查询技术,缓存技术以及性能方面的优化; *第十五阶段:Spring框架的学习,三大框架之一,包括了IOC,AOP,DataSource,事务,SSH集成以及...

    Java常见面试题208道.docx

    130.说一下 mybatis 的一级缓存和二级缓存? 131.mybatis 和 hibernate 的区别有哪些? 132.mybatis 有哪些执行器(Executor)? 133.mybatis 分页插件的实现原理是什么? 134.mybatis 如何编写一个自定义插件? 十...

    java面试题

    二级缓存: 56 71.4.3. 缓存管理 56 71.5. Hibernate 中Java对象的状态 58 71.5.1. 临时状态 (transient) 58 71.5.2. 持久化状态(persisted) 58 71.5.3. 游离状态(detached) 58 71.5.4. hibernate的三种状态之间...

Global site tag (gtag.js) - Google Analytics