Spring事务(2):声明式事务管理案例-转账(xml、注解)

1 编写转账案例,引出事务管理问题

需求:账号转账,Tom账号取出1000元,存放到Jack账号上

1.1 建表脚本(MySQL)

 CREATE TABLE t_account  (
 id INT(11) NOT NULL AUTO_INCREMENT,
 name VARCHAR(20) NOT NULL,
 money DOUBLE DEFAULT NULL,
 PRIMARY KEY (id)
)  

INSERT INTO `t_account` (`id`, `name`, `money`) VALUES ('1', 'tom', '1000');
INSERT INTO `t_account` (`id`, `name`, `money`) VALUES ('2', 'jack', '1100');
INSERT INTO `t_account` (`id`, `name`, `money`) VALUES ('3', 'rose', '2000');

1.2 新建工程

第一步:新建一个maven项目

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第1张图片

第二步:引入依赖和applicationContext.xml配置文件和log4j.properties文件和db.properties文件:

pom.xml:

    
        
        
            junit
            junit
            4.10
        

        
        
            org.springframework
            spring-context
            4.2.8.RELEASE
        
        
        
            org.springframework
            spring-test
            4.2.8.RELEASE
        

        
        
            org.springframework
            spring-jdbc
            4.2.8.RELEASE
        


        
        
            c3p0
            c3p0
            0.9.1.2
        

        
        
            mysql
            mysql-connector-java
            5.1.6
        

        
        
            org.aspectj
            aspectjweaver
            1.9.1
        

    

applicationContext.xml:



log4j.properties:

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.rootLogger=info, stdout

db.properties:

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.222.156:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.user=root
jdbc.password=123456

第三步:创建IAccountDao接口

创建AccounDaoImpl实现类,实现了IAccountDao接口

账户操作持久层

技术方案:jdbctempate

package com.example.demo.dao;

public interface IAccountDao {
    // 转出
    public void out(String name, Double money);

    // 转入
    public void in(String name, Double money);
}

第四步:建立service层,创建IAccountService接口,编写转账的业务代码:

package com.example.demo.service;

public interface IAccountService {
    //转账业务:
    public void transfer(String outName,String inName,Double money);
}

package com.example.demo.service.impl;

import com.example.demo.dao.IAccountDao;
import com.example.demo.service.IAccountService;

public class AccountServiceImpl implements IAccountService {
    // 注入dao
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    // 转账业务
    public void transfer(String outName, String inName, Double money) {

        // 先转出
        accountDao.out(outName, money);

        // 再转入
        accountDao.in(inName, money);

    }
}

第五步: 将对象配置到spring工厂

applicationContext.xml文件添加配置

    
    
    
    
        
        
        
        
    


    
    
        
        
    

    
        
    

第六步:使用SpringTest进行测试

package com.example.demo.service.impl;

import com.example.demo.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//集成spring测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class AccountServiceImplTest {

    //注入测试对象
    @Autowired
    private IAccountService accountService;


    @Test
    public void testTransfer() {
        accountService.transfer("tom", "jack", 1000d);
    }

}

但是发现问题:

事务管理问题:在Service层没有事务的情况下,如果出现异常,则会转账不成功,数据异常。

在转账方法中添加如下异常:

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第2张图片

运行前:

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第3张图片

运行后:

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第4张图片

事务未生效。

注意:如果不配置事务,那么每一个数据库的操作都是单独的一个事务。

2 XML配置方式添加事务管理(tx、aop元素)

【操作思路】:aop三步走

  1. 确定目标:需要对AccountService 的 transfer方法,配置切入点
  2. 需要Advice (环绕通知),方法前开启事务,方法后提交关闭事务
  3. 配置切面和切入点

配置Advice通知:

Spring为简化事务的配置,提供了****来配置事务管理,也可以理解为该标签是spring为你实现好了的事务的通知增强方案。

    
	
		
	

	
	
	
		
		
			
			
		
	

	
	
		
		
	

使用AccountServiceImplTest.java测试:数据正常!

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第5张图片

事物添加的前后对比

没有添加事务:

两个方法分属不同事务。

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第6张图片

添加事务后:

分属同一事务

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第7张图片

【注意】如果不配置,则走默认的事务(默认事务是每个数据库操作都是一个事务,相当于没事务),所以我们开发时需要配置事务。

3 注解配置方式添加事务管理 @Transactional

步骤:

  1. 在需要管理事务的方法或者类上面 添加@Transactional 注解
  2. 配置注解驱动事务管理(事务管理注解生效的作用)(需要配置对特定持久层框架使用的事务管理器)

创建项目spring_transaction_anntx:

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第8张图片

替换applicationContext.xml中的 配置为注解

改造dao:

package com.example.demo.dao.impl;

import com.example.demo.dao.IAccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;

@Repository("accountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

    //将数据源注入给父类,父类中需要通过数据源创建jdbctemplate
    @Autowired
    public void setSuperDataSource(DataSource dataSource){
        super.setDataSource(dataSource);
    }

    public void out(String name, Double money) {
        String sql = "update t_account set money = money-? where name = ?";
        super.getJdbcTemplate().update(sql, money, name);

    }

    public void in(String name, Double money) {
        String sql = "update t_account set money = money+? where name = ?";
        super.getJdbcTemplate().update(sql, money, name);
    }
}

改造service:

package com.example.demo.service.impl;

import com.example.demo.dao.IAccountDao;
import com.example.demo.service.IAccountService;
import org.springframework.stereotype.Service;

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    // 注入dao
    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    // 转账业务
    public void transfer(String outName, String inName, Double money) {

        // 先转出
        accountDao.out(outName, money);

        // 再转入
        accountDao.in(inName, money);

    }
}

在applicationContext.xml中配置注解扫描:

    
    

测试方法是否能正常运行

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第9张图片

以上步骤全部没问题后,开始配置注解方式的事物管理

第一步:配置 事物管理器:

在applicationContext.xml中,根据选用的持久层框架配置事物管理器:

    
    
        
    

第二步: 在需要管理事物的方法上添加@Transactional注解,表示对该方法进行事物管理

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第10张图片

第三步:在applicationContext.xml中开启事物注解驱动,让@Transactional注解生效



    
    
    
    
        
        
        
        
    

    
    

    
    
        
    

    
    

第四步:测试事物是否正常

Spring事务(2):声明式事务管理案例-转账(xml、注解)_第11张图片

提示:

如果 @Transactional 标注在 Class 上面, 那么将会对这个 Class 里面所有的 public 方法都包装事务方法。等同于该类的每个公有方法都放上了@Transactional。

如果某方法需要单独的事务定义,则需要在方法上加@Transactional来覆盖类上的标注声明。记住:方法级别的事务覆盖类级别的事务(就近原则)

package com.example.demo.service.impl;

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

import com.example.demo.dao.IAccountDao;
import com.example.demo.service.IAccountService;

@Service("accountService")
@Transactional
// 放置在类上表示对该类中所有的方法都进行事物管理
public class AccountServiceImpl implements IAccountService {

   // 注入dao
   @Autowired
   private IAccountDao accountDao;

   // 转账业务
   @Transactional
   public void transfer(String outName, String inName, Double money) {

   	// 先转出
   	accountDao.out(outName, money);

   	// 发生异常
   	int i = 1 / 0;

   	// 再转入
   	accountDao.in(inName, money);

   }

   @Transactional(readOnly = true)
   // 当方法上的事物定义信息和类上的冲突时,就近原则使用方法上的配置
   public Double queryMoney(String name) {
   	// TODO Auto-generated method stub
   	return null;
   }

}

4 小结-xml和注解的选择

XML配置方式和注解配置方式进行事务管理 哪种用的多?

XML方式,集中式维护,统一放置到applicationContext.xml文件中,缺点在于配置文件中的内容太多。

使用@Transactional注解进行事务管理,配置太分散,使用XML进行事务管理,属性集中配置,便于管理和维护

注意:以后的service的方法名字的命名,必须是上面规则,否则,不能被spring事务管理。!!!!

即以save开头的方法,update开头的方法,delete开头的方法,表示增删改的操作,故事务为可写

以find开头的方法,表示查询,故事务为只读

你可能感兴趣的:(spring,spring,xml,java)