Spring框架学习六:Spring对JDBC的支持

JdbcTemplate简介

为了使JDBC更加易于使用,Spring 在 JDBC API 上定义了一个抽象层,以此建立一个 JDBC 存取框架

作为 Spring JDBC 框架的核心,JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法。每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务。通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取工作量降到最低。

 

在test Schema下创建一张名为 user 的表,表结构如下

 

#添加c3p0数据库连接池
jdbc.user=root
jdbc.password=000
jdbc.jdbcUrl=jdbc:mysql:///test
jdbc.driverClass=com.mysql.jdbc.Driver

jdbc.initPoolSize=5
jdbc.maxPoolSize=10
    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        
        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>
    
    <!-- 配置 Spring 的 jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>    
    </bean>
    
    <!-- 还可以配置 NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参构造器,所以必须为其构造器指定参数 -->
    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="dataSource"></constructor-arg>
    </bean>
package com.bupt.springtest.jdbc;

public class User
{
    private int uid;
    private String name;
    private int age;
    public int getUid()
    {
        return uid;
    }
    public void setUid(int uid)
    {
        this.uid = uid;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public int getAge()
    {
        return age;
    }
    public void setAge(int age)
    {
        this.age = age;
    }
    @Override
    public String toString()
    {
        return "User [uid=" + uid + ", name=" + name + ", age=" + age + "]";
    }
}
package com.bupt.springtest.jdbc;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

public class JDBCTest
{
    ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
    
    //在jdbcTemplate中使用具名参数
    NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
    
    /*
     * 执行INSERT, UPDATE, DELETE
     */
    public void testUpdate() throws SQLException
    {
        String sql = "UPDATE user SET name = ? WHERE uid = ?";
        jdbcTemplate.update(sql, "jimmy", 6);
    }
    
    /*
     * 执行批量更新:批量的INSERT, UPDATE, DELETE
     * 最后一个参数是 Object[]的 List 类型:因为修改一条记录需要一个Object数组,多条即需要多个Object数组
     */
    public void testBatchUpdate()
    {
        String sql = "INSERT INTO user(uid, name, age) VALUES(?,?,?)";
        
        List<Object[]> batchArgs = new ArrayList<>();
        
        batchArgs.add(new Object[]{7, "A", 28});
        batchArgs.add(new Object[]{8, "B", 20});
        batchArgs.add(new Object[]{9, "C", 30});
        
        jdbcTemplate.batchUpdate(sql, batchArgs);
    }
    
    /*
     * 从数据库获取一条记录,实际得到对应的一个对象
     * 类跟表对应,对象跟记录对应
     * 调用queryForObject(String sql, RowMapper<User> rowMapper, Object... args)方法
     * 1. 其中的RowMapper指定如何映射结果集的行,常用实现类为 BeanPropertyRowMapper
     * 2. 使用SQL中列的别名完成列名和类的属性名完成映射
     * 如:若表中定义的name为 user_name,而在User类中定义为name,则sql语句应写为
     * "SELECT uid, user_name name, age FROM user WHERE uid = ?"
     * 3. 不支持级联属性,即表中关联元素不能查询到其他表中的数据
     */
    public void testQuery4Object()
    {
        String sql = "SELECT * FROM user WHERE uid = ?";
        
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
        User user = jdbcTemplate.queryForObject(sql, rowMapper, 1);
        
        System.out.println(user);
    }

    /*
     * 查询实体类的集合
     * 注意调用的不是 queryForList 方法
     */
    public void testQuery4List()
    {
        String sql = "SELECT uid, name, age FROM user WHERE uid > ?";
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
        List<User> users = jdbcTemplate.query(sql, rowMapper, 5);
        
        System.out.println(users);
    }
    
    /*
     * 获取单个列的值或做统计查询
     */
    public void testQueryForObject()
    {
        String sql = "SELECT count(uid) FROM user";
//        String sql = "SELECT name FROM user WHERE uid = ?";
//        String name = jdbcTemplate.queryForObject(sql, String.class, 2);
        long count = jdbcTemplate.queryForObject(sql, Long.class);
        
        System.out.println(count);
    }
    
    /*
     * 在经典的JDBC用法中, SQL参数是用占位符 ? 表示,并且受到位置的限制。定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数绑定
     * 在Spring JDBC 框架中,绑定 SQL 参数的另一种选择是使用具名参数(named parameter),它可以为参数起名字
     * 具名参数:SQL按名字(以冒号开头)而不是按位置进行指定。具名参数更易于维护,也提升了可读性。具名参数由框架类在运行时用占位符取代
     * 具名参数只在NamedParameterJdbcTemplate中得到支持
     * 1. 好处:若有多个参数,则不用去对应位置,直接对应参数名,便于维护
     * 2. 缺点:较为麻烦
     */
    public void testNamedParameterJdbcTemplate()
    {
        String sql = "INSERT INTO user VALUES(:id, :nm, :age)";
        
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("id", 10);
        paramMap.put("nm", "elle");
        paramMap.put("age", 30);
        
        namedParameterJdbcTemplate.update(sql, paramMap);
    }
    
    /*
     * 使用具名参数时,可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作
     * 1. SQL语句中的参数和类的属性一致
     * 2. 使用 SqlParameterSource的 BeanPropertySqlParameterSource 实现类作为参数
     */
    @Test
    public void testNamedParameterJdbcTemplate1()
    {
        String sql = "INSERT INTO user VALUES(:uid, :name, :age)";
        
        User user = new User();
        user.setUid(11);
        user.setName("lock");
        user.setAge(28);
        
        SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);
        namedParameterJdbcTemplate.update(sql, parameterSource);
    }
}

 


 

Spring 的事务管理

作为企业级应用程序框架,Spring 在不同的事务管理 API 之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理 API,就可以使用 Spring 的事务管理机制。

Spring 既支持编程式事务管理,也支持声明式事务管理。

编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事物操作中包含额外的事务管理代码。

声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过 AOP 方法模块化。Spring 通过 Spring AOP 框架支持声明式事务。

Spring 从不同的事务管理 API 中抽象了一整套的事务机制。开发人员不必了解底层事务 API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了。Spring 的核心事务管理抽象是 org.springframework.transaction.PlatformTransactionManager,它为事务管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

通过代码来说明Spring 对事务的支持,也学习下 JdbcTemplate 正真在实际中如何使用,我们先在test数据库中新建三张表

   account表              book表          book_stock表

    

    <context:component-scan base-package="com.bupt.springtest.tx"></context:component-scan>

    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置c3p0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
    </bean>
    
    <!-- 配置Spring的 jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>    
    </bean>
package com.bupt.springtest.tx;

public interface BookShopDao
{
    //根据书号获取书的单价
    public int findBookPriceByIsbn(String isbn);
    //更新书的库存,使书号对应的库存减1
    public void updateBookStock(String isbn);
    //更新用户的账户余额:使 username 的 balance - price
    public void updateUserAccount(String username, int price);
}
package com.bupt.springtest.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImp implements BookShopDao
{
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public int findBookPriceByIsbn(String isbn)
    {
        String sql ="SELECT price FROM book WHERE isbn = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn)
    {
        //检查书的库存是否足够,若不够,则抛出异常
        String sql1 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql1, Integer.class, isbn);
        if(stock <= 0)
        {
            throw new RuntimeException("库存不足!");
        }
        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
        jdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price)
    {
        //验证余额是否足够,若不够,则抛出异常
        String sql1 = "SELECT balance FROM account WHERE username = ?";
        int balance = jdbcTemplate.queryForObject(sql1, Integer.class, username);
        if(balance < price)
        {
            throw new RuntimeException("余额不足!");
        }
String sql
= "UPDATE account SET balance = balance - ? WHERE username = ?"; jdbcTemplate.update(sql, price, username); } }
package com.bupt.springtest.tx;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//对单个操作事务的测试
public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopDao bookShopDao = ctx.getBean(BookShopDao.class); @Test public void testBookShopDaoUpdateBookStock() { bookShopDao.updateBookStock("1001"); } public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); } public void testBookShopDaoUpdateUserAccount(){ bookShopDao.updateUserAccount("Tom", 100); } }

上面代码分别测试了单个操作事务,现在我们把上面的操作组成一个完整的事务过程,即买书 —> 更新书库存 —> 更新账户余额

package com.bupt.springtest.tx;

public interface BookShopService
{
    //买书事务
    public void purchase(String username, String isbn);
}
package com.bupt.springtest.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("bookShopService")
public class BookShopServiceImp implements BookShopService
{
    @Autowired
    private BookShopDao bookShopDao;
    
    @Override
    public void purchase(String username, String isbn)
    {
        //获取书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        bookShopDao.updateBookStock(isbn);
        //更新用户余额
        bookShopDao.updateUserAccount(username, price);
    }
}
package com.bupt.springtest.tx;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTransactionTest
{
    ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    BookShopService bookShopService = ctx.getBean(BookShopService.class);
    
    @Test
    public void testBookShopService()
    {
     //执行此代码前,先将account表中的balance改为60,即使用户余额不足以购买该书 bookShopService.purchase(
"Tom", "1001"); } }

执行测试之后我们可以看到,这个操作并不满足事务的要求,因为书的库存更新了但账户里的钱没有变,及违背了原子性。这种情况下,我们就需要 Spring 的事务管理来帮我们解决。

 

 

用事务通知声明式地管理事务

Spring 允许简单地使用 @Transactional 注解来标注事务方法。为了将 方法定义为支持事务处理,可以为方法添加 @Transactional 注解,根据 Spring AOP 基于代理机制,只能标注公有方法。可以在方法或者类级别上添加 @Transactional 注解,当把这个注解应用到类上时,这个类中的所有公有方法都会被定义为支持事务处理

在 bean 配置时,只需要在配置文件中启用 <tx:annotation-driven> 元素,并为之指定事务管理器就可以了。如果事务处理器名称是 transactionManager,就可以在 <tx:annotation-driven> 元素中省略 transaction-manager 属性。这个元素会自动检测该名称的事务处理器。

 

默认情况下,如果被注解的数据库操作方法发生了 Unchecked Exception 或 Error,所有的数据库操作将回滚;如果发生的是 Checked Exception,默认情况下数据库操作还是会提交的。

 

我们也可以来改变默认的规则:

1. 让 Checked Exception 也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)

2. 让 Unchecked Exception 不回滚:@Transactional(noRollbackFor=RuntimeException.class)

3. 不需要事务管理(只查询)的方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

注意:如果异常被 try-catch块捕获了,事物就不会回滚了,若想让事务回滚必须再往外抛异常,即在catch模块中继续往外抛 Unchecked Exception。

 

下面用代码来具体介绍用法

在ApplicationContext.xml文件中增加下面几行代码

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
     
     <!-- 启动注解事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

在 BookShopServiceImp 的 purchase() 方法上增加注解 @Transactional

package com.bupt.springtest.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImp implements BookShopService
{
    @Autowired
    private BookShopDao bookShopDao;
    
    //添加事务注解
    @Transactional
    @Override
    public void purchase(String username, String isbn)
    {
        //获取书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        bookShopDao.updateBookStock(isbn);
        //更新用户余额
        bookShopDao.updateUserAccount(username, price);
    }
}
package com.bupt.springtest.tx;

import java.util.Arrays;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//测试类
public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopService bookShopService = ctx.getBean(BookShopService.class); @Test public void testBookShopService() { bookShopService.purchase("Tom", "1001"); }

由结果可以看出,此时 purchase() 方法满足事务的要求。

 

除了使用上述声明事务的方法, Spring 还可以通过 tx Schema 中定义的 <tx:advice>  元素声明事务通知。

事务管理其实是一种横切关注点,声明了事务通知后,就需要把它与切入点关联起来。由于事务通知是在 <aop:config> 元素外部声明的,所以它无法直接与切入点产生关联。所以必须在 <aop:config> 元素中声明一个增强器通知与切入点关联起来。只有公有方法才能通过 Spring AOP 进行事务管理。

 

下面我们来看配置方法,将前面配置在xml文件中的 <tx:annotation-driven transaction-manager="transactionManager"/> 删掉,增加如下配置代码

     <!-- 声明事务通知,让所有方法都拥有事务 -->
     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="*"/>
        <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 声明事务通知需要通知方法(即需要进行管理的方法) --> <aop:config>
     <!-- 配置事务切点,把事务切入点和事务属性关联起来 --> <aop:pointcut id="bookShopOperation" expression="execution(* com.bupt.springtest.tx.BookShopServiceImp.*(..))"/> <aop:advisor advice-ref="bookShopTxAdvice" pointcut-ref="bookShopOperation"/> </aop:config>

删除 BookShopServiceImp.purchase() 方法上标注的 @Transactional 注解,测试后也能正常执行事务。

 

关于 <tx:method/> 属性的说明

属性 说明
name         方法名的匹配模式,通知根据该模式寻找匹配的方法。该属性可以使用通配符(*)。也可以声明为 (xxx*),即代表以xxx开头的所有方法,表示符合此命名规则的方法作为一个事务(此属性是必须要有的)
propagation 设定事务定义所用的传播级别(默认值为REQUIRED)
isolation 设定事务的隔离级别(默认值为DEFAULT)
timeout 指定事务的超时(单位为秒),默认值为-1
read-only 指定事务是否只读,若为true指示事务是只读的(一般来讲,对于只执行查询的事务你会将该属性设为true,如果出现了更新、插入或是删除语句时只读事务就会失败),默认值为 false
no-rollback-for 以逗号分隔的异常类的列表,目标方法可以抛出这些异常而不会导致通知执行回滚,如:NullPointException,ArithmeticException...
rollback-for 以逗号分隔的异常类列表,当目标方法抛出这些异常时会导致通知执行回滚。默认情况下,该列表为空,因此不在 no-rollback-for列表中的任何运行时异常都会导致回滚  

 

 

 

Spring 中事务的传播特性

当一个事务被另外一个事务调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行

事务的传播行为可以由传播属性来指定,Spring 定义了七种类型的传播行为

传播属性 描述
REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
REQUIRES_NEW 当前的方法必须启动新的事务,并在它自己的事务内运行。如果有事务正在运行,应该将它挂起来
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED 当前方法不应该运行在事务中,如果有运行的事务,将它挂起来
MANDATORY 当前方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。否则,就启动一个新的事务,并在它自己的事务内运行

 

下面通过代码来介绍传播特性的用法

配置文件改回 <tx:annotation-driven transaction-manager="transactionManager"/>

package com.bupt.springtest.tx;

import java.util.List;

//新定义一个接口,表示用户结算操作
public interface Cashier
{
    public void checkout(String name, List<String> isbn);
}
package com.bupt.springtest.tx;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("cashier")
public class CashierImp implements Cashier
{
    @Autowired
    private BookShopService bookShopService;
    
    //定义一个事物为 checkout
    @Transactional
    @Override
    public void checkout(String username, List<String> isbns)
    {
        for(String isbn : isbns)
        {
            bookShopService.purchase(username, isbn);
        }
    }
}

为 BookShopServiceImp.purchase 方法上的 @Transactional 注解添加属性:propagation=Propagation.REQUIRED

@Service("bookShopService")
public class BookShopServiceImp implements BookShopService
{
    @Autowired
    private BookShopDao bookShopDao;
    
    //使用propagation指定事务的传播行为,即当前事务方法被另外一个事物方法调用时
    //如何进行事务,默认取值为REQUIRED
    //添加事务注解
    @Transactional(propagation=Propagation.REQUIRED)
    @Override
    public void purchase(String username, String isbn)
    {
        //获取书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        bookShopDao.updateBookStock(isbn);
        //更新用户余额
        bookShopDao.updateUserAccount(username, price);
    }
}
//测试代码
public class SpringTransactionTest
{
    ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    Cashier cashier = ctx.getBean(Cashier.class);
    
    @Test
    public void testTransactionPropogation()
    {
        cashier.checkout("Tom", Arrays.asList("1001", "1002"));
    }
}

当 bookService 的 purchase() 方法被另外一个事务方法 checkout() 调用时,它默认会在现有事务内运行。这个默认的传播行为就是 REQUIRED。因此从 checkout() 方法的开始到结束这个事务过程中,只有一个事务在执行。这个事务只在 checkout() 方法结束的时候被提交,若此时账户余额不够支付两本书的价格,就导致用户一本书都买不了。这个流程可以用图表示为:

 

 

 

另一种常见的传播行为是 REQUIRES_NEW ,它表示该方法必须要启动一个新事物,并在自己的事务内运行。如果有事务在运行,就应该先把它挂起。相应的代码修改,只需修改 @Transactional 的属性

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    @Override
    public void purchase(String username, String isbn){

 

此时的事务流程可以表示为如下图,此时若账户金钱能买一本但不够两本时,会执行能购买的行为,而对不能购买的行为不执行

 

 

同样在xml配置文件中,我们也可以来设置传播属性

     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" 
             propagation="REQUIRES_NEW"/>
         </tx:attributes>
     </tx:advice>

 

 

事务的隔离级别

Spring 支持的事务隔离别如下表所示

 

隔离级别 描述
DEFAULT 使用地城数据库的默认隔离级别,对大多数数据库来说,默认的隔离级别都是 READ_COMMITED
READ_UNCOMMITTED     允许事务读取未被其他事务提交的变更。脏读、不可重复读和幻读的问题都会出现
READ_COMMITTED 只允许事务读取已经被其他事务提交的变更。可以避免脏读,但不可重复读和幻读问题仍然可能出现
REPEATABLE_READ 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读,但幻读的问题仍然存在
SERIALIZABLE 确保事务可以冲一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有并发问题都可以避免,但性能低下

Oracle 支持 2 种事务隔离级别:READ_COMMITED,SERIALIZABLE

MySql 支持 4 种

 

用法如下代码所示

    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED)
    public void purchase(String username, String isbn){
     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" 
             propagation="REQUIRES_NEW" 
             isolation="READ_COMMITTED""/>
         </tx:attributes>
     </tx:advice>

 

 

设置回滚事务属性

默认情况下只有未检查异常( RuntimeException 和 Error 类型的异常)会导致事务回滚。而受检查异常则不会。(通常情况下我们都是取默认值即可)

事务的回滚规则可以通过 @Transactional 注解的 rollbackFornorollbackFor 属性来定义。这两个属性被声明为 Class[] 类型的,因此可以为这两个属性指定多个异常类,也可以使用属性 rollbackFor=ClassName 和 norollbackFor=ClassName 通过异常类的名字直接指定。

    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            rollbackFor={IOException.class, SQLException.class},
            noRollbackFor=ArithmeticException.class)
    public void purchase(String username, String isbn){

我们还可以在 <tx:method/> 元素中指定回滚规则,此时属性值要填类全名

     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" 
             propagation="REQUIRED" 
             isolation="DEFAULT"
             rollback-for="java.io.IOException, java.sql.SQLExcpetion"
             no-rollback-for="java.lang.ArithmeticExcpeion"/>
         </tx:attributes>
     </tx:advice>

 

 

超时和只读属性

超时事务属性:表示事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源。由于事务可以在行和表上获得锁,因此长事务会占用资源,对整体性能产生影响。

只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。

超时和只读属性可以在 @Transactional 注解中定义。超时属性以秒为单位计算。

    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            rollbackFor={IOException.class, SQLException.class},
            noRollbackFor=ArithmeticException.class,
            readOnly=true,
            timeout=30)
    public void purchase(String username, String isbn){

也可以在 <tx:method/>元素中进行指定

     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" 
             propagation="REQUIRES_NEW" 
             isolation="READ_COMMITTED"
             rollback-for="java.io.IOException, java.sql.SQLExcpetion"
             no-rollback-for="java.lang.ArithmeticExcpeion"
             timeout="30"
             read-only="false"/>
         </tx:attributes>
     </tx:advice>

 

posted on 2016-06-19 17:12  Traveling_Light_CC  阅读(8759)  评论(0编辑  收藏  举报

导航