• 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>
  • 相关阅读:
    prettier 与 eslint 对比
    vscode快捷键补充
    什么是函数式编程
    让Chrome看不了WWDC直播的HLS技术详解
    IPv6启动五年后,距离我们究竟还有多远?
    WebSocket+MSE——HTML5 直播技术解析
    为什么各大厂商要抢先跟进H.265?
    如何通过 WebP 兼容减少图片资源大小
    IPv6,AppStore 审核不是唯一选择它的原因
    为什么非全站升级HTTPS不可?
  • 原文地址:https://www.cnblogs.com/2015110615L/p/5580074.html
Copyright © 2020-2023  润新知