3月2日——培训第69天

Spring与Hibernate的整合

Hibernate的核心是SessionFactory,它就像JDBC中的DataSource一样。

Spring提供了以IOC机制导入SessionFactory的可能,这是通过LocalSessionFactoryBean实现的。
(SessionFactory是接口,不能直接注入的)

由于Hibernate版本更新向前不兼容,因此Spring也提供了两套接口对于不同的Hibernate版本。
org.springframework.orm.hibernate支持Hibernate 2.1,
而org.springframework.orm.hibernate3 则支持hibernate3

例子:

加入spring.jar , 所有的hibernate的jar

package vo ;

public class Course
{
 private SessionFactory sessionFactory ;
 private int id ;
 private String name ;

 //getter方法和setter方法

 public void save()
 {
  Session session = null ;
  try
  {
   session = factory.openSession() ;
   session.beginTransaction() ;
  
   session.save(this);

   session.getTransaction().commit() ;
  }
  catch(HibernateException e)
  {
   e.pringStackTrace() ;
   if(session!=null&&session.getTransaction()!=null)
   {
    session.getTransaction().rollback();
   }
  }
  finally
  {
   if(session!=null) session.close();
  }
 }

}

bean.xml:

<bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">

 <property name="driverClassName">
  <value>com.mysql.jdbc.Driver</value>
 </property>
 <property name="url">
  <value>jdbc:mysql:///j2ee</value>
 </property>
 <property name="username">
  <value>root</value>
 </property>
 <property name="password">
  <value>root</value>
 </property>
</bean>

//配置sessionFactory的时候用了上面已经注册好的数据源
<bean id="sessionFactory"
 class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
 
 <property name="dataSource" ref="dataSource" />
 <property name="mappingResources" value="vo/Course.hbm.xml" />
 <property name="hibernateProperties">
  <props>
   <prop key="hibernate.dialect">
    org.hibernate.dialect.MySQLDialect
   </prop>
   <prop key="hibernate.show_sql">
    true
   </prop>
  </props>
 </property>
</bean>


<bean id="course" class="vo.Course">
 <property name="sessionFactory" ref="sessionFactory" />
</bean>

当然了,虽然hibernate的配置文件已经在bean.xml的注册中被搞定了,
但是映射文件还是得有。

Course.hbm.xml:

<hibernate-mapping>
 <class name="vo.Course" table="courses">
  <id name="id" column="course_id">
   <generator class="identity" />
  </id>
  <property name="name" column="course_name" />
 </class>
</hibernate-mapping>

写个demo:

public static void main(String[] args)
{
 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml") ;
 Course course = (Course)context.getBean("course");
 course.setName("hibernate");
 course.save() ;
}

--------------------------------------------------------------------------------

在应用上,Spring也提供了模板与回调模式来实现Hibernate对数据库的操作。
HibernateTemplate是模板,而HibernateCallback则是回调接口。
HibernateTemplate提供的execute方法,需要以HibernateCallback为参数,
执行时则在合适的时候回调对象。

类似于JdbcTemplate,HiberateTemplate中也加入了一些简便的方法,可以实现增、删改操作。
这些方法基本都可以在Hibernate的Session接口中找到,或者是HQL、QBC、QBE等等。
具体参照API文档

package vo ;

public class MyCallback implements HibernateCallback
{
 private Object target ;
 public MyCallback(Object obj)
 {
  this.target = obj ;
 }

 public Object doInHibernate(Session arg0)
  throws HibernateException,SQLException
 {
  Serializable id = null ;
  try
  {
   arg0.beginTransaction() ;
   id = arg0.save(target) ;
   arg0.getTransaction().commit() ;
  }
  catch(HibernateException e)
  {
   e.printStackTrace() ;
   if(session!=null&&session.getTransaction()!=null)
    session.getTransaction().rollback();
  }
  //由于是回调方法,所以session不要关!
 }
}

在配置文件中添加以下信息来注册hibernateTemplate:
<bean id="hibernateTemplate"
 class="org.springframework.orm.hibernate3.HibernateTemplate">
 
 <property name="sessionFactory" ref="sessionFactory" />
</bean>

//同时在course中依赖注入hibernateTemplate
<bean id="course" class="vo.Course">
 <property name="sessionFactory" ref="sessionFactory" />
 <property name="hibernateTemplate" ref="hibernateTemplate" />
</bean>


改变实体类信息,加入一个HibernateTemplate私有成员变量并且加入setter和getter
方法:

package vo ;

public class Course
{
 private SessionFactory sessionFactory ;
 private int id ;
 private String name ;

 private HibernateTemplate hibernateTemplate ;
 //getter方法和setter方法

 public void save()
 {
  hibernateTemplate.execute(new MyCallback(this));
  //hibernateTemplate.save(this);这句话也可以取代上面那句。
 }

}

======================================================================

Spring的事务支持:

Spring最为自豪的当属事务管理,因为它在不使用EJB服务器的情况下,实现了对事务的
宣称式支持。这在以往是仅能由Session Bean提供的功能。
Spring对事务的宣称式支持是通过AOP机制实现,也即在需要声明事务属性的方法中织入通知。

由于不同的平台实现事务管理采用的是不同的资源,例如JDBC使用直接使用Connection管理事务,
而Hibernate则使用Session。
在实现上,Spring要求为JDBC事务管理器注入数据源DataSource,而为Hibernate事务管理器
注入SessionFactory,所以使用不同的数据层解决方案,相应的事务管理器也完全不同。

与EJB相同,Spring对事务的支持也分为编程式和宣称式两种(对应EJB的BMT和CMT)。
在编程式事务管理中,Spring依然采用了模板与回调模式。
在宣称式事务管理中,Spring采用了与EJB类似的事务属性来管理事务。


Spring提供了两种方式实现编程式事务管理:
1、使用TransactionTemplate与TransactionCallback结合。
2、直接使用一个PlatformTransactionManager的实现。

事实上,Spring为模板提供了两个回调类,即TransactionCallbackWithoutResult
和TransactionCallback ,前者是后者的实现,并且是一个抽象类。
TransactionStatus作为参数传给回调函数,通过TransactionStatus可以设置事务只可回滚。
需要注意,运行时异常可导致自动回滚。其余则必须设置成只可回滚。

宣称式事务下午再说,上午主要说编程式事务

例子:

//考虑模拟一个银行转账的例子:
建立表account:
id、balance(余额),都是integer类型
建两个记录,余额都是1000

<bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">

 <property name="driverClassName">
  <value>com.mysql.jdbc.Driver</value>
 </property>
 <property name="url">
  <value>jdbc:mysql:///j2ee</value>
 </property>
 <property name="username">
  <value>root</value>
 </property>
 <property name="password">
  <value>root</value>
 </property>
</bean>

//如果要用hibernateTemplate的话,传入的是sessionFactory
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
 <property name="dataSource" ref="dataSource" /> 
</bean>

<bean id="transactionManager"
 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 
 <property name="dataSource" ref="dataSource" />
</bean>

<bean id="transactionTemplate"
 class="org.springframework.transaction.support.TransactionTemplate">
 
 <property name="transactionManager" ref="transactionManager" />
</bean>

<bean id="callback" class="vo.TransferCallback">
 <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

<bean id="account" class="vo.Account">
 <property name="transactionTemplate" ref="transactionTemplate" />
</bean>

建立一个vo:

package vo ;

public class Account implements ApplicationContextAware
{
 private int id ;
 private int balance ;
 private TransactionTemplate transactionTemplate ;
 private ApplicationContext applicationContext ;
 //加入getter和setter方法

 public void setApplicationContext(ApplicationContext arg0)
 {
  this.applicationContext = arg0 ;
 }
 //转账方法
 public void transferTo(int id)
 {
  TransferCallback callback =
   (TransferCallback)applicationContext.getBean("callback");
  callback.setFrom(this.id);
  callback.setTo(id) ;
  callback.setAmount(100) ;
  //execute方法里面的传入参数要求callback方法。
  transactionTemplate.execute(callback);
 }
}

//实现TransactionCallback的类
public class TransferCallback implements TransactionCallback
{
 private JdbcTemplate jdbcTemplate ;
 private int from ; //两个帐号
 private int to ;
 private int amount ; //转多少钱
 //加入getter和setter方法

 public Object doInTransaction(TransactionStatus status)
 {
  try
  {
   jdbcTemplate.update("update account set balance=balance-"+amount
    +" where id="+from);
   jdbcTemplate.update("update account set balance=balance+"+amount
    +" where id="+to);
  }
  catch(Exception e)
  {
   status.setRollbackOnly() ;
  }
  return null ;
  //这里注意,上面不需要返回值,但是如果需要的话,这里返回的值其实就是execute方法
  //的返回值,也就是说,回调函数的最终返回结果会作为execute方法的最终返回结果!!!
 }
}

做一个demo来测试一下:

public static void main(String[] args)
{
 ApplicationContext context =
  new ClassPathXmlApplicationContext("bean.xml") ;
 Account a = (Account)context.getBean("account");
 a.setId(1) ;
 a.transferTo(2) ;
}

====================================================================================
事务发生回滚一般都是发生异常的时候才处理,但是未必所有的异常发生都会回滚,但是和数据库操作相关的
异常必然需要回滚,EJB中将异常分为系统级别异常(这时必然导致事务回滚,是容器自动完成的)和应用级别
的异常(逻辑上的异常,而不是操作系统的异常,这些是需要自定义的,需要设置事务只可回滚,也就是类似于
上面的setRollbackOnly())。

在Spring中可以定制一些系统异常使之不回滚,还可以定制隔离级别,但是Spring没有办法实现分布式的
事务管理(比如为了系统备份,做了两个数据库,两个数据库不在一个地方,但是还要保持同步。两个数据库
一般要建立两个连接、两个连接还需要在一个事务里面。所以需要jta,也就是java transaction service
,java事务管理服务。EJB可以实现分布式的事务管理),在分布式的事务管理方面,Spring是绝对不可能取代
EJB的……

EJB中,发生系统级异常的时候事务自动回滚,在Spring中,发生运行时异常(扩展自runtimeException,不需要
被捕获)将导致自动回滚。

想要用Spring处理宣称式事务的话,没什么好说的,一定要使用Template模版才可以,除非你使用编程式处理事务
另当别论。

Spring宣称式事务与Session Bean的CMT事务管理在使用上极为相似,但是在很多方面Spring要优于EJB:
EJB事务管理是绑定在JTA上的,而Spring则可以应用在任何情况下。
EJB事务管理不支持POJOs
Spring提供声明式回滚规则,并且可以通过AOP在事务中定制其它行为
Spring不依赖服务器
但有一点,Spring是无法取代EJB的,那就是分布式的事务管理。

在Spring和EJB管理中,是将方法作为事务原子单元的。一个方法里面不可能有两个事务了。


Spring的宣称式事务管理实际上是应用了Spring的AOP特性,将事务管理的代码织入到原代码中。
使用的代理类是TransactionProxyFactoryBean,位于
org.springframework.transaction.interceptor包

CMT:Container Manager Transaction 容器管理事务

 

如果使用宣称式事务处理的话更改上午的程序,则不需要那个TransferCallback回调方法了,修改实体类
如下:
package vo ;

public class Account
{
 private int id ;
 private int balance ;
 private JdbcTemplate ;
 //加入getter和setter方法

 
 //转账方法
 public void transferTo(int id,int amount)
 {
  jdbcTemplate.update("update account set balance=balance-"+amount
    +" where id="+this.id);
   jdbcTemplate.update("update account set balance=balance+"+amount
    +" where id="+id); 
 }
}

然后bean的配置文件也要相应的做一些更改:

<bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">

 <property name="driverClassName">
  <value>com.mysql.jdbc.Driver</value>
 </property>
 <property name="url">
  <value>jdbc:mysql:///j2ee</value>
 </property>
 <property name="username">
  <value>root</value>
 </property>
 <property name="password">
  <value>root</value>
 </property>
</bean>

//如果要用hibernateTemplate的话,传入的是sessionFactory
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
 <property name="dataSource" ref="dataSource" /> 
</bean>

<bean id="transactionManager"
 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 
 <property name="dataSource" ref="dataSource" />
</bean>


<bean id="accountTarget" class="vo.Account">
 <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

//代理
<bean id="account"
 class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
 
 <property name="transactionManager" ref="transactionManager" />
 <property name="target" ref="accountTarget" />
 <property name="transactionAttributes">
  <props>
   <prop key="transferTo">PROPAGATION_REQUIRED</prop>
   //注意这里表明的是transferTo这个方法是需要事务回滚的,如果你不想让他回滚的话,
   //你这里设置成PROPAGATION_NEVER就可以了
  </props>
 </property>
</bean>

-------------------------------------------------------------------------------------------
下面是一些概念性的东西了,可能会枯燥一些……
而且事务的处理在企业级开发里面是最复杂的

事务属性描述了一个事务的特征,这包括:
传播行为(Transaction propagation)
隔离级别(Transaction isolation )
只读状态(Read-only status)
事务超时(Transaction timeout)

传播行为:a方法调用b方法,如果a处在一个事务当中的话,那么b是要被包含
  a的事务中呢,还是b要开启一个新的事务呢?
 如果b不在一个独立的事务里的话,b发生异常的话,a也会回滚。
 所谓传播行为就是两个方法之间如果有相互调用的关系的话,也就是a调用
 b的话,a是否处于一个事务中,b是否处于一个独立事务中会导致不同的情况。

上面的PROPAGATION_REQUIRED的含义是:
a如果在一个事务中,b则一定要包含在a的事务中,如果a不在一个事务中,那么b
就新开一个独立的事务

也就是:
the current method must run within a transaction.If an existing
transaction is in progress, the method will run within that
transaction.Otherwise, a new transaction will be started .

这就类似于下面的这种情况,网上购物,涉及两种关键的步骤,一是转账,二是
生成送货单。转账毫无疑问是事务,但是如果生成送货单出现异常的话,一定要
迫使转账也回滚这才合理,但是问题是一般转账和送货单是在两个数据库中进行的,
涉及了分布式处理,必须用EJB才可以。在这里,转账方法中包含着送货单的方法,
毫无疑问转账方法需要事务,但是送货单方法设置为PROPAGATION_REQUIRED的话,
就会让送货单方法也处于转账事务里面,一旦生成送货单出现异常的话,转账也会
回滚的。

PROPAGATION_REQUIRED就符合上面的要求,它是六种里面最安全的了。

----------------------------------------------------------

下面是PROPAGATION_REQUIRES_NEW,
the current method must run within its own transaction.
A new transaction is started and if an existing transaction
is in progress, it will be suspended for the duration of the
method. If using JTA TransactionManager, access to Transaction
Manager is required .

注意上面的suspended就是事务挂起的意思,也就是类似于保存点savepoint的意思,
直到把那个处于新事务中的方法执行完才利用保存点开启原来的事务。


什么时候用它呢?假如有人买东西买到一半不买了,我需要知道曾经有想买这种东西想法
的人到底多少,不管他是否最终交了钱。那这时候也就是说这个方法必须运行在一个事务
里面,但是又不需要外部的方法去影响它。

使用这种级别的方法不想被别的方法影响。
--------------------------------------------------------------------
PROPAGATION_NOT_SUPPORTED:
indicates that the method should not run within a transaction. If an
existing transaction is in progress, it will be suspended for the
duration of the method . If using JTATransactionManager, access to
TransactionManager is required .

应用场合:方法不涉及任何事务的问题,但是该方法也不能因为它自己本身出现问题而导致客户端
  事务回滚!这时候就用not_supported比较合适。
也就是说这个方法不想影响别的方法
--------------------------------------------------------------------------
PROPAGATION_SUPPORTS:

indicates that the current method does not require a transactional
context, but may run within a transaction if one is already in progress

这是比较宽容的一种做法了,该方法不需要事务处理,但是如果已经位于一个事务中也无所谓

-------------------------------------------------------------------------

PROPAGATION_MANDATORY:

indicates that the method must run within a transaction . If no existing
transaction is in progress, an exeption will be thrown .

该方法不可以处于独立的事务里面,必须被包含在别人的事务中,否则就出错了,一般适用于
一个独立事情的几个关键步骤,这几个关键步骤都必须运行,但是都不能独立去运行,都必须包含在
这个独立事情中的事务里面去运行

--------------------------------------------------------------------------
PROPAGATION_NEVER:

the current method should not run within a transactional context, if there
is an existing transaction in progress, an exception will be thrown .
这个方法甚至不能够在事务环境中运行,如果在的话,就抛出异常
----------------------------------------------------------------------------
PROPAGATION_NESTED:嵌入事务(这个是spring新加的)
the method should be run within a nested transaction if an existing transaction
is in progress. The nested transaction can be committed and rolled back individually
 from the enclosing transaction . If no enclosing transaction exists , behaves
 like PROPAGATION_REQUIRED.Beware that vendor support for this propagation behavior
 is spotty at best. Consult the documentation for your resource manager to determine
 if nested transaction are supported.
-----------------------------------------------------------------------------
REQUIRED、REQUIRED_NEW、MANDATORY这三种要求方法一定要在事务中!其他的三种就未必了。
上面的六种属于事务的传播行为

-----------------------------------------------------------------------------
TransactionDefinition 接口中定义以上事务属性,这包括代表事务属性
的常量和获取事务属性的方法

DefaultTransactionDefinition 给出了TransactionDefinition
接口的默认实现,事务属性为
(PROPAGATION_REQUIRED, ISOLATION_DEFAULT, TIMEOUT_DEFAULT, readOnly=false)
可以向PlatformTransactionManager的getTransaction传入此参数,
以获取TransactionStatus对事务进行管理。
-----------------------------------------------------------------------------
事务的隔离级别:
不可重复读:没有锁行所导致。第一次读到的字段值是10,后面由于其他操作修改了这一字段导致值不是10了……
幻读:没有锁表所导致。比如第一次读出这个表有10条记录,然后第二次再读的时候发现不是10条记录了
脏读:一个事务读到另外一个事务的中间数据,一般的数据库都只是防止到脏读而已。

详细来说就是下面的这些:

脏读(Dirty read):一个事务读取了另一个事务未提交的数据。当其它事务未对操作数据加锁时发生。
不可重复读(Nonrepeatable read):事务执行过程中,其它事务更改了当前事务操作的数据,
        导致前后记取的字段值不一致。当前事务未对操作记录加锁时发生。
幻读(Phantom read):事务执行过程中,其它事务向当前事务操作表加入数据。
     当前事务未对操作表加锁时发生。

五种事务隔离级别具体如下,以前讲jdbc的时候曾经很详细的说过这里,关键还是要搞清楚上述三者之间
的嵌套关系,防止了幻读,你就防止了所有;防止了不可重复读,你就防止了不可重复读和脏读两种情况;
防止了脏读和另外两种读一点关系也没有。

五种隔离级别解释如下:

ISOLATION_DEFAULT:
Use the default issolation level of the underlying datastore.

ISOLATION_READ_UNCOMMITTED:
Allows you to read changes that have not yet been committed.May result in
dirty reads,phantom reads, and nonrepeatable reads.

ISOLATION_READ_COMMITTED:
Allows reads from concurrent transactions that have been committed.
Dirty reads are prevented , but phantom and nonrepeatable reads may
still occur.

ISOLATION_REPEATABLE_READ:
Multiple reads of the same field will yield the same result, unless
changed by the transaction itself. Dirty reads and nonrepeatable reads
are prevented by phantom reads may still occur .

ISOLATION_SERIALIZABLE:
This fully ACID_compliant isolation level ensures that dirty reads,
nonrepeatable reads, and phantom reads are all prevented. This is the
slowest of all isolation levels because it is typically accomplished by
doing full table locks on the tables involved in the transaction .

ATOMIC.CONSISTENCY.ISOLATION.DURATION
原子性、一致性、隔离性、持久性,简称ACID

--------------------------------------------------------------------------

设置只读的目的在于由后台数据库优化事务操作,所以只对那些具有事务特性的方法标识为只读才有意义:
PROPAGATION_MANDATORY
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
 PROPAGATION_NESTED
如果是在Hibernate中,只读还将导致flush模式设置为FLUSH_NEVER以保证不向数据库写入数据

---------------------------------------------------------------------------


PROPAGATION,ISOLATION,readOnly,-Exceptions,+Exceptions
传播行为      隔离级别   只读状态     是否回滚

注意“-”是回滚,而且写异常名称的时候不要写包名,直接写类名就行了。
运行时异常才回滚,如果不是运行时异常的话不会给你自动回滚,如果你想要
那些非运行时的异常回滚的话,那么就需要设置

一个典型的例子就是上面bean.xml中的一个配置可以这么写:
<prop key="transferTo">
 PROPAGATION_NEVER, ISOLATION_DEFAULT,readOnly,-SQLException
</prop>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值