Spring基于AOP的事务管理

时间:2022-08-24 10:21:45

Spring基于AOP的事务管理

  • 事务

  事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务就将回到最开始的状态,仿佛一切都没发生过。例如,老生常谈的转账问题,从转出用户的总存款中扣除转账金额和增加转出用户的账户金额是一个完整的工作单元,如果只完成扣除或者增加都会导致错误,造成损失,而事务管理技术可以避免类似情况的发生,保证数据的完整性和一致性。同样在企业级应用程序开发过程中,事务管理技术也是必不可少的。

  事务有四个特性:ACID

  1. 原子性(Atomicity):事务是一个原子操作,有一系列动作组成。原子性保证所有动作都完成,或者不执行任何动作。
  2. 一致性(Consistency):一旦事务完成(不论成败),系统必须确保它所建模的业务处于一致的状态。
  3. 隔离性(Isolation):可能有很多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  4. 持久性(Durability):一旦事务完成,无论系统发生生什么系统错误,它的结果都不会受到影响,保证能从系统崩溃中恢复过来,通常事务的结果会被写入到持久化存储器中。

  Spring事务是基于面向切面编程(Aspect Oriented Programming,AOP)实现的(文中会简单讲解AOP)。Spring的事务属性分别为传播行为、隔离级别、回滚规则、只读和事务超时属性,所有这些属性提供了事务应用方法和描述策略。如下我们介绍Spring事务管理的三个核心接口。

  • 核心接口
  1. TransactionDefinition接口是事务描述对象,提供获取事务相关信息的方法。
  2. PlatformTransactionManager接口是平台事务管理器,用于管理事务。
  3. TransactionStatus接口是事务的状态,描述了某一时间点上事务的状态信息。

Spring基于AOP的事务管理

  关于事务管理器PlatformTransactionManager的详细介绍见:http://www.mamicode.com/info-detail-1248286.html

  • Spring AOP

  面向切面编程(Aspect Oriented Programing,AOP)采用横向抽取机制,是面向对象编程(Object Oriented Programing,OOP)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能、权限管理、异常处理等,该类功能往往横向地散布在核心代码当中,这种散布在各处的无关代码被称为横切。AOP恰是一种横切技术,解剖开封装对象的内部,将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为Aspect(切面),所谓切面,简单的说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

  AOP术语

  1. 连接点(Joinpoint):被拦截到的点,该连接点可以是被拦截到的方法、字段或者构造器;
  2. 切入点(Pointcut):指要对哪些连接点进行拦截,即被拦截的连接点;
  3. 通知(Advice):指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知;
  4. 目标(Target):代理的目标对象;
  5. 织入(Weaving):把增强的代码应用到目标上,生成代理对象的过程;
  6. 切面(Aspect):切入点和通知的集合。
  • 项目实践

  接下来我们就利用Spring的事务管理实现如上例子中所述的转账案例,利用mysql数据库创建名称为User的数据库,在User数据库中创建两张表:用户信息表(t_user)、用户存款表(account),然后实现用户A向用户B转账,更新数据库表信息,如果转账失败,则数据库信息自动返回转账前的状态。

  在Eclipse下创建Java工程,其中必要的jar包以及工程中的类如下所示,jar包的下载地址为:Spring_AOP.zip

Spring基于AOP的事务管理Spring基于AOP的事务管理

  项目中的类介绍如下:

用户类(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工程,输出如下所示:

Spring基于AOP的事务管理

  然后去mysql的User数据库中查看刚才生成的表如下:

Spring基于AOP的事务管理 Spring基于AOP的事务管理 Spring基于AOP的事务管理

  从控制台输出中,我们得知在代码(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的事务管理

  如上就是对Spring AOP事务管理一个简单的介绍,希望能对读者产生一点帮助。

Spring基于AOP的事务管理的更多相关文章

  1. Spring 的 AOP 进行事务管理的一些问题

    AspectJ AOP事务属性的配置(隔离级别.传播行为等): <tx:advice id="myAdvice" transaction-manager="mtTx ...

  2. Spring MVC 中使用AOP 进行事务管理--XML配置实现

    1.今天写一篇使用AOP进行事务管理的示例,关于事务首先需要了解以下几点 (1)事务的特性 原子性(Atomicity):事务是一个原子操作,由一系列动作组成.事务的原子性确保动作要么全部完成,要么完 ...

  3. 全面分析 Spring 的编程式事务管理及声明式事务管理

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  4. spring的annotation-driven配置事务管理器详解

    http://blog.sina.com.cn/s/blog_8f61307b0100ynfb.html ——————————————————————————————————————————————— ...

  5. 全面分析 Spring 的编程式事务管理及声明式事务管理--转

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  6. spring声明式的事务管理

    spring支持声明式事务管理和编程式事务管理两种方式. 编程式事务使用TransactionTemplate来定义,可在代码级别对事务进行定义. 声明式事务基于aop来实现,缺点是其最细粒度的事务声 ...

  7. 12 Spring框架 SpringDAO的事务管理

    上一节我们说过Spring对DAO的两个支持分为两个知识点,一个是jdbc模板,另一个是事务管理. 事务是数据库中的概念,但是在一般情况下我们需要将事务提到业务层次,这样能够使得业务具有事务的特性,来 ...

  8. Spring整合hibernate4:事务管理

    Spring整合hibernate4:事务管理 Spring和Hibernate整合后,通过Hibernate API进行数据库操作时发现每次都要opensession,close,beginTran ...

  9. 使用注解实现Spring的声明式事务管理

    使用注解实现Spring的声明式事务管理,更加简单! 步骤: 1) 必须引入Aop相关的jar文件 2) bean.xml中指定注解方式实现声明式事务管理以及应用的事务管理器类 3)在需要添加事务控制 ...

随机推荐

  1. &lbrack;ActionScript 3&period;0&rsqb; AS3 绘制正二十面体(线条)

    分析: 正二十面体共有12个顶点.30条棱,其20个面都是正三角形.每条棱所对应的弧度值为1.1071487177940904弧度,这个弧度值可通过求Math.sqrt(5)/5的反余弦值求得.正二十 ...

  2. python-类和对象(属性、方法)的动态绑定

    动态绑定 # coding=utf-8 ''' 当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性 ''' from types im ...

  3. 不用修改nginx的高并发合并回源架构

    nginx的连接都是一对一的,想改成一对多,比较麻烦,所以曾经看完了Nginx代码想改成一对多,我还是没改成,后来改变了一下思路想到一个更简单的方案,而且不失并发性能,还容易控制,下面先给出下面的图: ...

  4. ios用storyboard快速创建静态cell

    在实际开发中经常会遇到下面这样的页面,通常我们用静态cell来做可以快速创建,提高效率 下面讲一下用storyboard创建方法,将一个tableViewController控制器拖入storyboa ...

  5. 设M&equals;5&Hat;2003&plus;7&Hat;2004&plus;9&Hat;2005&plus;11&Hat;2006,求证8&vert;M。&lpar;整除理论,1&period;1&period;8&rpar;

    设M=52003+72004+92005+112006,求证8|M. 证明: 前提:对于,52003让我们去构造8,即用8-3替换5 第一步:用8-3替换5,且仅替换一个, 第二步:进行分项,则前一项 ...

  6. &lbrack;css 实践篇&rsqb; CSS box-orient

    定义和用法 box-orient 属性规定框的子元素应该被水平或垂直排列. 提示:水平框中的子元素从左向右进行显示,而垂直框的子元素从上向下进行显示.不过,box-direction 和 box-or ...

  7. Feign性能优化注意事项

    一.FeignClient注解 FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient注解的作用目标在接口上 @FeignClient(name ...

  8. Java中try catch finally语句中含有return语句的执行情况(总结版)

    在这里看到了try >但有一点是可以肯定的,finally块中的内容会先于try中的return语句执行,如果finall语句块中也有return语句的话,那么直接从finally中返回了,这也 ...

  9. D01-R语言基础学习

    R语言基础学习——D01 20190410内容纲要: 1.R的下载与安装 2.R包的安装与使用方法 (1)查看已安装的包 (2)查看是否安装过包 (3)安装包 (4)更新包 3.结果的重用 4.R处理 ...

  10. 导出pb模型之后测试的python代码

    链接:https://blog.csdn.net/thriving_fcl/article/details/75213361 saved_model模块主要用于TensorFlow Serving.T ...