Spring基于AOP的事务管理
Spring基于AOP的事务管理 |
- 事务
事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务就将回到最开始的状态,仿佛一切都没发生过。例如,老生常谈的转账问题,从转出用户的总存款中扣除转账金额和增加转出用户的账户金额是一个完整的工作单元,如果只完成扣除或者增加都会导致错误,造成损失,而事务管理技术可以避免类似情况的发生,保证数据的完整性和一致性。同样在企业级应用程序开发过程中,事务管理技术也是必不可少的。
事务有四个特性:ACID
- 原子性(Atomicity):事务是一个原子操作,有一系列动作组成。原子性保证所有动作都完成,或者不执行任何动作。
- 一致性(Consistency):一旦事务完成(不论成败),系统必须确保它所建模的业务处于一致的状态。
- 隔离性(Isolation):可能有很多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论系统发生生什么系统错误,它的结果都不会受到影响,保证能从系统崩溃中恢复过来,通常事务的结果会被写入到持久化存储器中。
Spring事务是基于面向切面编程(Aspect Oriented Programming,AOP)实现的(文中会简单讲解AOP)。Spring的事务属性分别为传播行为、隔离级别、回滚规则、只读和事务超时属性,所有这些属性提供了事务应用方法和描述策略。如下我们介绍Spring事务管理的三个核心接口。
- 核心接口
- TransactionDefinition接口是事务描述对象,提供获取事务相关信息的方法。
- PlatformTransactionManager接口是平台事务管理器,用于管理事务。
- TransactionStatus接口是事务的状态,描述了某一时间点上事务的状态信息。
关于事务管理器PlatformTransactionManager的详细介绍见:http://www.mamicode.com/info-detail-1248286.html。
- Spring AOP
面向切面编程(Aspect Oriented Programing,AOP)采用横向抽取机制,是面向对象编程(Object Oriented Programing,OOP)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能、权限管理、异常处理等,该类功能往往横向地散布在核心代码当中,这种散布在各处的无关代码被称为横切。AOP恰是一种横切技术,解剖开封装对象的内部,将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为Aspect(切面),所谓切面,简单的说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP术语
- 连接点(Joinpoint):被拦截到的点,该连接点可以是被拦截到的方法、字段或者构造器;
- 切入点(Pointcut):指要对哪些连接点进行拦截,即被拦截的连接点;
- 通知(Advice):指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知;
- 目标(Target):代理的目标对象;
- 织入(Weaving):把增强的代码应用到目标上,生成代理对象的过程;
- 切面(Aspect):切入点和通知的集合。
- 项目实践
接下来我们就利用Spring的事务管理实现如上例子中所述的转账案例,利用mysql数据库创建名称为User的数据库,在User数据库中创建两张表:用户信息表(t_user)、用户存款表(account),然后实现用户A向用户B转账,更新数据库表信息,如果转账失败,则数据库信息自动返回转账前的状态。
在Eclipse下创建Java工程,其中必要的jar包以及工程中的类如下所示,jar包的下载地址为:Spring_AOP.zip。
项目中的类介绍如下:
用户类(User):包含用户基本信息(id,name,password),以及基本信息的get/set方法。
- public class User {
- private int userID; //用户ID
- private String userName; //用户名
- private String password; //用户密码
- public int getUserID() {
- return userID;
- }
- public void setUserID(int userID) {
- this.userID = userID;
- }
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- @Override
- public String toString(){
- return "user ID:" + this.getUserID() + " userName:" + this.getUserName() + " user password:" + this.getPassword();
- }
- }
创建用户的工厂(UserFactory):创建用户的工厂,创建具体用户对象。
- public class UserFactory {
- public User createUser(String name, int id, String password){
- User user = new User();
- user.setUserName(name);
- user.setUserID(id);
- user.setPassword(password);
- return user;
- }
- }
用户数据访问接口(UserDao):定义对用户表(t_user)的基本操作(增、删、改、查)。
- public interface UserDao {
- public int addUser(User user);
- public int updateUser(User user);
- public int deleteUser(User user);
- public User findUserByID(int id);
- public List<User> findAllUser();
- }
用户数据访问实现类(UserDaoImpl):实现接口(UserDao)中定义的方法。
- public class UserDaoImpl implements UserDao{
- private JdbcTemplate jdbcTemplate;
- public void setJdbcTemplate(JdbcTemplate jdbc){
- this.jdbcTemplate = jdbc;
- }
- @Override
- public int addUser(User user) {
- // TODO Auto-generated method stub
- String sql = "insert into t_user(userid,username,password)values(?,?,?)";
- Object[] obj = new Object[]{
- user.getUserID(),
- user.getUserName(),
- user.getPassword()
- };
- return this.execute(sql, obj);
- }
- @Override
- public int updateUser(User user) {
- // TODO Auto-generated method stub
- String sql = "update t_user set username=?,password=? where userid=?";
- Object[] obj = new Object[]{
- user.getUserName(),
- user.getPassword(),
- user.getUserID()
- };
- return this.execute(sql, obj);
- }
- @Override
- public int deleteUser(User user) {
- // TODO Auto-generated method stub
- String sql = "delete from t_user where userid=?";
- Object[] obj = new Object[]{
- user.getUserID()
- };
- return this.execute(sql, obj);
- }
- private int execute(String sql, Object[] obj){
- return this.jdbcTemplate.update(sql, obj);
- }
- @Override
- public User findUserByID(int id) {
- // TODO Auto-generated method stub
- String sql = "select * from t_user where userid=?";
- RowMapper<User> rowMapper = new BeanPropertyRowMapper(User.class);
- return this.jdbcTemplate.queryForObject(sql, rowMapper, id);
- }
- @Override
- public List<User> findAllUser() {
- // TODO Auto-generated method stub
- String sql = "select * from t_user";
- RowMapper<User> rowMapper = new BeanPropertyRowMapper(User.class);
- return this.jdbcTemplate.query(sql, rowMapper);
- }
- }
存款访问接口(AccountDao):定义对存款表(account)的基本操作。
- public interface AccountDao {
- public void addAccount(int id, double account);
- public void inAccount(int id, double account);
- public void outAccount(int id, double account);
- }
存款访问实现类(AccountDaoImpl):实现接口(AccountDao)定义的方法。
- public class AccountDaoImpl implements AccountDao{
- private JdbcTemplate jdbcTemplate;
- public void setJdbcTemplate(JdbcTemplate jdbc){
- this.jdbcTemplate = jdbc;
- }
- @Override
- public void addAccount(int id, double account) {
- // TODO Auto-generated method stub
- String sql = "insert into account values(" + id + "," + account + ")";
- this.jdbcTemplate.execute(sql);
- }
- @Override
- public void inAccount(int id, double account) {
- // TODO Auto-generated method stub
- String sql = "update account set account=account+? where userid=?";
- this.jdbcTemplate.update(sql, account,id);
- }
- @Override
- public void outAccount(int id, double account) {
- // TODO Auto-generated method stub
- String sql = "update account set account=account-? where userid=?";
- this.jdbcTemplate.update(sql, account,id);
- }
- }
存款服务层方法接口(AccountService):定义暴露对外的,提供给用户访问的接口。
- public interface AccountService {
- /*
- * 转账,实现从outUser转出account金额的钱到inUser
- */
- public void transfer(User outUser, User inUser, double account);
- }
存款服务层方法实现类(AccountServiceImpl):实现接口(AccountService)中定义的方法。
- public class AccountServiceImpl implements AccountService{
- private AccountDao accountDao;
- public void setAccountDao(AccountDao accountDao) {
- this.accountDao = accountDao;
- }
- @Override
- public void transfer(User outUser, User inUser, double account){
- // TODO Auto-generated method stub
- this.accountDao.outAccount(outUser.getUserID(), account);
- //模拟程序异常,无法执行inAccount方法
- int i = 1 / 0;
- this.accountDao.inAccount(inUser.getUserID(), account);
- }
- }
创建数据库表的类(CreateTables)
- public class CreateTables {
- //通过JdbcTemplate对象创建表
- private JdbcTemplate jdbcTemplate;
- public void setJdbcTemplate(JdbcTemplate jdbc){
- jdbcTemplate = jdbc;
- }
- public void createTable(String sql){
- jdbcTemplate.execute(sql);
- }
- }
客户端类(Client)如下:
- public class Client {
- public static void main(String[] args) {
- //定义配置文件路径
- String path = "com/jdbc/JdbcTemplateBeans.xml";
- //加载配置文件
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
- //获取CreateTables实例
- CreateTables tables = (CreateTables) applicationContext.getBean("createTables");
- //创建t_user表
- String create_user = "create table t_user(userid int primary key auto_increment, username varchar(20), password varchar(32))";
- tables.createTable(create_user);
- //创建工资表,工资表的userid关联t_user表的userid
- String create_account = "create table account(userid int primary key auto_increment, account double, foreign key(userid) references t_user(userid) on delete cascade on update cascade)";
- tables.createTable(create_account);
- //创建用户
- User user1 = new UserFactory().createUser("张三", 1, "zhangsan");
- User user2 = new UserFactory().createUser("李四", 2, "lisi");
- User user3 = new UserFactory().createUser("王五", 3, "wangwu");
- User user4 = new UserFactory().createUser("赵六", 4, "zhaoliu");
- //获取用户数据访问对象
- UserDao userDao = (UserDao) applicationContext.getBean("userDao");
- System.out.println(userDao.addUser(user1));
- System.out.println(userDao.addUser(user2));
- System.out.println(userDao.addUser(user3));
- System.out.println(userDao.addUser(user4));
- //获取存款数据访问对象
- AccountDao account = (AccountDao) applicationContext.getBean("accountDao");
- account.addAccount(1, 100);
- account.addAccount(2, 290.5);
- account.addAccount(3, 30.5);
- account.addAccount(4, 50);
- AccountService accountService = (AccountService) applicationContext.getBean("accountService");
- accountService.transfer(user1, user3, 10);
- }
- }
最后的也是我们实现Spring AOP最关键的配置文件JdbcTemplateBeans.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-4.3.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context.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-4.3.xsd">
- <!-- 配置数据源 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <!-- 数据库驱动 -->
- <property name="driverClass" value="com.mysql.jdbc.Driver"/>
- <!-- 连接数据库的URL -->
- <property name="jdbcUrl" value="jdbc:mysql://localhost/User"/>
- <!-- 连接数据库的用户名 -->
- <property name="user" value="root"/>
- <!-- 连接数据的密码 -->
- <property name="password" value="123"/>
- </bean>
- <!-- 配置JDBC模板 -->
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <!-- 默认必须使用数据源 -->
- <property name="dataSource" ref="dataSource"/>
- </bean>
- <bean id="createTables" class="com.jdbc.CreateTables">
- <!-- 通过setter方法实现JdbcTemplate对象的注入 -->
- <property name="jdbcTemplate" ref="jdbcTemplate"/>
- </bean>
- <bean id="userDao" class="com.jdbc.UserDaoImpl">
- <property name="jdbcTemplate" ref="jdbcTemplate"/>
- </bean>
- <bean id="accountDao" class="com.jdbc.AccountDaoImpl">
- <property name="jdbcTemplate" ref="jdbcTemplate"/>
- </bean>
- <bean id="accountService" class="com.jdbc.AccountServiceImpl">
- <property name="accountDao" ref="accountDao"/>
- </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>
- <!-- <tx:method> 给切入点添加事务详情
- name:方法名称, *表示任意方法, do* 表示以do开头的方法
- propagation:设置传播行为
- isolation:隔离级别
- read-only:是否只读 -->
- <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
- </tx:attributes>
- </tx:advice>
- <!-- aop编写,让Spring自动对目标进行代理,需要使用AspectJ的表达式 -->
- <aop:config>
- <!-- 切入点 -->
- <aop:pointcut expression="execution(* com.jdbc.AccountServiceImpl.*(..))" id="txPointCut"/>
- <!-- 切面:将切入点和通知整合 -->
- <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
- </aop:config>
- </beans>
启动mysql数据库创建名称为User的数据库,然后运行该Java工程,输出如下所示:
然后去mysql的User数据库中查看刚才生成的表如下:
从控制台输出中,我们得知在代码(int i = 1 / 0)处发生了异常,而在异常发生之前,转出方存款已经发生变化,而通过查看account表发现金额还是输入的状态,User1的金额并没有减少,从而实现了在系统出现异常情况下,事务的回滚。本来想写到这里就结束的,但是总感觉没有把AOP说的特别透彻,于是想通过在转账前后增加日志的方式对AOP做进一步的讲解。
在原来项目的基础上,增加一个日志打印类(LogHandler),该类代码如下:
- public class LogHandler {
- //切入点执行之前需要执行的方法
- public void LogBefore(){
- System.out.println("转账开始时间:" + System.currentTimeMillis());
- }
- //切入点执行结束执行该方法
- public void LogAfter(){
- System.out.println("转账结束时间:" + System.currentTimeMillis());
- }
- }
当然啦,还需要在XML配置文件中增加配置信息:
- <!-- 配置日志打印类 -->
- <bean id="logHandler" class="com.jdbc.LogHandler"/>
- <aop:config>
- <!-- order属性表示横切关注点的顺序,当有多个时,序号依次增加 -->
- <aop:aspect id="log" ref="logHandler" order="1">
- <!-- 切入点为AccountServiceImpl类下的transfer方法 -->
- <aop:pointcut id="logTime" expression="execution(* com.jdbc.AccountServiceImpl.transfer(..))"/>
- <aop:before method="LogBefore" pointcut-ref="logTime"/>
- <aop:after method="LogAfter" pointcut-ref="logTime"/>
- </aop:aspect>
- </aop:config>
然后将AccountServiceImpl类中transfer方法中异常语句(int i = 1 / 0)注释掉,将Client类中的创建表、添加表项的代码也注释掉,再次执行主函数,则显示日志输出,查看转账前后数据库表状态。表如下:
如上就是对Spring AOP事务管理一个简单的介绍,希望能对读者产生一点帮助。
Spring基于AOP的事务管理的更多相关文章
- Spring 的 AOP 进行事务管理的一些问题
AspectJ AOP事务属性的配置(隔离级别.传播行为等): <tx:advice id="myAdvice" transaction-manager="mtTx ...
- Spring MVC 中使用AOP 进行事务管理--XML配置实现
1.今天写一篇使用AOP进行事务管理的示例,关于事务首先需要了解以下几点 (1)事务的特性 原子性(Atomicity):事务是一个原子操作,由一系列动作组成.事务的原子性确保动作要么全部完成,要么完 ...
- 全面分析 Spring 的编程式事务管理及声明式事务管理
开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...
- spring的annotation-driven配置事务管理器详解
http://blog.sina.com.cn/s/blog_8f61307b0100ynfb.html ——————————————————————————————————————————————— ...
- 全面分析 Spring 的编程式事务管理及声明式事务管理--转
开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...
- spring声明式的事务管理
spring支持声明式事务管理和编程式事务管理两种方式. 编程式事务使用TransactionTemplate来定义,可在代码级别对事务进行定义. 声明式事务基于aop来实现,缺点是其最细粒度的事务声 ...
- 12 Spring框架 SpringDAO的事务管理
上一节我们说过Spring对DAO的两个支持分为两个知识点,一个是jdbc模板,另一个是事务管理. 事务是数据库中的概念,但是在一般情况下我们需要将事务提到业务层次,这样能够使得业务具有事务的特性,来 ...
- Spring整合hibernate4:事务管理
Spring整合hibernate4:事务管理 Spring和Hibernate整合后,通过Hibernate API进行数据库操作时发现每次都要opensession,close,beginTran ...
- 使用注解实现Spring的声明式事务管理
使用注解实现Spring的声明式事务管理,更加简单! 步骤: 1) 必须引入Aop相关的jar文件 2) bean.xml中指定注解方式实现声明式事务管理以及应用的事务管理器类 3)在需要添加事务控制 ...
随机推荐
- Microservice架构模式简介
在2014年,Sam Newman,Martin Fowler在ThoughtWorks的一位同事,出版了一本新书<Building Microservices>.该书描述了如何按照Mic ...
- 拨开迷雾,找回自我:DDD 应对具体业务场景,Domain Model 到底如何设计?
写在前面 除了博文内容之外,和 netfocus 兄的讨论,也可以让你学到很多(至少我是这样),不要错过哦. 阅读目录: 迷雾森林 找回自我 开源地址 后记 毫无疑问,领域驱动设计的核心是领域模型,领 ...
- JS与APP原生控件交互
"热更新"."热部署"相信对于混合式开发的童鞋一定不陌生,那么APP怎么避免每次升级都要在APP应用商店发布呢?这里就用到了混合式开发的概念,对于电商网站尤其显 ...
- Linux 常用命令(持续补充)
常用命令: command &:将进程放在后台执行 ctrl + z:暂停当前进程 并放入后台 jobs:查看当前后台任务 bg( %id):将任务转为后台执行 fg( %id):将任务调回前 ...
- GJM : C#设计模式(1)——单例模式
感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...
- Android使用静默安装时碰见的问题
升级时碰见的异常 private void installPackage(String appName,final File apk) { if (!apk.exists()) { setHasNew ...
- Android中的多线程断点下载
首先来看一下多线程下载的原理.多线程下载就是将同一个网络上的原始文件根据线程个数分成均等份,然后每个单独的线程下载对应的一部分,然后再将下载好的文件按照原始文件的顺序"拼接"起来就 ...
- Maven(一)linux下安装
1.检查是否安装JDK,并且设置了环境变量(JAVA_HOME): echo $JAVA_HOME java -version 运行结果: 显示jdk的安装路径,和java的版本,如: #jdk路径 ...
- 服务治理要先于SOA
讲在前面的话: 若企业缺乏对服务变更的控制和规则,那么一个服务在经过几个项目之后,就很有可能被随意更改成多个版本,将来变成什么样更是无法预测.久而久之,降低了服务重用的可能性,提高了服务利用的成本 ...
- Ognl表达式基本原理和使用方法
Ognl表达式基本原理和使用方法 1.Ognl表达式语言 1.1.概述 OGNL表达式 OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,他是一个 ...