hibernate 4

第23课 性能优化策略

1、  注意session.clear()的动用,尤其在不断分页循环的时候

a)        在一个大集合中进行遍历,遍历msg,取出其中的含有敏感字样的对象

b)        另外一种形式的内存泄露 //面试是:Java有内存泄漏吗?

2、  1 + N问题 //典型的面试题

a)        Lazy

b)        BatchSize   设置在实体类的前面

c)         joinfetch

3、  list 和 iterate不同之处

a)        list取所有

b)        Iterate先取ID,等用到的时候再根据ID来取对象

c)         session中list第二次发出,仍会到数据库查询

d)        iterate第二次,首先找session级缓存

 

第24课 hibernate缓存

一、 Session级缓存(一级缓存)

一级缓存很短和session的生命周期一致,因此也叫session级缓存或事务级缓存

         hibernate一级缓存

        

那些方法支持一级缓存:

         * get()

         * load()

         * iterate(查询实体对象)

        

如何管理一级缓存:

         * session.clear(),session.evict()

        

如何避免一次性大量的实体数据入库导致内存溢出

         * 先flush,再clear

        

如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具

 

二、 二级缓存

Hibernate默认的二级缓存是开启的。

         二级缓存也称为进程级的缓存,也可称为SessionFactory级的缓存(因为SessionFactory可以管理二级缓存),它与session级缓存不一样,一级缓存只要session关闭缓存就不存在了。而二级缓存则只要进程在二级缓存就可用。

         二级缓存可以被所有的session共享

         二级缓存的生命周期和SessionFactory的生命周期一样,SessionFactory可以管理二级缓存

         二级缓存同session级缓存一样,只缓存实体对象,普通属性的查询不会缓存

         二级缓存一般使用第三方的产品,如EHCache

 

1、   二级缓存的配置和使用:

         配置二级缓存的配置文件:模板文件位于hibernate\etc目录下(如ehcache.xml),将模板存放在ClassPath目录中,一般放在根目录下(src目录下)

<ehcache>

    <!--设置当缓存对象益出时,对象保存到磁盘时的保存路径。

            如 d:\xxxx

         The following properties aretranslated:

         user.home - User's home directory

         user.dir - User's current workingdirectory

         java.io.tmpdir - windows的临时目录 -->

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

 

    <!--默认配置/或对某一个类进行管理

        maxInMemory       - 缓存中可以存入的最多个对象数

        eternal           - true:表示永不失效,false:不是永久有效的。

        timeToIdleSeconds - 空闲时间,当第一次访问后在空闲时间内没有访问,则对象失效,单位为秒

        timeToLiveSeconds - 被缓存的对象有效的生命时间,单位为秒

      overflowToDisk  当缓存中对象数超过核定数(益出时)时,对象是否保存到磁盘上。true:保存;false:不保存

                         如果保存,则保存路径在标签<diskStore>中属性path指定

        -->

    <defaultCache

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="120"

        timeToLiveSeconds="120"

        overflowToDisk="true"

        />      

</ehcache>

 

2、   二级缓存的开启:

         Hibernate中二级缓存默认就是开启的,也可以显示的开启

         二级缓存是hibernate的配置文件设置如下:

<!--开启二级缓存,hibernate默认的二级缓存就是开启的 -->

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

 

3、   指定二级缓存产品提供商:

         修改hibernate的 配置文件,指定二级缓存提供商,如下:

<!--指定二级缓存提供商-->

<property name="hibernate.cache.provider_class">

org.hibernate.cache.EhCacheProvider

</property>

以下为常见缓存提供商:

Cache

Provider class

Type

Cluster Safe

Query Cache Supported

Hashtable (not intended for production use)

org.hibernate.cache.HashtableCacheProvider

memory

 

yes

EHCache

org.hibernate.cache.EhCacheProvider

memory, disk

 

yes

OSCache

org.hibernate.cache.OSCacheProvider

memory, disk

 

yes

SwarmCache

org.hibernate.cache.SwarmCacheProvider

clustered (ip multicast)

yes (clustered invalidation)

 

JBoss TreeCache

org.hibernate.cache.TreeCacheProvider

clustered (ip multicast), transactional

yes (replication)

yes (clock sync req.)

4、   使用二级缓存

a)       xml方式:指定哪些实体类使用二级缓存:

         方法一:在实体类映射文件中,使用<cache>来指定那个实体类使用二级缓存,如下:

<cache 
    usage="transactional|read-write|nonstrict-read-write|read-only"  (1)
    region="RegionName"                                              (2)
    include="all|non-lazy"                                           (3)
/>

(1)   usage(必须)说明了缓存的策略: transactionalread-writenonstrict-read-writeread-only

(2)   region (可选, 默认为类或者集合的名字(class orcollection role name)) 指定第二级缓存的区域名(name of the secondlevel cache region)

(3)   include (可选,默认为 all) non-lazy 当属性级延迟抓取打开时, 标记为lazy="true"的实体的属性可能无法被缓存

另外(首选?), 你可以在hibernate.cfg.xml中指定<class-cache><collection-cache> 元素。

这里的usage 属性指明了缓存并发策略(cache concurrency strategy)

 策略:只读缓存(Strategy:read only)

如果你的应用程序只需读取一个持久化类的实例,而无需对其修改, 那么就可以对其进行只读 缓存。这是最简单,也是实用性最好的方法。甚至在集群中,它也能完美地运作。

<class name="eg.Immutable" mutable="false">
    <cache usage="read-only"/>
    ....
</class>

策略:读/写缓存(Strategy:read/write)

如果应用程序需要更新数据,那么使用/写缓存 比较合适。 如果应用程序要求“序列化事务”的隔离级别(serializable transaction isolation level),那么就决不能使用这种缓存策略。 如果在JTA环境中使用缓存,你必须指定hibernate.transaction.manager_lookup_class属性的值, 通过它,Hibernate才能知道该应用程序中JTA的TransactionManager的具体策略。 在其它环境中,你必须保证在Session.close()、或Session.disconnect()调用前, 整个事务已经结束。 如果你想在集群环境中使用此策略,你必须保证底层的缓存实现支持锁定(locking)。Hibernate内置的缓存策略并不支持锁定功能。

<class name="eg.Cat" .... >
    <cache usage="read-write"/>
    ....
    <set name="kittens" ... >
        <cache usage="read-write"/>
        ....
    </set>
</class>

策略:非严格读/写缓存(Strategy: nonstrictread/write)

如果应用程序只偶尔需要更新数据(也就是说,两个事务同时更新同一记录的情况很不常见),也不需要十分严格的事务隔离,那么比较适合使用非严格读/写缓存策略。如果在JTA环境中使用该策略,你必须为其指定hibernate.transaction.manager_lookup_class属性的值, 在其它环境中,你必须保证在Session.close()、或Session.disconnect()调用前, 整个事务已经结束。

策略:事务缓存(transactional)

Hibernate的事务缓存策略提供了全事务的缓存支持, 例如对JBoss TreeCache的支持。这样的缓存只能用于JTA环境中,你必须指定为其hibernate.transaction.manager_lookup_class属性。

没有一种缓存提供商能够支持上列的所有缓存并发策略。下表中列出了各种提供器、及其各自适用的并发策略。

表 19.2.  各种缓存提供商对缓存并发策略的支持情况(Cache Concurrency Strategy Support)

Cache

read-only

nonstrict-read-write

read-write

transactional

Hashtable (not intended for production use)

yes

yes

yes

 

EHCache

yes

yes

yes

 

OSCache

yes

yes

yes

 

SwarmCache

yes

yes

 

 

JBoss TreeCache

yes

 

 

yes

 

注:此方法要求:必须要标签<cache>放在<id>标签之前

 

<class name="com.wjt276.hibernate.Student"table="t_student">

        <!--  指定实体类使用二级缓存 -->

        <cache usage="read-only"/>//***********

        <id name="id"column="id">

            <generator class="native"/>

        </id>

        <property name="name"column="name"/>

        <!--

            使用多对一标签映射 一对多双向,下列的column值必需与多的一端的key字段值一样。

         -->

        <many-to-one name="classes"column="classesid"/>

    </class>

 

         方法二:在hibernate配置文件(hibernate.cfg.xml)使用<class-cache>标签中指定

                   要求:<class-cache>标签必须放在<maping>标签之后。

<hibernate-configuration>

    <session-factory>  

        …………

<mapping resource="com/wjt276/hibernate/Classes.hbm.xml"/>

        <mapping resource="com/wjt276/hibernate/Student.hbm.xml"/>

       

        <class-cache class="com.wjt276.hibernate.Student"usage="read-only"/>

    </session-factory>

</hibernate-configuration>

 

    一般推荐使用方法一。

b)       annotation注解

为了优化数据库访问,你可以激活所谓的Hibernate二级缓存.该缓存是可以按每个实体和集合进行配置的.

@org.hibernate.annotations.Cache定义了缓存策略及给定的二级缓存的范围. 此注解适用于根实体(非子实体),还有集合.

@Entity

@Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

publicclass Forest { ... }

    @OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)

    @JoinColumn(name="CUST_ID")

    @Cache(usage =CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

    public SortedSet<Ticket> getTickets(){

        return tickets;

    }

@Cache(

    CacheConcurrencyStrategy usage();                 (1)

    String region() default "";                       (2)

    String include() default"all";                   (3)

)

(1)

usage: 给定缓存的并发策略(NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL)

(2)

region (可选的):缓存范围(默认为类的全限定类名或是集合的全限定角色名)

(3)

include (可选的):值为all时包括了所有的属性(proterty), 为non-lazy时仅含非延迟属性(默认值为all)

 

5、   应用范围

     没有变化,近似于静态的数据。

 

6、   二级缓存的管理:

1、   清除指定实体类的所有数据

SessionFactory.evict(Student.class);

2、   清除指定实体类的指定对象

SessionFactory.evict(Student.class, 1);//第二个参数是指定对象的ID,就可以清除指定ID的对象

使用SessionFactory清除二级缓存

Sessionsession = null;

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            Student student = (Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

       

        //管理二级缓存

        SessionFactory factory = HibernateUtils.getSessionFactory();

        //factory.evict(Student.class);

        factory.evict(Student.class, 1);

       

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //会发出查询sql,因为二级缓存中的数据被清除了

            Student student = (Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

    }

 

7、   二级缓存的交互

Sessionsession = null;

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //仅向二级缓存读数据,而不向二级缓存写数据

            session.setCacheMode(CacheMode.GET);

            Student student =(Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

       

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //发出sql语句,因为session设置了CacheMode为GET,所以二级缓存中没有数据

            Student student =(Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

       

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //只向二级缓存写数据,而不从二级缓存读数据

            session.setCacheMode(CacheMode.PUT);

           

            //会发出查询sql,因为session将CacheMode设置成了PUT

            Student student = (Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

CacheMode参数用于控制具体的Session如何与二级缓存进行交互。

·                                CacheMode.NORMAL - 从二级缓存中读、写数据。

·                                CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。

·                                CacheMode.PUT - 仅向二级缓存写数据,但不从二级缓存中读数据。

·                                CacheMode.REFRESH - 仅向二级缓存写数据,但不从二级缓存中读数据。通过hibernate.cache.use_minimal_puts的设置,强制二级缓存从数据库中读取数据,刷新缓存内容。

如若需要查看二级缓存或查询缓存区域的内容,你可以使用统计(Statistics API。

Map cacheEntries = sessionFactory.getStatistics()
        .getSecondLevelCacheStatistics(regionName)
        .getEntries();

此时,你必须手工打开统计选项。可选的,你可以让Hibernate更人工可读的方式维护缓存内容。

hibernate.generate_statistics true
hibernate.cache.use_structured_entries true

8、   总结

load默认使用二级缓存,iterate默认使用二级缓存

list默认向二级缓存中加数据,但是查询时候不使用

三、 查询缓存

查询缓存,是用于缓存普通属性查询的,当查询实体时缓存实体ID。
默认情况下关闭,需要打开。查询缓存,对list/iterator这样的操作会起作用。
可以使用<property name=”hibernate.cache.use_query_cache”>true</property>来打开查询缓存,默认为关闭。
所谓查询缓存:即让hibernate缓存list、iterator、createQuery等方法的查询结果集。如果没有打开查询缓存,hibernate将只缓存load方法获得的单个持久化对象。
在打开了查询缓存之后,需要注意,调用query.list()操作之前,必须显式调用query.setCachable(true)来标识某个查询使用缓存。
查询缓存的生命周期:当前关联的表发生修改,那么查询缓存生命周期结束
注意查询缓存依赖于二级缓存,因为使用查询缓存需要打开二级缓存

查询缓存的配置和使用:

    * 在hibernate.cfg.xml文件中启用查询缓存,如:

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

    * 在程序中必须手动启用查询缓存,如:

         query.setCacheable(true);
 
例如:

session= HibernateUtils.getSession();

            session.beginTransaction();

            Query query = session.createQuery("selects.name from Student s");

            //启用查询查询缓存

            query.setCacheable(true);

           

            List names = query.list();

            for (Iteratoriter=names.iterator();iter.hasNext(); ) {

                String name =(String)iter.next();

                System.out.println(name);

            }

            System.out.println("-------------------------------------");

            query = session.createQuery("selects.name from Student s");

            //启用查询查询缓存

            query.setCacheable(true);

           

            //没有发出查询sql,因为启用了查询缓存

            names = query.list();

            for (Iteratoriter=names.iterator();iter.hasNext(); ) {

                String name =(String)iter.next();

                System.out.println(name);

            }

         session.getTransaction().commit();
 

Session session = sf.openSession();

        session.beginTransaction();

        List<Category>categories = (List<Category>)session.createQuery("from Category")

                                    .setCacheable(true).list();

        session.getTransaction().commit();

        session.close();

       

        Session session2 = sf.openSession();

        session2.beginTransaction();

        List<Category> categories2= (List<Category>)session2.createQuery("from Category")

        .setCacheable(true).list();

       

        session2.getTransaction().commit();

         session2.close();
 
注:查询缓存的生命周期与session无关。
查询缓存只对query.list()起作用,query.iterate不起作用,也就是query.iterate不使用

四、 缓存算法

1、  LRU、LFU、FIFO

 


———————————————————-----------------------------------------------------------------------------

解决1+N问题

package com.demo.hibernate;

import java.util.Date;
import java.util.List;

import javax.persistence.FetchType;
import javax.persistence.ManyToOne;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.BatchSize;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Restrictions;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class HibernateTreeTest {
	private static SessionFactory sf = null;
	@BeforeClass
	public static void beforClass() {  
//		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句	
		try {
					sf = new AnnotationConfiguration().configure().buildSessionFactory();
				} catch (Exception e) {
					e.printStackTrace();
				}
	}
	@AfterClass
	public static void afterClass() {
		sf.close();
	}
	
	@Test
	public void testSchemaExport() {  //生成建表语句
		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句
	}
	
	@Test
	public void testSave() {
		Session session = sf.openSession();
		session.beginTransaction();
		// 存入10个板块
		for(int i=0; i<10; i++) {
			Category c = new Category();
			c.setName("c"+i);
			Topic t = new Topic();
			t.setCategory(c);
			t.setCreateDate(new Date());
			t.setTitle("t" + i);
			session.save(c);
			session.save(t);
		}
		session.getTransaction().commit();
		session.close();
	}
	
	
	
	

	//1+N 关联
	@Test
	public void testQuery1() {
		Session session = sf.openSession();
		session.beginTransaction();
//		List<Topic> topics = (List<Topic>)session.createCriteria(Topic.class).list(); //默认的已经处理了1+N
		List<Topic> topics = (List<Topic>)session.createQuery("from Topic").list();
		
		for(Topic t : topics) {
			System.out.println(t.getId() + "--" + t.getTitle());
		}
		session.getTransaction().commit();
		session.close();
	}
	
	//解决1+N 问题 第一种方法@ManyToOne(fetch=FetchType.LAZY)  
	@Test
	public void testQuery2() {
		Session session = sf.openSession();
		session.beginTransaction();
		List<Topic> topics = (List<Topic>)session.createQuery("from Topic").list();
		
		for(Topic t : topics) {
			System.out.println(t.getId() + "--" + t.getTitle());
			System.out.println(t.getCategory().getName());
		}
		session.getTransaction().commit();
		session.close();
	}
	//解决1+N 问题 第二种方法@BatchSize(size=5)//加载Category的时候一次性加载5条public class Category {}
	
	@Test
	public void testQuery3() {
		Session session = sf.openSession();
		session.beginTransaction();
		List<Topic> topics = (List<Topic>)session.createQuery("from Topic").list();
		
		for(Topic t : topics) {
			System.out.println(t.getId() + "--" + t.getTitle());
			System.out.println(t.getCategory().getName());
		}
		session.getTransaction().commit();
		session.close();
	}
	
	//解决1+N 问题 三第种方法  join fetch
	@Test
	public void testQuery4() {
		Session session = sf.openSession();
		session.beginTransaction();
		List<Topic> topics = (List<Topic>)session.createQuery("from Topic t left join fetch t.category c").list();
		
		for(Topic t : topics) {
			System.out.println(t.getId() + "--" + t.getTitle());
			System.out.println(t.getCategory().getName());
		}
		session.getTransaction().commit();
		session.close();
	}
	
	
	
	
	public static void main(String[] args) {
		beforClass();
	}
	
	
}

 

缓存

在内存里开辟一块空间,把本来应该存储在硬盘上的东西,然后放在内存里,将来再读的时候直接从内存读,这部分内存就叫缓存.

 could not instantiate RegionFactory [org.hibernate.cache.impl.bridge.RegionFactoryCacheProviderBridge]     EhCache的jar包  ehcache-1.2.3.jar


org/apache/commons/logging/LogFactory      EhCache的jar包commons-logging-1.1.1.jar 


二级缓存

hibernate.cfg.xml  里面配置

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

<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

二级缓存范围

@Cache(usage  = CacheConcurrencyStrategy.READ_WRITE)  //常用方式
public class Category { }


查询缓存

查询缓存(2个语句一样)---重复查询才会使用缓存,2个查询不一样一定用不了,没有相应的算法能够实现


查询缓存的配置和使用:
	* 在hibernate.cfg.xml文件中启用查询缓存,如:
	<property name="hibernate.cache.use_query_cache">true</property>
	* 在程序中必须手动启用查询缓存,如:
	query.setCacheable(true);


查询缓存一定是依赖二级缓存的,所以要先打开二级缓存,再打开查询缓存

如何证明查询缓存起作用,发2条相同的list 如果不出现2条sql 语句即可证明


缓存算法:  内存的对象一旦满了之后,新来的对象要把原来的对象给替换掉,满了之后的对象先完蛋.

package com.demo.hibernate;

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.FetchType;
import javax.persistence.ManyToOne;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.BatchSize;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Restrictions;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class HibernateCacheTest {
	private static SessionFactory sf = null;
	@BeforeClass
	public static void beforClass() {  
//		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句	
		try {
					sf = new AnnotationConfiguration().configure().buildSessionFactory();
				} catch (Exception e) {
					e.printStackTrace();
				}
	}
	@AfterClass
	public static void afterClass() {
		sf.close();
	}
	
	@Test
	public void testSchemaExport() {  //生成建表语句
		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句
	}
	
	@Test
	public void testSave() {
		Session session = sf.openSession();
		session.beginTransaction();
		// 存入10个板块
		for(int i=0; i<10; i++) {
			Category c = new Category();
			c.setName("c"+i);
			Topic t = new Topic();
			t.setCategory(c);
			t.setCreateDate(new Date());
			t.setTitle("t" + i);
			session.save(c);
			session.save(t);
		}
		session.getTransaction().commit();
		session.close();
	}
	
	
	
	

	@Test
	public void testCache1() {
		Session session = sf.openSession();
		session.beginTransaction();
		Category c = (Category)session.load(Category.class, 1);
		System.out.println(c.getName());
		
		Category c1 = (Category)session.load(Category.class, 1);
		System.out.println(c1.getName());
		// 只发一条sql语句 ,因为session 有缓存
		
		
		session.getTransaction().commit();
		session.close();
	}
	
	// 证明一个session 是不能去取另外一个session 的缓存的  利用二级缓存可以解决此问题 
	@Test
	public void testCache2() {
		Session session = sf.openSession();
		session.beginTransaction();
		Category c = (Category)session.load(Category.class, 1);
		System.out.println(c.getName());
		session.getTransaction().commit();
		session.close();
		
		Session session1 = sf.openSession();
		session1.beginTransaction();
		Category c1 = (Category)session1.load(Category.class, 1);
		System.out.println("-----" + c1.getName());
		session1.getTransaction().commit();
		session1.close();
	}
	//证明查询缓存起作用
	@Test
	public void testQueryCache() {
		Session session = sf.openSession();
		session.beginTransaction();
		List<Category> categories1 = (List<Category>)session.createQuery("from Category").setCacheable(true).list();
		List<Category> categories2 = (List<Category>)session.createQuery("from Category").setCacheable(true).list();
		
 		
		session.getTransaction().commit();
		session.close();
	}
	
	public static void main(String[] args) {
		beforClass();
	}
	
	
}

第25课 事务并发处理

一、 数据库的隔离级别:并发性作用。

1、   ReadUncommited(未提交读):没有提交就可以读取到数据(发出了Insert,但没有commit就可以读取到。)很少用

2、   ReadCommited(提交读):只有提交后才可以读,常用,

3、   RepeatableRead(可重复读):mysql默认级别, 必需提交才能见到,读取数据时数据被锁住。

4、   Serialiazble(序列化读):最高隔离级别,串型的,你操作完了,我才可以操作,并发性特别不好,

隔离级别

是否存在脏读

是否存在不可重复读

是否存在幻读

Read Uncommitted(未提交读)

Y

Y

Y

Read Commited(提交读)

N

Y(可采用悲观锁解决)

Y

Repeatable Read(可重复读)

N

N

Y

Serialiazble(序列化读)

 

 

 








脏读:没有提交就可以读取到数据称为脏读

不可重复读:再重复读一次,数据与你上读取的不一样。称不可重复读。

幻读:在查询某一条件的数据,开始查询的后,别人又加入或删除些数据,再读取时与原来的数据不一样了。

1、   Mysql查看数据库隔离级别:

方法:select@@tx_isolation;

2、   Mysql数据库修改隔离级别:

方法:set transactionisolation level 隔离级别名称;

例如:修改为未提交读:settransaction isolation level read uncommitted;

二、 事务概念(ACID)

ACID即:事务的原子性、一致性、独立性及持久性
事务的原子性:是指一个事务要么全部执行,要么不执行.也就是说一个事务不可能只执行了一半就停止了.比如你从取款机取钱,这个事务可以分成两个步骤:1划卡,2出钱.不可能划了卡,而钱却没出来.这两步必须同时完成.要么就不完成.
事务的一致性:是指事务的运行并不改变数据库中数据的一致性.例如,完整性约束了a+b=10,一个事务改变了a,那么b也应该随之改变.
事务的独立性:是指两个以上的事务不会出现交错执行的状态.因为这样可能会导致数据不一致.
事务的持久性:是指事务运行成功以后,就系统的更新是永久的.不会无缘无故的回滚.

三、 事务并发时可能出现问题

1、第一类丢失更新(Lost Update)

时间

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第26课 hibernate悲观锁、乐观锁

Hibernate谈到悲观锁、乐观锁,就要谈到数据库的并发问题,数据库的隔离级别越高它的并发性就越差

         并发性:当前系统进行了序列化后,当前读取数据后,别人查询不了,看不了。称为并发性不好

    数据库隔离级别:见前面章级

一、 悲观锁

悲观锁:具有排他性(我锁住当前数据后,别人看到不此数据)

悲观锁一般由数据机制来做到的。

1、   悲观锁的实现

通常依赖于数据库机制,在整修过程中将数据锁定,其它任何用户都不能读取或修改(如:必需我修改完之后,别人才可以修改)

2、   悲观锁的适用场景:

悲观锁一般适合短事务比较多(如某一数据取出后加1,立即释放)

长事务占有时间(如果占有1个小时,那么这个1小时别人就不可以使用这些数据),不常用。

3、   实例:

 

 
 

用户1、用户2 同时读取到数据,但是用户2先 -200,这时数据库里的是800,现在用户1也开始-200,可是用户1刚才读取到的数据是1000,现在用户用刚刚一开始读取的数据1000-200为800,而用户1在更新时数据库里的是用房更新的数据800,按理说用户1应该是800-200=600,而现在是800,这样就造成的更新丢失。这种情况该如何处理呢,可采用两种方法:悲观锁、乐观锁。先看看悲观锁:用户1读取数据后,用锁将其读取的数据锁上,这时用户2是读取不到数据的,只有用户1释放锁后用户2才可以读取,同样用户2读取数据也锁上。这样就可以解决更新丢失的问题了。

实体类:

public class Inventory {

    private int itemNo;

    privateString itemName;   

    private int quantity;

    public intgetItemNo() {

        return itemNo;

    }

    public voidsetItemNo(intitemNo) {

        this.itemNo =itemNo;

    }

    publicString getItemName() {

        return itemName;

    }

    public voidsetItemName(String itemName) {

        this.itemName =itemName;

    }

    public intgetQuantity() {

        return quantity;

    }

    public voidsetQuantity(intquantity) {

        this.quantity =quantity;

    }  

}

映射文件:

<hibernate-mapping>

    <class name="com.wjt276.hibernate.Inventory"table="t_inventory">

        <id name="itemNo">

            <generator class="native"/>

        </id>

        <property name="itemName"/>

        <property name="quantity"/>

    </class>

</hibernate-mapping>

4、   悲观锁的使用

如果需要使用悲观锁,肯定在加载数据时就要锁住,通常采用数据库的for update语句。

Hibernate使用Load进行悲观锁加载。

Session.load(Classarg0, Serializable arg1, LockMode arg2) throws HibernateException

LockMode:悲观锁模式(一般使用LockMode.UPGRADE)

session= HibernateUtils.getSession();

            tx = session.beginTransaction();

            Inventory inv =(Inventory)session.load(Inventory.class, 1,LockMode.UPGRADE);

            System.out.println(inv.getItemName());

            inv.setQuantity(inv.getQuantity()-200);

           

            session.update(inv);

            tx.commit();

5、   执行输出SQL语句:

Hibernate:select inventory0_.itemNo as itemNo0_0_, inventory0_.itemName as itemName0_0_,inventory0_.quantity as quantity0_0_ from t_inventory inventory0_ whereinventory0_.itemNo=? for update //在select语句中加入for update进行使用悲观锁。

脑白金

Hibernate:update t_inventory set itemName=?, quantity=? where itemNo=?

注:只有用户释放锁后,别的用户才可以读取

 

注:如果使用悲观锁,那么lazy(悚加载无效)

 

二、 乐观锁

乐观锁:不是锁,是一种冲突检测机制。

     乐观锁的并发性较好,因为我改的时候,别人随边修改。

     乐观锁的实现方式:常用的是版本的方式(每个数据表中有一个版本字段version,某一个用户更新数据后,版本号+1,另一个用户修改后再+1,当用户更新发现数据库当前版本号与读取数据时版本号不一致(等于小于数据库当前版本号),则更新不了。

         Hibernate使用乐观锁需要在映射文件中配置项才可生效。

实体类:

public classInventory {

    private int itemNo;

    privateString itemName;   

    private int quantity;  

    private int version;//Hibernate用户实现版本方式乐观锁,但需要在映射文件中配置

    public intgetItemNo() {

        return itemNo;

    }

    public voidsetItemNo(intitemNo) {

        this.itemNo =itemNo;

    }

    publicString getItemName() {

        return itemName;

    }

    public voidsetItemName(String itemName) {

        this.itemName =itemName;

    }

    public intgetQuantity() {

        return quantity;

    }

    public voidsetQuantity(intquantity) {

        this.quantity =quantity;

    }

    public intgetVersion() {

        return version;

    }

    public voidsetVersion(intversion) {

        this.version =version;

    }

}

映射文件

<hibernate-mapping>

<!-- 映射实体类时,需要加入一个开启乐观锁的属性

optimistic-lock="version" 共有好几种方式:

    - none  -version   - dirty - all

    同时需要在主键映射后面映射版本号字段

    -->

<class name="com.wjt276.hibernate.Inventory"table="t_inventory"optimistic-lock="version">

        <id name="itemNo">

            <generator class="native"/>

        </id>

        <version name="version"/><!—必需配置在主键映射后面 -->

        <property name="itemName"/>

        <property name="quantity"/>

    </class>

</hibernate-mapping>

导出输出SQL语句:

createtable t_inventory (itemNo integer not null auto_increment, versioninteger not null, itemName varchar(255), quantity integer,primary key (itemNo))

注:添加的版本字段version,还是我们来维护的,是由hibernate来维护的。

乐观锁在存储数据时不用关心


_____________________________________________________________________



脏读:没有提交就可以读取到数据称为脏读

不可重复读:再重复读一次,数据与你上读取的不一样。称不可重复读。

幻读:在查询某一条件的数据,开始查询的后,别人又加入或删除些数据,再读取时与原来的数据不一样了。


static int TRANSACTION_NONE 
          指示事务不受支持的常量。 
static int TRANSACTION_READ_COMMITTED                                                      2
          指示防止发生脏读的常量;不可重复读和虚读有可能发生。 
static int TRANSACTION_READ_UNCOMMITTED                                                 1
          指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。 
static int TRANSACTION_REPEATABLE_READ                                                        3
          指示防止发生脏读和不可重复读的常量;虚读有可能发生。 
static int TRANSACTION_SERIALIZABLE                                                                 4
          指示防止发生脏读、不可重复读和虚读的常量。 

hibernate.connection.isolation = 2    

e.g. 1, 2, 4, 8  (二进制效率)




悲观锁

package com.demo.hibernate;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Account {
	private int id;
	private int balance; //BigDecimal 银行程序(精度)
	
	@Id
	@GeneratedValue
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getBalance() {
		return balance;
	}
	public void setBalance(int balance) {
		this.balance = balance;
	}
	
}

package com.demo.hibernate;

import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class HibernateCacheTest {
	private static SessionFactory sf = null;
	@BeforeClass
	public static void beforClass() {  
//		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句	
		try {
					sf = new AnnotationConfiguration().configure().buildSessionFactory();
				} catch (Exception e) {
					e.printStackTrace();
				}
	}
	@AfterClass
	public static void afterClass() {
		sf.close();
	}
	
	@Test
	public void testSchemaExport() {  //生成建表语句
		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句
	}
	
	@Test
	public void testSave() {
		Session session = sf.openSession();
		session.beginTransaction();
		
		Account a = new Account();
		a.setBalance(100);
		session.save(a);
		session.getTransaction().commit();
		session.close();
	}
	

	@Test
	public void testOperation1() {
		Session session = sf.openSession();
		session.beginTransaction();
		
		Account a = (Account)session.load(Account.class, 1);
		int balance = a.getBalance();
		//do some caculations
		balance = balance - 10;
		a.setBalance(balance);
		session.getTransaction().commit();
		session.close();
	}
	
	@Test
	public void testPessimisticLock() {
		Session session = sf.openSession();
		session.beginTransaction();
		//LockMode.UPGRADE  就是读取这条记录的时候请数据库在我读取的时候加把锁,在这个事物没有提交之前不会有人更新它,所以叫做悲观锁
		Account a = (Account)session.load(Account.class, 1, LockMode.UPGRADE); 
		
		int balance = a.getBalance();
		//do some caculation
		balance = balance - 10;
		a.setBalance(balance);
		session.getTransaction().commit();
		session.close();
	}
	
	
	
	public static void main(String[] args) {
		beforClass();
	}
	
	
}

乐观锁

package com.demo.hibernate;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class Account {
	private int id;
	private int balance; //BigDecimal 银行程序(精度)
	private int version;
	
	@Version  //版本的问题
	public int getVersion() {
		return version;
	}
	public void setVersion(int version) {
		this.version = version;
	}
	@Id
	@GeneratedValue
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getBalance() {
		return balance;
	}
	public void setBalance(int balance) {
		this.balance = balance;
	}
	
}

package com.demo.hibernate;

import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class HibernateCacheTest {
	private static SessionFactory sf = null;
	@BeforeClass
	public static void beforClass() {  
//		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句	
		try {
					sf = new AnnotationConfiguration().configure().buildSessionFactory();
				} catch (Exception e) {
					e.printStackTrace();
				}
	}
	@AfterClass
	public static void afterClass() {
		sf.close();
	}
	
	@Test
	public void testSchemaExport() {  //生成建表语句
		new SchemaExport(new AnnotationConfiguration().configure()).create(false, true); //自动创建SQL语句
	}
	
	@Test
	public void testSave() {
		Session session = sf.openSession();
		session.beginTransaction();

		Account a = new Account();
		a.setBalance(100);
		session.save(a);

		session.getTransaction().commit();
		session.close();
	}

	@Test
	public void testOptimisticLock() {
		Session session = sf.openSession();

		Session session2 = sf.openSession();

		
		
		
		session.beginTransaction();
		Account a1 = (Account) session.load(Account.class, 1);
		

		session2.beginTransaction();
		Account a2 = (Account) session2.load(Account.class, 1);
		
		a1.setBalance(900);
		a2.setBalance(1100);

		session.getTransaction().commit();
		System.out.println(a1.getVersion());

		session2.getTransaction().commit();
		System.out.println(a2.getVersion());

		session.close();
		session2.close();
	}	
	
	
	public static void main(String[] args) {
		beforClass();
	}
	
	
}





















  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值