Spring——(七)事务管理

2018-11-06 13:59:00     

一、摘要

通过例子说明:

  • 去ATM机取1000块钱,大体有两个步骤:第一步输入密码金额,银行卡扣掉1000元钱;第二步从ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。
  • 如何保证这两个步骤不会出现一个出现异常了,而另一个执行成功呢?事务就是用来解决这样的问题。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。

二、编程式事务处理实现转账

1.配置pom.xml文件引入需要的jar包,新增如下代码:

	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.8.9</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>4.3.6.RELEASE</version>
	</dependency>
	<!-- 连接数据库的依赖 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.18</version>
	</dependency>
    <!-- 第三方C3P0数据库连接池 -->
	<dependency>
		<groupId>com.mchange.c3p0</groupId>
		<artifactId>com.springsource.com.mchange.v2.c3p0</artifactId>
		<version>0.9.1.2</version>
	</dependency>
</dependencies>
<repositories>
	<repository>
		<id>com.springsource.repository.bundles.release</id>
		<name>EBR Spring Release Repository</name>
		<url>http://repository.springsource.com/maven/bundles/release</url>
		<releases>
			<enabled>true</enabled>
		</releases>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</repository>
	<repository>
		<id>com.springsource.repository.bundles.external</id>
		<name>EBR External Release Repository</name>
		<url>http://repository.springsource.com/maven/bundles/external</url>
		<releases>
			<enabled>true</enabled>
		</releases>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</repository>
</repositories>
  • <repositories>标签里边内容是帮助下载第三方C3P0数据库连接池的jar包,网上找的偏方

2.编写dao层,在dao包下新建AccountDao类如下代码:

package dao;

public interface AccountDao {
	
	/**
	 * 汇款
	 * @param outer
	 * @param account
	 */
	public void out(String outer,int account);
	
	/**
	 * 收款
	 * @param iner
	 * @param account
	 */
	public void in(String iner, int account);
}

3.编写接口在service包下新建AccountService接口代码如下:

package service;

public interface AccountService {
	
	/**
	 * 转账
	 * @param outer
	 * @param iner
	 * @param account
	 */
	public void transfer(String outer,String iner,int account);
}

4.在impl包下新建AccountServiceImpl实现类代码如下:

package impl;

import dao.AccountDao;
import service.AccountService;

public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;

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

	@Override
	public void transfer(String outer, String iner, int account) {
		accountDao.out(outer, account);
		accountDao.in(iner, account);
	}
}

5.在impl包下新建AccountDaoImpl类代码如下:

package impl;

import org.springframework.jdbc.core.support.JdbcDaoSupport;
import dao.AccountDao;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

	/*
	 * 根据用户名减少账户金额
	 */
	@Override
	public void out(String outer, int account) {
		this.getJdbcTemplate().update("update transaction set account = account - ? where name = ?", account, outer);
	}

	/*
	 * 根据用户名增加账户金额
	 */
	@Override
	public void in(String iner, int account) {
		this.getJdbcTemplate().update("update transaction set account = account + ? where name = ?", account, iner);
	}

}

6.修改spring的xml文件如下配置:

<?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:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">
	<!-- 引入外部文件 -->
    <context:property-placeholder location="classpath:spring/db.properties"/>
    <!-- c3p0连接池 -->
	<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 设置连接字符串 -->
        <property name="driverClass" value="${driverClass}" />
		<property name="jdbcUrl" value="${jdbcUrl}" />
		<property name="user" value="${user}" />
		<property name="password" value="${password}" />
	</bean>
	<!-- 配置dao,注入jdbctemplate 对象-->
	<bean name="accountDaoImpl" class="impl.AccountDaoImpl">
		<!-- 注入数据源,才拥有jdbctemplate -->
		<property name="dataSource" ref="dataSource" />
	</bean>
	<!-- 配置service -->
	<bean name="accountServiceImpl" class="impl.AccountServiceImpl">
		<!-- 注入dao -->
		<property name="accountDao" ref="accountDaoImpl" />
	</bean>
	<!-- 第一步:配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
	<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 第二步:定义通知,通知中要处理的就是事务 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
			<tx:attributes>
				<!-- method定义的方法都会添加事务管理 -->
				<tx:method name="transfer" isolation="DEFAULT"    propagation="REQUIRED" timeout="-1" read-only="false"/>
				<!-- 支持通配符:切面中 方法名字符合下面规则的方法都会添加事务管理  -->
           		<tx:method name="save*"/>
            	<tx:method name="update*"/>
            	<tx:method name="delete*"/>
            	<tx:method name="find*" read-only="true"/>
			</tx:attributes>
	</tx:advice>
	<!-- 第三步:配置切入点,让通知关联切入点,即事务控制业务层的方法 -->
	<aop:config>
        <!-- 切入点 -->
        <aop:pointcut expression="bean(*Impl)" id="txPointcut"/>
        <!-- 切面 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
</beans>
  • 注意要引入xmlns:aop xmlns:tx的命名空间

7.在spring的xml文件的同级目录新建db.properties文件用于连接数据库,代码如下:

driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/transaction
user=root
password=123456

8.在test包下新建TestTrans测试类并用注解的方式配置,如下代码:

package test;

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;

import service.AccountService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("../spring/application-config.xml")
public class TestTrans {
	@Autowired
	private AccountService accountService;

	@Test
	public void TestOne() {
		accountService.transfer("Cubi", "Bounds", 100);
		System.out.println("转账成功。。。");
	}
}
  • 运行测试后,Cubi减少了100块,而Bounds增加了100块。
  • 此时模拟一个异常,在Cubi减少100块后,发生异常。在AccountServiceImpl实现类中新增一个int i=1/0的异常,代码如下:

package impl;

import dao.AccountDao;
import service.AccountService;

public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;

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

	@Override
	public void transfer(String outer, String iner, int account) {
		accountDao.out(outer, account);
		int i=1/0;
		accountDao.in(iner, account);
	}
}
  • 正常情况下应该是,Cubi减少100块,Bounds不增不减。测试后发现Cubi没有减少100块,Bounds也没有增加100块,就好像什么事都没有发生一样,这正是我们要的效果

三、声明式事务处理实现转账

1.修改spring的xml文件如下代码:

<?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:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">
	<!-- 引入外部文件 -->
    <context:property-placeholder location="classpath:spring/db.properties"/>
    <!-- c3p0连接池 -->
	<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 设置连接字符串 -->
        <property name="driverClass" value="${driverClass}" />
		<property name="jdbcUrl" value="${jdbcUrl}" />
		<property name="user" value="${user}" />
		<property name="password" value="${password}" />
	</bean>
	<!-- 配置bean注解扫描 -->
    <context:component-scan base-package="impl"/>
	<!-- 第一步:配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
	<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 默认的平台事务管理器的名字叫:transactionManager,如果与自定义的事务管理器id相同,transaction-manager="transactionManager"可以省略 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

2.修改AccountDaoImpl类为注解方式,如下代码:

package impl;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Component;

import dao.AccountDao;

@Component("accountDaoImpl")
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
	
	/**
	 * 当初始化dao的时候,会调用该方法,通过set方法的形参注入数据源
	 * @param dataSource
	 */
	@Autowired
    public void setSuperDataSource(DataSource dataSource){
        //调用父类的setDataSource方法,注入数据源
        super.setDataSource(dataSource);        
    }
	
	/* 
	 * 根据用户名减少账户金额
	 */
	@Override
	public void out(String outer, int account) {
		this.getJdbcTemplate().update("update transaction set account = account - ? where name = ?",account,outer);
	}

	/* 
	 * 根据用户名增加账户金额
	 */
	@Override
	public void in(String iner, int account) {
		this.getJdbcTemplate().update("update transaction set account = account + ? where name = ?",account,iner);
	}

}

3.修改AccountServiceImpl实现类为注解方式,如下代码:

package impl;

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

import dao.AccountDao;
import service.AccountService;
@Component("accountServiceImpl")
public class AccountServiceImpl implements AccountService {
	
	@Autowired
	private AccountDao accountDao;

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

	@Transactional
	@Override
	public void transfer(String outer, String iner, int account) {
		accountDao.out(outer, account);
		accountDao.in(iner, account);
	}
}
  • 测试结果不变

五、总结

事务的四个特性(ACID):

  • 原子性(Atomicity)事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用
  • 一致性(Consistency)一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏
  • 隔离性(Isolation)可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
  • 持久性(Durability)一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中

ps:本篇博客源码下载链接:https://pan.baidu.com/s/11M-pVsPu23Axr8e1t33s4Q 密码:dtsz
Spring——(六)注解方式AOP

一、摘要本篇博客使用上一篇博客的源代码,对上一篇博客进行改造:Spring——(四)AOP面向切面注解方式核心业务功能注解方式周边功能(切面)注解方式测试二、注解方式AOP1.使用@Component注解Student类@Component表示这是一个bean,由Spring进行管理。代码如下:package service; import org.springframework.stereotype.Component; /** * @author bounds * 定义一个核心功能 */ @Component("student") public class Student { /** * 赛跑 */ public void run(String name){ System.out.println(name+"参加100米赛跑。。。"); } } 2.@Aspect注解AsptectTest类表示这是一个切面@Around(value="execution(* service.Student.*(..))")表示对Student类中的所有方法定义为切面。代码如下:package

SpringMVC——(一)注解配置

一、前期准备 jar包准备,你可以从网上下载最新版本的jar包,也可以使用本站提供的jar包: SpringMVC的jar包 二、第一个SpringMVC1.在eclipse中新建项目【SpringMVCTest01】使用dynamic web project的方式,引入jar包到【lib】目录下,并且配置web.xml文件如下代码:<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/

 发表评论