重温Spring笔记7 - 事务管理

一、事务基本概念

  • 事务是一系列操作组成的工作单元,该工作单元的操作是不可分割的,要么所有操作都完成,要么所有操作都不做。它必须满足ACID特性。即原子性、一致性、隔离性、持久性。
  1. 原子性(atomicity):事务是不可分割的最小工作单元,事务要么全做,要么全不做
  2. 一致性(consistency):一旦事务完成(不过成功与否),系统必须确保它所建模的业务处于一致的状态,显示的数据不应该被破坏。
  3. 隔离性(isolation):并发事务不影响,在一个事务内部的操作对其他事务不产生影响,这需要事务隔离级别来指定隔离性。
  4. 持久性(durable):事务一旦执行成功,塔对数据库数据的改变必须是永久的。

 

二、Spring事务管理抽象

  • Spring事务管理的抽象层主要有三个接口,分别是PlatformTransactionManager、TransactionDefinition、TransactionStatus。
  1. TransactionDefinition:用于描述事务的隔离级别、超时时间、是否只读、传播行为等控制事务具体行为的具体属性。
  2. PlatformTransactionManager:根据TransactionDefinition提供的事务属性信息创建事务
  3. TransactionStatus:描述事务的状态。

 

三、事务管理器

  • Spring并不直接管理事务,而是提供了多种事务管理器,它将事务管理的责任委托给JTA或者其他持久化机制所提供的平台相关的事务实现。
  • DataSourceTransactionManager:位于org.springframework.jdbc.datasource,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架,ibatis或MyBatis框架的事务管理。
<!-- 事务管理器,dataSource是数据源 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
  • HibernateTransactionManage:位于org.springframework.orm.hibernate,提供对单个org.hibernate.sessionFactory事务支持,用于集成hibernate框架是的事务管理,该事务管理器只支持hibernate3以上的版本。
<!-- 事务管理器,sessionFactory是hibernate的session工厂 -->
<bean id="txManager"  class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
  • JpaTransactionManager:位于org.springframework.orm.jpa,提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理。
<!-- 事务管理器,entityManagerFactory指定需要事务管理的javax.persistence.Entity.ManagerFactory对象 -->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
  • JtaTransactionManager:位于org.springframework.transaction.jta,提供了分布式事务管理的支持,并将事务管理委托给java EE 应用服务器管理器。
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> 


四、声明式事务(通过AOP实现)

1、定义事务的属性

  • 传播行为:定义了客户端与调用方法直接的事务边界

事务传播行为类型

说明

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行

PROPAGATION_MANDATORY

事务当前事务,如果当前没有事务,则抛出异常

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,则吧当前事务挂起

PROPAGATION_NO_SUPPORTED

以非事务方式执行操作,如果当前存在事务,则把当前事务挂起

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作

  •  隔离级别:定义了一个事务受其他事务影响的程度

隔离级别

说明

ISOLATION_DEFAULT

使用数据库默认的事务隔离级别(mysql默认是ISOLATION_REPEATABLE_READ;oracle、sql server默认的是)ISOLATION_READ_COMMITTED

ISOLATION_READ_UNCOMMITTED

A事务可以看到B事务未提交的数据,该隔离级别会产生脏读、不可重复读和幻读

ISOLATION_READ_COMMITTED

保证A事务不能读取B事务未提交的数据,避免了脏读,但可能出现不可重复读和幻读

ISOLATION_REPEATABLE_READ

保证A事务不能读取B事务未提交的数据,也避免了脏读和不可重复读,但可能出现幻读

ISOLATION_SERIALIZABLE

最可靠的隔离级别,避免了脏读、不可重复读、幻读

注:(1、脏读:A事务读取了B事务尚未提交的数据;2、不可重复读:A事务读取了B事务提交的修改数据;3、幻读:A事务读取了B事务提交的新增数据

  • 是否只读:设置只读数据库,可以对一些操作进行优化
  • 事务超时:限定事务的时间
  • 回滚原则:设置事务在遇到哪些异常时回滚

2、基于xml的声明式事务(采用Spring JDBC模板)

  • 创建userDao,实现JdbcDaoSupport
public class UserDao extends JdbcDaoSupport {
    //保存用户
    public void saveUser(String userName) throws Exception{
        //创建sql
        String sql = "insert into user(userName) values(?)";
        //执行sql
        this.getJdbcTemplate().update(sql,new Object[]{userName});
    }
    //获取用户
    public User getUser(long id)throws Exception{
        final User user = new User();
        //创建sql
        String sql = "select * from user where id=?";
        //执行sql
        this.getJdbcTemplate().query(sql, new Object[]{id}, new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet rs) throws SQLException {
                user.setId(rs.getLong("id"));
                user.setUserName(rs.getString("username"));
            }
        });
        return user;
    }
}

注:User类有id和userName两变量,数据库也有id和userName两列

  • Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!-- 为spring jdbcTemplate注入数据源 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事务 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!--传播行为:save开头的方法都需要支持事务 -->
            <tx:method name="save*" propagation="REQUIRED"/>
            <!-- 隔离级别:save开头的方法隔离级别都为SERIALIZABLE -->
            <tx:method name="save*" isolation="SERIALIZABLE"/>
            <!-- 是否只读:get开头的方法都是只读-->
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置事务通知器 -->
    <aop:config>
        <!-- 定义切面 -->
        <aop:pointcut id="txPointcut" expression="execution(* com.chensr.test.UserDao.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

    <!-- 定义userDao,并注入jdbcTemplate目标 -->
    <bean id="userDao" class="com.chensr.test.UserDao">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

注: 事务配置中

  • <tx:advice>:事务通知定义,用于指导事务属性,其中transaction-manager属性指定事务管理器。
  • <tx:attributes>:指定具体拦截的方法。
  • <tx:method>:用name属性指定方法,比如name=”save*”表示拦截以save开头的方法。其中method还有五个属性,propatation表示传播行为、isolation表示隔离级别、read-only表示是否只读、timeout表示事物超时、no-rollbace-for表示回滚原则。
  • 创建测试类
public class Test {
    @org.junit.Test
    public void test() throws Exception{
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-context.xml");
        UserDao userDao = (UserDao)ac.getBean("userDao");
        userDao.saveUser("操蛋的特朗普");
        User user = userDao.getUser(1l);
        System.out.println(user.toString());
    }
}

运行结果:数据库中多了一条username为“操蛋的特朗普”的数据,并且控制台打印

       User{id=1, userName='操蛋的特朗普'} 

  • 事务测试,修改save(添加int error =1/0),让程序抛异常
//保存用户
public void saveUser(String userName) throws Exception{

    //创建sql
    String sql = "insert into user(userName) values(?)";
    //执行sql
    this.getJdbcTemplate().update(sql,new Object[]{userName});
    int error = 1/0;
}
  • 运行测试类,会看到控制器报异常,并且数据库并没有添加任何数据(如果没进行事务处理,控制器报异常,但数据库会添加一条数据)。

3、基于注解的声明式事务

  • 基于注解的声明式事务不需要在配置文件中配置事务和通知器。只需开启注解<ontent:component-scan/>,开启基于注解的事务管理<tx:annotation-driven transaction-manager="txManager"/>,然后在需要事务管理的bean上加入@Transactional即可。
  • 修改配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx" 
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

     <!--开启注解 -->
    <content:component-scan base-package="com.chensr">
        <content:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </content:component-scan>

    <!-- 配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!-- 为spring jdbcTemplate注入数据源 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 开启基于注解的事务 -->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>
  • 修改userDao
@Transactional(readOnly = true)
@Repository("userDao")
public class UserDao extends JdbcDaoSupport {

    @Autowired
    public UserDao(JdbcTemplate jdbcTemplate) {
        super.setJdbcTemplate(jdbcTemplate);
    }

    //保存用户
    @Transactional(readOnly = false,isolation = Isolation.SERIALIZABLE)
    public void saveUser(String userName) throws Exception{
        //创建sql
        String sql = "insert into user(userName) values(?)";
        //执行sql
        this.getJdbcTemplate().update(sql,new Object[]{userName});
    }
    //获取用户
    public User getUser(long id)throws Exception{
        final User user = new User();
        //创建sql
        String sql = "select * from user where id=?";
        //执行sql
        this.getJdbcTemplate().query(sql, new Object[]{id}, new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet rs) throws SQLException {
                user.setId(rs.getLong("id"));
                user.setUserName(rs.getString("username"));
            }
        });
        return user;
    }
}

注:由于JdbcTemplate是继承于JdbcDaoSupport得来的,所以在注入JdbcTemplate时,需要向父类注入super.setJdbcTemplate(jdbcTemplate)

  • 运行测试类,测试结果与上面例子一致

五、编程式事务

  • 所谓的编程式事务就是以编码方式实现事务,在配置文件中配置事务管理器,并将配置管理器注入相应的bean中。
  • 配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">


    <!-- 配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!-- 为spring jdbcTemplate注入数据源 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 为userDao注入jdbc模板和事务管理器 -->
    <bean id="userDao" class="com.chensr.test.UserDao">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
        <property name="transactionTemplate" ref="txManager"/>
     </bean>
</beans>
  • 编写UserDao
public class UserDao extends JdbcDaoSupport {

    private TransactionTemplate transactionTemplate;


    //保存用户
    @Transactional(readOnly = false,isolation = Isolation.SERIALIZABLE)
    public void saveUser(final String userName) throws Exception{
        //设置事务
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.setReadOnly(false);
        transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                //创建sql
                String sql = "insert into user(userName) values(?)";
                //执行sql
                 getJdbcTemplate().update(sql,new Object[]{userName});
            }
        });


    }
    //获取用户
    public User getUser(final long id)throws Exception{

        //设置事务
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
        transactionTemplate.setReadOnly(true);

        //返回查询到的数据
        return transactionTemplate.execute(new TransactionCallback<User>() {
                @Override
                public User doInTransaction(TransactionStatus status) {
                    final User user = new User();
                    //创建sql
                    String sql = "select * from user where id=?";
                    //执行sql
                    getJdbcTemplate().query(sql, new Object[]{id}, new RowCallbackHandler() {
                        @Override
                        public void processRow(ResultSet rs) throws SQLException {
                            user.setId(rs.getLong("id"));
                            user.setUserName(rs.getString("username"));
                        }
                    });
                    return user;
                }
            });
    }
    //注入事务管理器
    public void setTransactionTemplate(PlatformTransactionManager transactionTemplate) {
        this.transactionTemplate = new TransactionTemplate(transactionTemplate) ;
    }
}

注:TransactionCallbackWithoutResult用于没有返回结果的事务管理,TransactionCallback<Object>用于有返回结果的事务管理

  • 测试类
public class Test {
    @org.junit.Test
    public void test() throws Exception{
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-context.xml");
        UserDao userDao = (UserDao)ac.getBean("userDao");
        userDao.saveUser("操蛋的特朗普");
        User user = userDao.getUser(1l);
        System.out.println(user.toString());
    }
}

执行结果与上面例子一致

 

注:相互嵌套的方法不在同一线程执行,不同线程的事务是独立的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值