23_传智播客Spring2.5视频教程_使用Spring注解方式管理事务与传播行为详解
前面讲解到使用@Transactionl对业务方法进行事务管理;执行业务方法前打开事务,执行完毕后关闭事务;那么,
Q:Spring什么时候决定事务提交,什么时候决定事务回滚(失败)呢?
A:Spring默认时对运行期例外回滚,对用户例外(比如check)不回滚;
check例外,一个方法抛出的例外需要调用者捕获的话,就是check例外;
一个方法抛出的例外,不需要调用者捕获的话,就是非check例外;如运行期例外;
—————
运行期例外 // unchecked;默认回滚
Service层代码:
public void delete( Integer personid ){
    jdbcTemplate.update(“delete from person where id=?”, new Object[]{personid} ,
    new int[] {java.sql.Types.INTEGER});    
    throw new RuntimeException(“运行期例外”);

}

此时单元测试时调用是不需要check该异常的
@Test public void delete(){
    personService.delete(6);
}
 
需要check的例外 // checked; 默认不回滚
Service层代码: 
public void delete( Integer personid ) throws Exception{
    jdbcTemplate.update(“delete from person where id=?”, new Object[]{personid} ,
    new int[] {java.sql.Types.INTEGER});    
    throw new Exception(“非运行期例外”);

}

单元测试如下:
@Test public void delete(){
    try{
        personService.delete(6);
    }catch (Exception e){
        e.printStackTrace();
    }
}
如果需要check的例外需要回滚,则需要设置@Transactional参数
@Transactional ( rollbackFor=Exception.class )
public void delete( Integer personid ) throws Exception{
    jdbcTemplate.update(“delete from person where id=?”, new Object[]{personid} ,
    new int[] {java.sql.Types.INTEGER});    
    throw new Exception(“非运行期例外”);

}

当某个方法不需要事务是怎么处理?
比如,getPerson(Integer personid)方法就不需要开启事务。开启事务是需要消耗性能的;
这时,我们需要是使用@Transactional(propagation=Propagation.NOT_SUPPORTED)来指明该方法不需要事务处理;NOT_SUPPORTED为事务传播属性;
事务传播属性
REQUIRED:业务方法需要在一个事务中运行,如果方法运行时已经处在一个事务中,那么如何加入到该事务,否则为自己创建一个新事务;// 默认80%都是这种事务传播行为;
NOT_SUPPORTED:声明方法不需要事务,如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,知道方法执行结束,新事务才算结束,原先的事务才会恢复执行。
MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
SUPPORTS:这一事务属性表明,如果业务方法在某个事物范围内被调用,则方法称为该事务的一部分。如果业务方法在事务范围外被调用,则方法在某有事务的环境下执行。
Never:执行业务方法绝对不能在事务范围内执行。如果业务方法在某个事物中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行,它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效;
另,
前几种REQUIREDNOT_SUPPORTEDREQUIRESNEWMANDATORYSUPPORTSNever在EJB中也有体现(Spring借鉴EJB的地方);最后一种NESTED为Spring独有;
—————-
事务传播属性NESTED介绍
@Resource OtherService otherService;
public void xxx(){
    stmt.executeUpdate(“update person set name=’888′ where id = 1″);
    otherService.update();// OtherService的update方法的事务传播属性为NESTED
    stmt.executeUpdate(“delete from person where id = 9″);
}
上面的xxx()方法事务在内部执行的过程
Connection conn = null;
try {
    conn.setAutoCommit(false);
    Statement stmt = conn.createStatement();
    stmt.executeUpdate(“update person set name = ’888′ where id = 1″);
    Savepoint savepoint = conn.setSavepoint();
    try{
        conn.createStatement().executeUpdate(“update person set name = ’222′ where sid = 2″);
    }catch(Exception ex){
        conn.rollback(savepoint);
    }
    stmt.executeUpdate(“delete from person where id = 9″);
    conn.commit();
    stmt.close();
}catch (Exception e){
    conn.rollback();
}finally{
    try{
        if(null != conn && !conn.isClosed()) conn.close();
    }catch( SQLException e){
        e.printStackTrace();
    }
}
总结:
内部事务不会影响到外部事物的结果;
但外部事务如果失败,内部事务同样也要回滚;
—————-
数据库系统提供了四种事务隔离级别
数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最低。大多数据库默认的隔离界别为Read Commited,如SQL Server 2000,当然也有少部分数据库默认的隔离级别为Repeatable Read,如Mysql;SQL Server 2005提供了默认的Read Commited的隔离级别,当然也通过快照技术提供了Repeatable Read隔离级别(推荐使用后者)的支持;
  • Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读);
  • Read Commited:读提交数据(会出现不可重复读和幻读);
  • Repeatable Read:可重复读(会出现幻读)
  • Serializable:串行化
脏读:一个事务读取到另一个事务未提交的更新数据;
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已经提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已经提交的更新数据。
—————-
快照技术实现可重复读
—————-
代码:
创建表:person.sql
/*
Navicat MySQL Data Transfer
 
Source Server : localhost
Source Server Version : 50133
Source Host : localhost:3306
Source Database : itcast
 
Target Server Type : MYSQL
Target Server Version : 50133
File Encoding : 65001
 
Date: 2010-07-23 15:54:58
*/
 
SET FOREIGN_KEY_CHECKS=0;
– —————————-
– Table structure for `person`
– —————————-
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
– —————————-
– Records of person
– —————————-
PersonServiceBean.java
package cn.itcast.service.impl;
 
import cn.itcast.bean.Person;
import cn.itcast.service.PersonService;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
 
/**
 *
 * @author kang.cunhua
 */
@Transactional
public class PersonServiceBean implements PersonService {
 
    private JdbcTemplate jdbcTemplate;
 
    @Override
    public void save(Person person) {
        System.out.println(“俺是save(Person person)方法!”);
        jdbcTemplate.update(“insert into person(name) values(?)”,
                new Object[]{person.getName()},
                new int[]{java.sql.Types.VARCHAR});
 
    }
 
    @Override
    public void update(Person person) {
        System.out.println(“俺是update(Person person)方法!”);
        jdbcTemplate.update(“update person set name=? where id=?”,
                new Object[]{person.getName(), person.getId()},
                new int[]{java.sql.Types.VARCHAR, java.sql.Types.INTEGER});
 
    }
 
    @Override
    public Person getPerson(Integer personid) {
        System.out.println(“俺是getPerson(Integer personid)方法!”);
        return (Person) jdbcTemplate.queryForObject(“select * from person where id=?”, new Object[]{personid},
                new int[]{java.sql.Types.INTEGER},
                new PersonRowMapper());
    }
 
    @Override
    public List<Person> getPersons() {
        System.out.println(“俺是getPersons()方法!”);
        return (List<Person>) jdbcTemplate.query(“select * from person”, new PersonRowMapper());
    }
 
    @Override
    public void delete(Integer personid) {
        System.out.println(“俺是delete(Integer personid)方法!”);
        jdbcTemplate.update(“delete from person where id=?”,
                new Object[]{personid},
                new int[]{java.sql.Types.INTEGER});
        throw new RuntimeException(“运行期例外”); // 抛出运行期例外,测试事务回滚!
    }
 
    /**
     * @param dataSource the dataSource to set
     */
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
}
PersonServiceBeanTest.java
package cn.itcast.service.impl;
 
import cn.itcast.bean.Person;
import cn.itcast.service.PersonService;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
/**
 *
 * @author kang.cunhua
 */
public class PersonServiceBeanTest {
 
    private static PersonService personService;
 
    public PersonServiceBeanTest() {
    }
 
    @BeforeClass
    public static void setUpClass() throws Exception {
        ApplicationContext cxt = new ClassPathXmlApplicationContext(“beans.xml”);
        personService = (PersonService) cxt.getBean(“personService”);
    }
 
    @AfterClass
    public static void tearDownClass() throws Exception {
    }
 
    @Before
    public void setUp() {
    }
 
    /**
     * Test of save method, of class PersonServiceBean.
     */
    @Test
    public void testSave() {
        for (int i = 0; i < 5; i++) {
            personService.save(new Person(“传智播客” + i));
        }
 
    }
 
    /**
     * Test of getPerson method, of class PersonServiceBean.
     */
    @Test
    public void testGetPerson() {
        Person person = personService.getPerson(2);
        System.out.println(” testGetPerson() :” + person.getName());
    }
 
    /**
     * Test of update method, of class PersonServiceBean.
     */
    @Test
    public void testUpdate() {
        Person person = personService.getPerson(2);
        person.setName(“张**”);
        personService.update(person);
 
    }
 
    /**
     * Test of getPersons method, of class PersonServiceBean.
     */
    @Test
    public void testGetPersons() {
        for (Person person:personService.getPersons()) {
            System.out.println(“testGetPersons():” + person.getName());
        }
    }
 
    /**
     * Test of delete method, of class PersonServiceBean.
     */
    @Test
    public void testDelete() {
        personService.delete(5);
    }
}
另,代码变动不大。其余代码和上一讲一样。主要是事务理念的理解。为今后的应用打下基础。
 

相关日志

, , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
Trackback

本文到目前为止有 1 个回复

  1. 太阳雨 @ 2010-07-30 09:47

    路过

添加回复