Spring声明式事务配置管理方法

来源:互联网 发布:考研有多难放弃知乎 编辑:程序博客网 时间:2024/06/07 03:58

Spring和Hibernate整合后,通过Hibernate API进行数据库操作时发现每次都要opensession,close,beginTransaction,commit,这些都是重复的工作,我们可以把事务管理部分交给spring框架完成。

使用spring管理事务后在dao中不再需要调用beginTransaction和commit,也不需要调用session.close(),使用API  sessionFactory.getCurrentSession()来替代sessionFactory.openSession()

@Repositorypublic class UserDaoImpl implements UserDao {  @Autowired  private SessionFactory sessionFactory;          public User findUserById(int id) {         Session session = sessionFactory.getCurrentSession();         User user = (User)session.get(User.class, id);         session.delete(user);         return user;     } }

采用getCurrentSession()创建的session会绑定到当前线程中,而采用openSession()创建的session则不会。

采用getCurrentSession()创建的session在commit或rollback时会自动关闭,而采用openSession()创建的session必须手动关闭。

使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置:

* 如果使用的是本地事务(jdbc事务)

<property name="hibernate.current_session_context_class">thread</property>

* 如果使用的是全局事务(jta事务)

<property name="hibernate.current_session_context_class">jta</property>

如果采用的时Hibernate4,使用getCurrentSession()必须配置事务,否则无法取到session


 配置事务(xml方式)

applicationContext.xml配置

<!--数据源--><bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">     <property name="driverClass" value="${jdbc.driver}"/>     <property name="jdbcUrl" value="${jdbc.url}"/>     <property name="username" value="${jdbc.username}"/>     <property name="password" value="${jdbc.password}"/><!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->      <property name="idleConnectionTestPeriod" value="60"/><!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->      <property name="idleMaxAge" value="60"/> <!-- 每个分区最大的连接数 -->      <property name="maxConnectionsPerPartition" value="10"/><!-- 每个分区最小的连接数 -->      <property name="minConnectionsPerPartition" value="2"/> <!-- 分区数 ,默认值2,最小1,推荐3-4,视应用而定-->      <property name="partitionCount" value="2"/><!-- 每次去拿数据库连接的时候一次性要拿几个,默认值:2 -->      <property name="acquireIncrement" value="3"/><!-- 缓存prepared statements的大小,默认值:0 -->      <property name="statementsCacheSize" value="30"/><!-- 每个分区释放链接助理进程的数量,默认值:3,除非你的一个数据库连接的时间内做了很多工作,不然过多的助理进程会影响你的性能 -->      <property name="releaseHelperThreads" value="3"/>     <property name="poolAvailabilityThreshold" value="10"/></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">     <property name="dataSource">       <ref local="dataSource" />   </property></bean><!-- 定义Hibernate Session工厂 --><bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">      <property name="dataSource" ref="dataSource" />       <property name="packagesToScan" value="com.cn.entity" /><!-- 如果多个,用“,”分隔 -->       <property name="hibernateProperties">                  <props>                     <prop key="hibernate.dialect">${hibernate.dialect}</prop>                     <prop key="hibernate.cache.use_second_level_cache">false</prop>                     <prop key="hibernate.cache.use_query_cache">false</prop>                     <prop key="hibernate.default_batch_fetch_size">30</prop>                     <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>                     <prop key="current_session_context_class">thread</prop>                   </props>      </property>  </bean>                    <!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到--><bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">        <property name="sessionFactory" ref="sessionFactory"></property>                                                                                   </bean><!--配置事务传播特性--><tx:advice id="txAdvice" transaction-manager="txManager">     <tx:attributes>               <tx:method name="save*" propagation="REQUIRED" />                <tx:method name="add*" propagation="REQUIRED" />     <tx:method name="del*" propagation="REQUIRED" />         <tx:method name="query*" propagation="REQUIRED" read-only="true" />    <tx:method name="count*" propagation="REQUIRED" read-only="true" />    <tx:method name="*" propagation="SUPPORTS"/>    </tx:attributes></tx:advice><!--配置参与事务的类--><aop:config expose-proxy="true"> <!-- 只对业务逻辑层实施事务 -->  <aop:pointcut id="txPointcut" expression="execution(* com.cn..service..*.*(..))" />    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>


需要注意的地方:
  1、advice(建议)的命名:由于每个模块都会有自己的Advice,所以在命名上需要作出规范,初步的构想就是模块名+Advice(只是一种命名规范)。
  2、tx:attribute标签所配置的是作为事务的方法的命名类型。
       如<tx:methodname="save*"propagation="REQUIRED"/>其中*为通配符,即代表以save为开头的所有方法,即表示符合此命名规则的方法作为一个事务。
     propagation="REQUIRED"代表支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
    3、aop:pointcut标签配置参与事务的类,由于是在Service中进行数据库业务操作,配的应该是包含那些作为事务的方法的Service类。
     首先应该特别注意的是id的命名,同样由于每个模块都有自己事务切面,所以我觉得初步的命名规则因为all+模块名+ServiceMethod。而且每个模块之间不同之处还在于以下一句:
     expression="execution(*com.test.testAda.test.model.service.*.*(..))"
     其中第一个*代表返回值,第二*代表service下子包,第三个*代表方法名,“(..)”代表方法参数。
    4、aop:advisor标签就是把上面我们所配置的事务管理两部分属性整合起来作为整个事务管理
 
      5、 getCurrentSession()与openSession()的区别? 
 采用getCurrentSession()创建的session会绑定到当前线程中,而采用openSession()创建的session则不会 
 采用getCurrentSession()创建的session在commit或rollback时会自动关闭,而采用openSession创建的session必须手动关闭 
      6、使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置:
  如果使用的是本地事务(jdbc事务) <property name=”hibernate.current_session_context_class”>thread</property> 
  如果使用的是全局事务(jta事务)  <property name=”hibernate.current_session_context_class”>jta</property>


采用编程式事务:


1、采用声明式事务配置 
* 配置SessionFactory
* 配置事务管理器
* 事务的传播特性
* 那些类哪些方法使用事务


2、编写业务逻辑方法
* 继承HibernateDaoSupport类,使用HibernateTemplate类持久化,HibernateTemplate是Hibernate session的封装
* 默认的回滚是RuntimeException(包括继承RuntimeException的子类),普通异常不回滚
* 在编写业务逻辑方法时,最好将异常一直往上抛出,在呈现层处理(struts)
* spring的事务需要设置到业务方法上(事务边界定义到Facade类上),不要添加到Dao上


3、了解事务的集中传播忒性
*PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启。
*PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
*PROPAGATION_MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
*PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果一个事务存在,则将这个存在的事务挂起。
*PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。
*PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。
*PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动事务,则按TransactionDefinition.PROPAGATION_REQUIRED属性执行


4、Spring事务的隔离级别
*ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。
*ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
*ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
*ISOLATION_REPEATALBE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻想读。它除了保证一个事务不能读取另外一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
  *ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不课重复读外,还避免了幻想读。


配置事务(声明方式)

需要在xml配制中设置<tx:annotation-driven transaction-manager="transactionManager"

<!--数据源--><bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">     <property name="driverClass" value="${jdbc.driver}"/>     <property name="jdbcUrl" value="${jdbc.url}"/>     <property name="username" value="${jdbc.username}"/>     <property name="password" value="${jdbc.password}"/><!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->      <property name="idleConnectionTestPeriod" value="60"/><!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->      <property name="idleMaxAge" value="60"/> <!-- 每个分区最大的连接数 -->      <property name="maxConnectionsPerPartition" value="10"/><!-- 每个分区最小的连接数 -->      <property name="minConnectionsPerPartition" value="2"/> <!-- 分区数 ,默认值2,最小1,推荐3-4,视应用而定-->      <property name="partitionCount" value="2"/><!-- 每次去拿数据库连接的时候一次性要拿几个,默认值:2 -->      <property name="acquireIncrement" value="3"/><!-- 缓存prepared statements的大小,默认值:0 -->      <property name="statementsCacheSize" value="30"/><!-- 每个分区释放链接助理进程的数量,默认值:3,除非你的一个数据库连接的时间内做了很多工作,不然过多的助理进程会影响你的性能 -->      <property name="releaseHelperThreads" value="3"/>     <property name="poolAvailabilityThreshold" value="10"/></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">     <property name="dataSource">       <ref local="dataSource" />   </property></bean><!-- 定义Hibernate Session工厂 --><bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">      <property name="dataSource" ref="dataSource" />       <property name="packagesToScan" value="com.cn.entity" /><!-- 如果多个,用“,”分隔 -->       <property name="hibernateProperties">                  <props>                     <prop key="hibernate.dialect">${hibernate.dialect}</prop>                     <prop key="hibernate.cache.use_second_level_cache">false</prop>                     <prop key="hibernate.cache.use_query_cache">false</prop>                     <prop key="hibernate.default_batch_fetch_size">30</prop>                     <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>                     <prop key="current_session_context_class">thread</prop>                   </props>      </property>  </bean>                    <!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到--><bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">        <property name="sessionFactory" ref="sessionFactory"></property>                                                                                   </bean>  <!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到-->                                                       <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property>                                                               </bean><!--注解-->  <tx:annotation-driven transaction-manager="txManager"/>  




 事物注解方式: @Transactional
当标于类前时,标示类中所有方法都进行事物处理,以下代码在service层进行事务处理(给Service层配置事务是比较好的方式,因为一个Service层方法操作可以关联到多个DAO的操作。在Service层执行这些Dao操作,多DAO操作有失败全部回滚,成功则全部提交。)


public class UserServiceImpl implements UserService {    @Autowired      private UserDao userDao;          public User getUserById(int id) {         return userDao.findUserById(id);     } }

 当类中某些方法不需要事物时:

@Service @Transactional public class UserServiceImpl implements UserService {    @Autowired      private UserDao userDao;          @Transactional(propagation = Propagation.NOT_SUPPORTED)    public User getUserById(int id) {       return userDao.findUserById(id);    } }

@Transactional(propagation=Propagation.REQUIRED) 
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) 
容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) 
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) 
必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) 
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) 
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
事物超时设置:
@Transactional(timeout=30) //默认是30秒
事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)
读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)
串行化
MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED
脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据

原创粉丝点击