hiberante入门(十五):事务相关-悲观乐观锁

1.事务引发的三层架构MVC讨论:
在前面的程序代码中多次用到了事务:
Transaction tx = s.beginTransaction(); 
          
对应的jdbc代码:connection. setAutoCommit(false);

.....

.....数据处理
.....
tx.commit();
          
对应的jdbc代码:connection.commit();

tx.rollback();

对应的jdbc代码:connection.rollback();  

以上的三处事务相关的代码统称为“事务边界”其实hibernate的事务本质是依赖jdbc的事务来实现,其实对于事务而言,它是应该由业务逻辑层来实现,但是事务所依赖的对象又出现在Model层,所以想严格按照MVC的原理来实现将相当难(在前面我们所写的代码中事务的处理是不符合mvc模式得),但是利用推出的EJB,SPRING这些框架将能达到此目的,下图展示了三层架构,与原始的MVC相比,多了一个持久层。

持外层

2.模拟session context:

>>步骤一,重写工具类,核心代码如下:

Java代码 复制代码
  1. package com.asm.hibernate.utils;   
  2. public class HibernateUtilOSV {   
  3.     private static SessionFactory sf;   
  4.     private static ThreadLocal session = new ThreadLocal();   
  5.     private HibernateUtilOSV() {   
  6.     }   
  7.     public static Session getThreadLocalSession() {   
  8.         Session s = (Session) session.get();   
  9.         if (s == null) {   
  10.             s = getSession();   
  11.             session.set(s);   
  12.         }   
  13.         return s;   
  14.     }   
  15.     public static void closeSession() {   
  16.         Session s = (Session) session.get();   
  17.         if (s != null) {   
  18.             s.close();   
  19.             session.set(null);   
  20.         }   
  21.     }   
  22.     static {   
  23.         Configuration cf = new Configuration();   
  24.         cf.configure();   
  25.         sf = cf.buildSessionFactory();   
  26.     }   
  27.     public static SessionFactory getSessionFactory() {   
  28.         return sf;   
  29.     }   
  30.     public static Session getSession() {   
  31.         return sf.openSession();   
  32.     }   
  33. }  
package com.asm.hibernate.utils;
public class HibernateUtilOSV {
	private static SessionFactory sf;
	private static ThreadLocal session = new ThreadLocal();
	private HibernateUtilOSV() {
	}
	public static Session getThreadLocalSession() {
		Session s = (Session) session.get();
		if (s == null) {
			s = getSession();
			session.set(s);
		}
		return s;
	}
	public static void closeSession() {
		Session s = (Session) session.get();
		if (s != null) {
			s.close();
			session.set(null);
		}
	}
	static {
		Configuration cf = new Configuration();
		cf.configure();
		sf = cf.buildSessionFactory();
	}
	public static SessionFactory getSessionFactory() {
		return sf;
	}
	public static Session getSession() {
		return sf.openSession();
	}
}

 

>>步骤二、导入servlet包,使用Filter. 内容如下:

Java代码 复制代码
  1. package com.asm.hibernate.osv;   
  2. public class OpenSessionViewFilter implements Filter {   
  3.     public void destroy() {   
  4.         // TODO Auto-generated method stub   
  5.     }   
  6.     public void doFilter(ServletRequest arg0, ServletResponse arg1,   
  7.             FilterChain arg2) throws IOException, ServletException {   
  8.         Session s = null;   
  9.         Transaction tx = null;   
  10.         try {   
  11.             s = HibernateUtilOSV.getThreadLocalSession();   
  12.             tx = s.beginTransaction();   
  13.   
  14.             arg2.doFilter(arg0, arg1);   
  15.             tx.commit();   
  16.         } catch (Exception e) {   
  17.             if (tx != null)   
  18.                 tx.rollback();   
  19.         } finally {   
  20.             HibernateUtilOSV.closeSession();   
  21.         }   
  22.     }   
  23.     public void init(FilterConfig arg0) throws ServletException {   
  24.         // TODO Auto-generated method stub   
  25.     }   
  26. }  
package com.asm.hibernate.osv;
public class OpenSessionViewFilter implements Filter {
	public void destroy() {
		// TODO Auto-generated method stub
	}
	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		Session s = null;
		Transaction tx = null;
		try {
			s = HibernateUtilOSV.getThreadLocalSession();
			tx = s.beginTransaction();

			arg2.doFilter(arg0, arg1);
			tx.commit();
		} catch (Exception e) {
			if (tx != null)
				tx.rollback();
		} finally {
			HibernateUtilOSV.closeSession();
		}
	}
	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub
	}
}

 

>>步骤三、Userdao

Java代码 复制代码
  1. package com.asm.hibernate.osv;   
  2. public class UserDao {   
  3.     static void addUseDao(User user) {   
  4.         HibernateUtilOSV.getThreadLocalSession().save(user);   
  5.     }   
  6. }  
package com.asm.hibernate.osv;
public class UserDao {
	static void addUseDao(User user) {
		HibernateUtilOSV.getThreadLocalSession().save(user);
	}
}

原理流程分析:当有被Filter监测的请求到达此Filter时,Filter会利用工具类来获得一个Session,并用这个session打开事务,然后再把请求传给下一个Filter.当所有操作执行完成后再返回到这个Filter,如果没有任何异常便可以提交事务,否则回滚事务。这样便可以有效保证事务的完整性,而且有利于懒加载,因为在整个请求的过程中,Session一直未关闭,所以在filter不断向下传递的过程中可以放心的使用session来完成懒加载,这也是解决懒加载的一种方式。其实OpenSessionInView主要解决了两个问题:一是实现事务在逻辑层控制,二是利于懒加载的实现。下面再来看看工具类:此工具类主要完成了两个工作:一是生成一个线程级的Session,即是说在一个线程的事务开启到事务结束,一直用的是同一个Session,这样有利于保证事务的完整性,而工具类主要借助了ThradLocal来生成这个线程级的Session。 二是完成Session的关闭。

3.使用Hibernate自带的SessionContext

>>步骤一、在主配置文件中增加如下内容:   

<property name="current_session_context_class">thread</property>以支持事务边界。 说明:它实质也是利用ThreadLocal来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边界。需要注意的是,当我们打开此功能后再通过SessionFactory.getCurrentSession()来获取一个Session,这个Session不能调用close方法,因为当我们提交事务或者回滚事务都会引起这个Session关闭,而通常的事务最会被提交或回滚,所以我们永远不要在这样的Session中调用close方法。同样的它的一个重要作用就是在生成页面时保持Session打开(即OpenSessionInView),因此为懒加载提供了方便。 除了配置为thread外还可以配置值为“jta” ,配置成jta后主要JTA事务管理器来管理事务,jta事务管理器主要用在有多个数据库的情况

>>步骤二、重写工具类:核心代码如下:

Java代码 复制代码
  1. package com.asm.hibernate.utils;   
  2. public class HibernateUtilSelf {   
  3.     private static SessionFactory sf;   
  4.     private HibernateUtilSelf() {   
  5.     }   
  6.     public static Session getThreadLocalSession() {   
  7.         Session s = sf.getCurrentSession();   
  8.         if (s == null) {   
  9.             s = getSession();   
  10.         }   
  11.         return s;   
  12.     }   
  13.     static {   
  14.         Configuration cf = new Configuration();   
  15.         cf.configure();   
  16.         sf = cf.buildSessionFactory();   
  17.     }   
  18.     public static SessionFactory getSessionFactory() {   
  19.         return sf;   
  20.     }   
  21.     public static Session getSession() {   
  22.         return sf.openSession();   
  23.     }      
  24. }  
package com.asm.hibernate.utils;
public class HibernateUtilSelf {
	private static SessionFactory sf;
	private HibernateUtilSelf() {
	}
	public static Session getThreadLocalSession() {
		Session s = sf.getCurrentSession();
		if (s == null) {
			s = getSession();
		}
		return s;
	}
	static {
		Configuration cf = new Configuration();
		cf.configure();
		sf = cf.buildSessionFactory();
	}
	public static SessionFactory getSessionFactory() {
		return sf;
	}
	public static Session getSession() {
		return sf.openSession();
	}	
}

 

>>编写测试类

Java代码 复制代码
  1. package com.asm.hibernate.osv;   
  2. public class UserTest {   
  3.     public static void main(String[] args) {   
  4.         User user = new User();   
  5.         user.setName("richie");   
  6.         user.setDate(new Date());   
  7.         addUser(user);   
  8.     }   
  9.     static void addUser(User user) {   
  10.         Session s = null;   
  11.         Transaction ts = null;   
  12.         try {   
  13.             s = HibernateUtilSelf.getThreadLocalSession();   
  14.             ts = s.beginTransaction();   
  15.             s.save(user);   
  16.             ts.commit();   
  17.         } catch (HibernateException e) {   
  18.             if (ts != null)   
  19.                 ts.rollback();   
  20.             throw e;   
  21.         } finally {   
  22.             if (s != null)   
  23.                 // s.close(); //注意这里不能使用close,并注意下面的打印结果   
  24.                 System.out.println("s=" + s);   
  25.         }   
  26.     }   
  27. }  
package com.asm.hibernate.osv;
public class UserTest {
	public static void main(String[] args) {
		User user = new User();
		user.setName("richie");
		user.setDate(new Date());
		addUser(user);
	}
	static void addUser(User user) {
		Session s = null;
		Transaction ts = null;
		try {
			s = HibernateUtilSelf.getThreadLocalSession();
			ts = s.beginTransaction();
			s.save(user);
			ts.commit();
		} catch (HibernateException e) {
			if (ts != null)
				ts.rollback();
			throw e;
		} finally {
			if (s != null)
				// s.close(); //注意这里不能使用close,并注意下面的打印结果
				System.out.println("s=" + s);
		}
	}
}

 

执行后的结果为s=SessionImpl(<closed>)

4.悲观锁:

悲观地认为每个事务在操纵数据时,肯定会有其它的事务同时访问该数据资源,当前事务为防止自身受到影响,先将相关数据资源锁定,直到事务结束后释放锁定。

实现方式一:利用LockMode实现数据锁;原理是使用带有LockMode参数的load方法来实现数据加锁,LockMode主要有以下几种锁定方式:

 

锁定方式

功能

LockMode.NONE

Hibernate的默认值,首先到缓存中检索,检测不到时连数据库

LockMode.READ

不管缓存,直接到数据库中加载。如果在实体配置文件中配置了版本元素,则比较缓存中的版本与数据库中的版本是否一致

LockMode.UPGRADE

READ的基础上,如果数据库支持悲观锁,则执行select...for update,否则执行变通的select语句。

UPGRADE_NOWAIT

主要是针对Oracle数据库,在UPGRADE的基础上,如果执行select语句不能获得锁,则会抛出锁定异常

实现方式二、通过逻辑实现锁定数据;原理是在实体类的数据库表中设定一个boolean的字段,当这个字段为true,表示该数据正在被访问,此时记录被锁定。如果为false,则表示可以访问。过程:(1)当访问的数据为false时,表示可以访问,此时修改字段为true,以避免其它对象访问,操作完成后再设此字段为false,以让等待的对象访问 (2)当访问数据据为true,表示不可被访问。

5.乐观锁:

乐观地认为每个事务在操纵数据时,很少有或者不会其它事务同时访问数据资源,因而不会在数据库层锁定,而是通过逻辑来实现版本控制。

实现方式一<version>方式:为了方便操作新建一个名为OptimizeLock的项目.

>>步骤一,拷贝以前的User类及相应的实体配置文件等相关文件。
>>
步骤二,在User类中增加
private int version;及相应的get/set方法。Version名字可以随意取。

>>步骤三、修改配置文件:增加<version name="version"></version>属性,并注意在<class>元素中使用optimistic-lock="version"属性,这也是官方推荐使用的属性值。但不能使用none,因为它不支持乐观锁,也可这样说,即使我们为乐观锁做好了一切准备,但我们不想在某个实体对象中使用时,只需要配置optimistic-locknone即可。
>>
步骤四、编写测试类

Java代码 复制代码
  1. package com.asm.hibernate.test;   
  2.   
  3. public class VersionUserTest {   
  4.   
  5.     public static void main(String[] args) {   
  6.   
  7.        addUser();   
  8.   
  9.        update(1);   
  10.   
  11.     }   
  12.   
  13.     static void update(int id) {   
  14.   
  15.        Session s1 = null;   
  16.   
  17.        Session s2 = null;   
  18.   
  19.        Transaction tx1 = null;   
  20.   
  21.        Transaction tx2 = null;   
  22.   
  23.        try {   
  24.   
  25.            s1 = HibernateUtil.getSession();   
  26.   
  27.            tx1 = s1.beginTransaction();   
  28.   
  29.            User user1 = (User) s1.get(User.class, id);   
  30.   
  31.            System.out.println(user1.getName());   
  32.   
  33.            s2 = HibernateUtil.getSession();   
  34.   
  35.            tx2 = s2.beginTransaction();   
  36.   
  37.            User user2 = (User) s2.get(User.class, id);   
  38.   
  39.   
  40.   
  41.            user1.setName("user1Name");   
  42.   
  43.            user2.setName("user2Name");   
  44.   
  45.     
  46.   
  47.            tx1.commit();   
  48.   
  49.            tx2.commit();   
  50.   
  51.        } catch (HibernateException e) {   
  52.   
  53.            ....   
  54.   
  55.        } finally {   
  56.   
  57.            ....   
  58.   
  59.        }   
  60.   
  61.     }   
  62.   
  63.     static void addUser() {   
  64.   
  65.        ...主要作用是增加一条记录以实现操作。   
  66.   
  67.     }   
  68.   
  69. }  
package com.asm.hibernate.test;

public class VersionUserTest {

    public static void main(String[] args) {

       addUser();

       update(1);

    }

    static void update(int id) {

       Session s1 = null;

       Session s2 = null;

       Transaction tx1 = null;

       Transaction tx2 = null;

       try {

           s1 = HibernateUtil.getSession();

           tx1 = s1.beginTransaction();

           User user1 = (User) s1.get(User.class, id);

           System.out.println(user1.getName());

           s2 = HibernateUtil.getSession();

           tx2 = s2.beginTransaction();

           User user2 = (User) s2.get(User.class, id);



           user1.setName("user1Name");

           user2.setName("user2Name");

 

           tx1.commit();

           tx2.commit();

       } catch (HibernateException e) {

           ....

       } finally {

           ....

       }

    }

    static void addUser() {

       ...主要作用是增加一条记录以实现操作。

    }

}

 

分析:在进行分析前,先提下笔者曾范的一个测试错误,由于是复制以前的代码,由于以前在User的配置文件中配置了二级缓存的“<cache usage="read-only"/>” ,这样执行时总是报“Can't write to a readonly object” 。由这点大家可以再细想下悲观锁的更多细节问题。下面来谈version,当我们向数据表中添加一个version字段,hibernate会控制此字段,当访问一条记录时,同时访问此记录的版本信息,如果对该记录做了修改,在提交事务时,hibernate会将访问时的版本信息与数据库中的版本信息进行对比,如果一致则提交修改并将原版本信息加1,否则撤销本次修改。 上面的两个session并分别有开启了两个事务,当提交第一个事务时,符合要求进行了提交。但我们再来提交第二个事务时,由于第一个事务修改了版本信息,尽管user1user2最初获得的版本是一样得,但由于第一个事务提交修改了数据库中版本信息,所以第二个事务将不能被提交(如不能明白,请仔细阅读本段中的黑体字部份)。  请这样测试:执行完后(会报“Transaction not successfully started”异常)查数据表,然后再交换以上两个事务提交的顺序再执行,再查数据表。 另请不要深究异常处理。

实现方式二<timestamp>

基本和方式一相同,只是在User类中增加一个Date类型的属性,同时在实体配置文件配置如下内容: <timestamp name="实体类的中Date类型的属性"></timestamp> 

你可能感兴趣的:(oracle,Hibernate,mvc,jdbc,配置管理)