Spring(三)事务

Spring 事务

案例

转账案例

IAccountDao 接口

public interface IAccountDao {
    /**
     * 根据名称查询账户实体
     * @param name
     * @return
     */
    Account findByName(String name);

    /**
     *
     * @param account
     */
    void update(Account account);

}

AccountDaoImpl 实现类

@Repository
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private QueryRunner queryRunner;
    @Autowired
    private TxManager tx;

    public Account findByName(String name) {
        try {
            return queryRunner.query(tx.getConnection(), "SELECT * FROM tb_account WHERE name = ?", new BeanHandler<Account>(Account.class), name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public void update(Account account) {
        try {
            queryRunner.update(tx.getConnection(), "UPDATE tb_account set balance=? WHERE name=?", account.getBalance(), account.getName());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

IAccountService接口

public interface IAccountService {
    /**
     * 转账操作
     *
     * @param sourceAccountName 来源账户名
     * @param targetAccountName 目标账户名
     * @param balance           金额
     */
    void transfer(String sourceAccountName,
                  String targetAccountName,
                  Float balance);

    /**
     * 根据名称查询账户信息
     *
     * @param name
     * @return
     */
    Account findByName(String name);
}

AccountServiceImpl实现类

@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    @Autowired
    private TxManager tx;

    public void transfer(String sourceAccountName, String targetAccountName, Float balance) {
        try {
            //开启事务
            tx.begin();
            //查询
            Account sourceAccount = accountDao.findByName(sourceAccountName);
            Account targetAccount = accountDao.findByName(targetAccountName);
            //余额变动
            sourceAccount.setBalance(sourceAccount.getBalance() - balance);
            targetAccount.setBalance(targetAccount.getBalance() + balance);
            //更新
            accountDao.update(sourceAccount);
            int i = 1 / 0;
            accountDao.update(targetAccount);
            //没有报错
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback();
        } finally {
            //事务关闭
            tx.close();
        }
    }

    public Account findByName(String name) {
        return accountDao.findByName(name);
    }
}

自定义事务管理器 使用ThreadLocal

@Component
public class TxManager {
    @Autowired
    private DataSource dataSource;
    //本地存放connection集合 ThreadLocal
    private ThreadLocal<Connection> th = new ThreadLocal<Connection>();

    /**
     * 获取conneciton对象
     *
     * @return
     */
    public Connection getConnection() {
        try {
            Connection connection = th.get();
            if (connection == null) {
                connection = dataSource.getConnection();
                //存储connection
                th.set(connection);
            }
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 开启事务
     */
    public void begin() {
        try {
            getConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public void commit() {
        try {
            getConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 事务回滚
     */
    public void rollback() {
        try {
            getConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭事务
     */
    public void close() {
        try {
            getConnection().close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

配置类

  • SpringConfig.java
//扫描注解
@ComponentScan("com.itheima")
//随着ioc容器加载
@Configuration
@Import(DBConfig.class)

public class SpringConfig {
}
  • DBConfig.java
@Configuration
@PropertySource("db.properties")
public class DBConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public QueryRunner queryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

测试类

//指定单元测试运行器
@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private IAccountService service;
    @Test
    public void testAccountService() {
        //转账操作
        service.transfer("test1", "test2", 5f);
    }
}

使用动态代理优化

JDK代理

使用Java提供的动态代理,InvocationHandler来对代理对象进行增强

//指定单元测试运行器
@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private IAccountService service;
    @Autowired
    private TxManager tx;

    @Test
    public void testAccountService() {
        InvocationHandler invocationHandler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object object = null;

                try {
                    //开启事务
                    tx.begin();
                    //调用原来目标对象方法
                    object = method.invoke(service, args);
                    tx.commit();
                } catch (Exception e) {
                    tx.rollback();
                    e.printStackTrace();
                } finally {
                    tx.close();
                }
                return object;
            }
        };
        //创建代理对象
        IAccountService accountService = (IAccountService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
                service.getClass().getInterfaces(),
                invocationHandler);
        //代理对象工作
        accountService.transfer("test2", "test3", 100f);
        //转账操作
//        service.transfer("test1", "test2", 5f);
    }
}

cglib动态代理

使用cglib创建代理对象

 public void testAccountService() {
        InvocationHandler invocationHandler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object object = null;

                try {
                    //开启事务
                    tx.begin();
                    //调用原来目标对象方法
                    object = method.invoke(service, args);
                    tx.commit();
                } catch (Exception e) {
                    tx.rollback();
                    e.printStackTrace();
                } finally {
                    tx.close();
                }
                return object;
            }
        };
        //创建代理对象
       /* IAccountService accountService = (IAccountService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
                service.getClass().getInterfaces(),
                invocationHandler);
        //代理对象工作
        accountService.transfer("test2", "test3", 100f);
        //转账操作*/
//        service.transfer("test1", "test2", 5f);
        //cglib 创建增强器
        Enhancer enhancer = new Enhancer();
        //设置父类 目标对象对应的类
        enhancer.setSuperclass(AccountServiceImpl.class);
        enhancer.setCallback(invocationHandler);
        service = (AccountServiceImpl) enhancer.create();
        service.transfer("test1","test2",100f);
    }

jdk和cglib两种代理方式的选择

  • 在创建代理实现类时,jdk的速度要高于cglib
    • 当被代理有接口的时候,使用jdk动态代理,因为效率较高
    • 当被代理类没有接口的时候,使用cglib动态代理,只能选择cglib

AOP

image.png

AOP概念

AOP(面向切面编程)是一种思想,它的目的就是在不修改源代码的基础上,对原有功能进行增强

Spring AOP 是对AOP思想的一种实现,Spring底层同时支持jdk和cglib动态代理

Spring会根据被代理的类是否有接口自动选择代理方式:

  • 如果有接口,就采用jdk动态代理
  • 没有接口,就采用cglib的方式

术语

  • 目标对象 被代理类产生的对象
  • 连接点 目标对象中的所有方法
  • 切入点 目标对象中一部分方法(要进行功能增强的那部分)
  • 增强(通知) 具体的功能
  • 织入 将增强功能添加在切入点的过程(动作)
  • 代理对象 经过织入之后产生的对象(最后工作的对象)
  • 切面:切面是一种描述,描述了一件事:一个什么样的增强功能加到了哪个切点的哪个位置上
    • 所以说 切面=切点+增强


      image.png

SpringAOP

  • 引入坐标
 <!--    spring context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <!--        切点表达式解析坐标-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
  • 目标对象
public interface AccountDao {
    void save(Object o);

    //查询所有
    List<Object> findAll();

    //主键查询
    Object findById(Integer id);
}
  • 目标对象实现类
public class AccountDaoImpl implements AccountDao {
    public void save(Object o) {
        System.out.println("save");
    }

    public List<Object> findAll() {
        System.out.println("find All");
        return null;
    }

    public Object findById(Integer id) {
        System.out.println("find by id");
        return null;
    }
}
  • 开发增强
public class Logger {
    public void beforeMethod() {
        System.out.println("方法运行之前");
    }

    public void afterMethod(){
        System.out.println("方法运行之后");
    }
}
  • 配置切面

切面配置文件

  <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
    <bean id="logger" class="com.itheima.log.Logger"/>

    <!--    配置切面-->
    <aop:config>
        <!--
                切点表达式:
                * 代表忽略返回值类型
                com.xxx 代表类型
                * 代表一个或多个参数
                .. 代表0个或多个参数
        -->
        <aop:pointcut id="pt" expression="execution(* com.itheima.dao.impl.*.*(..))"/>
        <!--        切面
        一个打印日志的增强功能,配置到pt的aop:before上
        -->
        <aop:aspect ref="logger">
            <!--            aop:before 代表添加到pt上 切点为pt-->
            <aop:before method="beforeMethod" pointcut-ref="pt"/>
            
        </aop:aspect>
    </aop:config>

SpringAOP配置详解

切点表达式

   <!--
                切点表达式:
                * 代表忽略返回值类型
                com.xxx 代表类型
                * 代表一个或多个参数
                .. 代表0个或多个参数
        -->
        <aop:pointcut id="pt" expression="execution(* com.itheima.dao.impl.*.*(..))"/>

四大通知

四大通知描述的就是增强方法在切点方法的什么位置上执行

  • 前置通知(before):在切点运行之前执行
  • 后置通知(after-returning):在切点正常运行结束之后执行
  • 异常通知(after-throwing):在切点发生异常的时候执行
  • 最终通知(after)在切点的最终执行

根据四大通知的特性我们可以使用一个结构来使用

try{
    前置通知
    //方法运行
    后置通知
}catch(Exception e){
    异常通知
}finally{
    最终通知
}
  • 方法
public class Logger {
    public void beforeMethod() {
        System.out.println("方法运行之前");
    }

    public void afterMethod() {
        System.out.println("方法运行之后");
    }

    public void exceptionMethod() {
        System.out.println("方法异常");
    }

    public void finallyMethod() {
        System.out.println("方法执行到最后");
    }
}
  • 配置
<!--        配置切点-->
        <aop:pointcut id="pt" expression="execution(* com.itheima.dao.impl.*.*(..))"/>
        <!--        切面
        一个打印日志的增强功能,配置到pt的aop:before上
        -->
        <aop:aspect ref="logger">
            <!--            aop:before 代表添加到pt上 切点为pt-->
            <aop:before method="beforeMethod" pointcut-ref="pt"/>
            <aop:after-returning method="afterMethod" pointcut-ref="pt"/>
            <aop:after-throwing method="exceptionMethod" pointcut-ref="pt"/>
            <aop:after method="finallyMethod" pointcut-ref="pt"/>

        </aop:aspect>

环绕通知

环绕通知是一种特殊的通知,允许以编码的形式实现四大通知

  • 方法
 //    环绕通知
    public void arroundMethod(ProceedingJoinPoint pjp) {
        try {
            System.out.println("方法执行之前");
            //切点方法执行
            pjp.proceed();
            System.out.println("方法执行之后");
        } catch (Throwable throwable) {
            System.out.println("异常发生");
            throwable.printStackTrace();
        } finally {
            System.out.println("方法运行到最后");
        }
    }
  • 配置
  <aop:aspect ref="logger">
            <!--            aop:before 代表添加到pt上 切点为pt-->
            <!-- <aop:before method="beforeMethod" pointcut-ref="pt"/>
             <aop:after-returning method="afterMethod" pointcut-ref="pt"/>
             <aop:after-throwing method="exceptionMethod" pointcut-ref="pt"/>
             <aop:after method="finallyMethod" pointcut-ref="pt"/>-->
            <aop:around method="arroundMethod" pointcut-ref="pt"/>
        </aop:aspect>

SpringAOP注解版

  • 添加注解扫描
<!--   注解扫描-->
   <context:component-scan base-package="com.itheima"/>
  • 激活切面自动代理
<!--   激活切面自动代理-->
   <aop:aspectj-autoproxy/>
  • accountDao
@Repository
public class AccountDaoImpl implements AccountDao {
  • logger
@Component
public class Logger {
  • 切面换成注解
@Component
@Aspect //在增强类上配置一个切面注释
public class Logger {
    //@Pointcut 声明切点 execution切点表达式
    @Pointcut("execution(* com.itheima.dao.impl.*.*(..))")
    public void pt() {
    }

    @Before("pt()")
    public void beforeMethod() {
        System.out.println("方法运行之前");
    }

    @AfterReturning("pt()")
    public void afterMethod() {
        System.out.println("方法运行之后");
    }

    @AfterThrowing("pt()")
    public void exceptionMethod() {
        System.out.println("方法异常");
    }

    @After("pt()")
    public void finallyMethod() {
        System.out.println("方法执行到最后");
    }

    //    环绕通知
    @Around("pt()")
    public void arroundMethod(ProceedingJoinPoint pjp) {
        try {
            System.out.println("方法执行之前");
            //切点方法执行
            pjp.proceed();
            System.out.println("方法执行之后");
        } catch (Throwable throwable) {
            System.out.println("异常发生");
            throwable.printStackTrace();
        } finally {
            System.out.println("方法运行到最后");
        }
    }
}

Spring的四大通知一旦全部使用注解,会出现BUG所以推荐使用环绕通知

环绕通知

 @Around("execution(* com.itheima.dao.impl.*.*(..))")
    public void arroundMethod(ProceedingJoinPoint pjp) {
        try {
            System.out.println("方法执行之前");
            //切点方法执行
            pjp.proceed();
            System.out.println("方法执行之后");
        } catch (Throwable throwable) {
            System.out.println("异常发生");
            throwable.printStackTrace();
        } finally {
            System.out.println("方法运行到最后");
        }
    }
  • Spring主配置 SpringConfig
@ComponentScan("com.itheima")//相当于<context:componet-scan base-package="com.itheima"
@EnableAspectJAutoProxy//相当于激活自动代理<aop:aspectj-autoproxy/>
public class SpringConfig {
}

  • 测试
  @Test
    public void testAccountDao() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountDao accountDao = ac.getBean(AccountDao.class);
        accountDao.findAll();
    }

使用Spring的AOP完成事务管理

在上面的基础项目上

目标对象serviceImpl

  • 准备增强
@Component
public class TxManger {

    @Autowired
    private DataSource dataSource;


    //本地存放connection对象的集合
    private ThreadLocal<Connection> th = new ThreadLocal<Connection>();

    //获取到connection对象
    public Connection getConnection() throws SQLException {
        Connection connection = th.get();
        if (connection == null) {
            connection = dataSource.getConnection();
            th.set(connection);
        }
        return connection;
    }


    //开启
    public void begin() {
        try {
            getConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //提交
    public void commit() {
        try {
            getConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //回滚
    public void rollback() {
        try {
            getConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //关闭
    public void close() {
        try {
            getConnection().close();
            th.remove();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
xml版本
  • 配置切点、切面
    <aop:config>
        <!--        配置切点-->
        <aop:pointcut id="pt" expression="execution(* com.itheima.dao.impl.*.*(..))"/>
        <!--        配置切面 切面就是一种功能描述 描述了什么样的一个增强功能加到了哪个切点上-->
        <aop:aspect ref="txManger">
            <aop:before method="begin" pointcut-ref="pt"/>
            <!--            配置正常结束-->
            <aop:after-returning method="commit" pointcut-ref="pt"/>
            <!--            配置异常抛出-->
            <aop:after-throwing method="rollback" pointcut-ref="pt"/>
            <!--            配置最终通知-->
            <aop:after method="close" pointcut-ref="pt"/>

        </aop:aspect>

    </aop:config>

  • 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {

        accountService.transfer("test1",
                "test2",
                1f);
    }
}
  • 环绕——增强方法
/**
     * 环绕方法
     * @param pointcut
     */
    public void aroundMethod(ProceedingJoinPoint pointcut) {
        try {
            //开启事务
            getConnection().setAutoCommit(false);
            pointcut.proceed();
            getConnection().commit();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            try {
                getConnection().rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        } finally {
            try {
                getConnection().close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
  • 配置环绕方法
 <aop:aspect ref="txManger">
            <aop:around method="aroundMethod" pointcut-ref="pt"/>
        </aop:aspect>

注解版

  • applicaitonContext.xml 加入激活自动代理
    <!--注解扫描-->
    <context:component-scan base-package="com.itheima"/>
<!--    激活切面自动代理-->
    <aop:aspectj-autoproxy/>
  • 增强类注解
 /**
     * 环绕通知
     *
     * @param point 切点对象
     */
    @Around("execution(* com.itheima.service.impl.*.*(..))")
    public void aroundMethod(ProceedingJoinPoint point) {
        try {
            //开启事务
            getConnection().setAutoCommit(false);
            //切点方法执行
            point.proceed();
            //正常执行 提交事务
            getConnection().commit();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            try {
                //异常 事务回滚
                getConnection().rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        } finally {
            //最终
            try {
                getConnection().close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

JdbcTemplate

JdbcTemplate是Spring提供的持久层技术,用于操作数据库,底层封装了JDBC

核心类

JdbcTemplate :用于执行增删改查SQL语句

RowMapper 这是一个接口,主要作用就是将数据库返回的记录封装进实体对象ResultSetHandler

核心方法

  • update() 用来执行增删改查语句
  • query() queryForObject() 用来执行查询语句

实例代码

//创建一个JdbcTemplate对象,用来执行增删改查, 需要给一个数据源
JdbcTemplate  jdbcTemplate = new JdbcTemplate(dataSource);
//update方法,用于执行增删改语句
//第一个参数:sql语句   后面的参数:sql语句中的所需要的的值
jdbcTemplate.update("insert into account value(null,?,?)",1,2);
//query或者queryForObject方法,用于执行查询语句
//query 用于查询多条记录,返回一个集合   queryForObject用于查询一条记录,返回一个实体
//第一个参数:sql语句   第二个参数:封装返回值   后面的参数:sql语句中的所需要的的值
jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class)); 
jdbcTemplate.queryForObject("select * from account where aid = ?",  new BeanPropertyRowMapper<Account>(Account.class),  1);

案例:

  • 创建工程 引入maven坐标
 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
  • 创建dao接口
public interface AccountDao {
    Account findByName(String name);

    //更新操作
    void update(Account account);
    //查询所有

    List<Account> findAll();
}
  • 创建dao实现类
@Repository
public class AccountDaoImpl implements IAccountDao {
    //注入jdbctemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public Account findByName(String name) {
        return jdbcTemplate.queryForObject("SELECT * FROM tb_account WHERE name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
    }

    public void update(Account account) {
        jdbcTemplate.update("UPDATE tb_account SET balance=? WHERE name=?", account.getBalance(), account.getName());
    }

    public List<Account> findAll() {
        return jdbcTemplate.query("SELECT * FROM tb_account", new BeanPropertyRowMapper<Account>(Account.class));
    }
}
  • 创建Service接口
@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void transfer(String oriAccountName, String tatgetAccountName, Float balance) {
        Account sourceAccount = accountDao.findByName(oriAccountName);
        Account targetAccount = accountDao.findByName(tatgetAccountName);
        //更新钱
        sourceAccount.setBalance(sourceAccount.getBalance() - balance);
        targetAccount.setBalance(targetAccount.getBalance() + balance);
        //更新数据库
        accountDao.update(sourceAccount);
        accountDao.update(targetAccount);
    }

    public List<Account> findAll() {
        return accountDao.findAll();
    }
}
  • 加入配置文件
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--    扫描注解-->
    <context:component-scan base-package="com.itheima"/>

    <!--    注入数据库datasource-->
    <!--    注入jdbctemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql:///spring_lsn"/>
        <property name="username" value="root"/>
        <property name="password" value="xml123xml"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    </bean>
</beans>
  • 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccountService {
    @Autowired
    private IAccountService service;

    @Test
    public void testAccountService() {
        service.findAll().stream().forEach(account -> System.out.println(account));
    }
}

Spring中的事务管理

事务管理方式

Spring支持两种事务管理方式:编程式事务和声明式事务

  • 编程式事务:将业务代码和业务代码放在一起书写,耦合性高不推荐使用
  • 生命是食物就是将事务代码和业务代码隔离开发,然后通过一段配置让他们组装运行,最后达到控制事务的目的

Spring事务管理相关的API

PlatformTransactionManager

image.png

这是Spring进行事务管理的一个根接口,我们要使用它的实现类做事务管理

mybatis和jdbcTemplate都可以使用它的一个子类(DataSourceTransactionManager)做事务管理

TransactionDefinition

TransactionDefinition这个API是用来做事务定义


image.png
隔离级别
image.png
传播行为
image.png

事务传播行为指的就是当一个业务方法被另一个业务方法调用时,应该如何进行事务控制

TransactionStatus

TransactionStatus代表的是事务的当前状态

三个API之间的关系

PlatformTransactionManager通过读取TransactionDefinition中定义事务信息参数来管理事务

管理之后会产生一些列的TransactionStatus

声明式事务

思路:使用DataSourceTransactionManager作为事务管理器,需要向事务管理器传递一些参数:事务隔离级别、事务传播行为、事务超时、事务是否只读

xml版

  • 准备目标对象[自己开发的业务类]
@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void transfer(String oriAccountName, String tatgetAccountName, Float balance) {
        Account sourceAccount = accountDao.findByName(oriAccountName);
        Account targetAccount = accountDao.findByName(tatgetAccountName);
        //更新钱
        sourceAccount.setBalance(sourceAccount.getBalance() - balance);
        targetAccount.setBalance(targetAccount.getBalance() + balance);
        //更新数据库
        accountDao.update(sourceAccount);
        accountDao.update(targetAccount);
    }

    public List<Account> findAll() {
        return accountDao.findAll();
    }
}

  • 配置事务管理器
<!--向事务管理传递参数的地方-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--
                name  必传参数,它的作用是在切点中再次匹配, 然后控制行为
                isolation="DEFAULT"   设置事务隔离级别
                propagation="REQUIRED"   设置事务传播行为
                read-only="false"   设置事务是否只读
                timeout="-1"   事务超时时长
                no-rollback-for=""   指定哪些异常不回滚
                rollback-for="" 指定哪些异常回滚
            -->
            <!--            <tx:method name="find*" read-only="true" />
                        <tx:method name="get*" read-only="true" />
                        <tx:method name="select*" read-only="true" />
                        <tx:method name="update*"  />
                        <tx:method name="delete*"  />
                        <tx:method name="save*"  />-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
  • 测试
 @Test
    public void testAccountService() {

//        service.findAll().stream().forEach(account -> System.out.println(account));
        service.transfer("test1",
                "test2",
                100f);
    }

注解版本

  • 删除配置文件中的所有aop和tx开头
  • 添加事务注解驱动
    <tx:annotation-driven transaction-manager="transactionManager"/>

  • 使用注解做声明式事务
 @Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,//默认隔离级别
            timeout = -1,
            readOnly = false

    )
    public void transfer(String oriAccountName, String tatgetAccountName, Float balance) {
        Account sourceAccount = accountDao.findByName(oriAccountName);
        Account targetAccount = accountDao.findByName(tatgetAccountName);
        //更新钱
        sourceAccount.setBalance(sourceAccount.getBalance() - balance);
        targetAccount.setBalance(targetAccount.getBalance() + balance);
        //更新数据库
        accountDao.update(sourceAccount);
        accountDao.update(targetAccount);
    }
  • 事务优化

默认配置

@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
  • 纯注解配置
@Configuration
@ComponentScan("com.itheima")
@EnableTransactionManagement
public class SpringConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("xml123xml");
        dataSource.setUrl("jdbc:mysql:///spring_lsn");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource);
        return manager;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容