今天我们一起了解下Spring的数据库事务操作。在操作数据库时,我们经常会使用到事务,为此Spring 提供了专门的用于处理事务的API方便开发者调用,那么本文就着重来讲解下Spring 对于事务的相关功能
Spring 事务的核心接口 Spring 通过一个名为spring-tx-4.3.6-RELEASE 的JAR包来管理事务,在这个JAR包中的org.Springframework.transaction 包中包含了三个接口文件:
PlatformTramsactionManager 主要用于管理事务,包括获取事务的状态、提交事务和回滚事务;
TramsactionDefinition 该接口是事务定义的对象,包括了获取事务的名称、隔离级别、事务的传播行为、超时时间、事务是否只读等;
TramsactionStatus 该接口是事务的状态,描述了某一个时间点事务状态信息,包括刷新事务、获取是否存在保存点、是否是新事务、是否回滚、设置事务回滚
实例讲解 接下来我们将通过实例的方式来讲解如何使用注解的方式来通过Spring 进行事务的处理,手续我们在maven的pom.xml 中增加事务的JAR包
<dependency > <groupId > org.springframework</groupId > <artifactId > spring-tx</artifactId > <version > 4.3.6.RELEASE</version > </dependency >
我们首先准备一个数据库
CREATE TABLE IF NOT EXISTS `user `( `id` INT UNSIGNED AUTO_INCREMENT, `username` VARCHAR (100 ) NOT NULL , `password ` VARCHAR (40 ) NOT NULL , `jifen` int (10 ) NOT NULL , PRIMARY KEY ( `id` ))ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后向数据库中写入一些数据,包括了用户名、密码和积分,如下所示
MariaDB [spring_db]> select * from user;+----+ ----------+----------+ -------+| id | username | password | jifen | +----+----------+----------+-------+ | 1 | zhangsan | 123 | 1000 | | 2 | lisi | 1234 | 1000 || 3 | wangwu | 1234 | 1000 | +----+----------+----------+-------+ 3 rows in set (0.000 sec)
我们要做的事情就是把张三的积分转给李四
我们需要创建一个 User 类,如下
package com.SpringDemo ;public class User { private Integer id; private String username; private String password; private Integer jifen; public Integer getId ( ) { return id; } public void setId (Integer id ) { this .id = id; } public String getUsername ( ) { return username; } public void setJifen (Integer jifen ){ this .jifen = jifen; } public Integer getjifen ( ) { return jifen; } public void setUsername (String username ) { this .username = username; } public String getPassword ( ) { return password; } public void setPassword (String password ) { this .password = password; } public String toString ( ) { return "User [id=" + id + ", username=" + username + ", password=" + password + "]" ; } }
然后创建一个接口 UserDao
package com.SpringDemo;import java.util.List;public interface UserDao { public int addUser (User user) ; public int updateUser (User user) ; public int deleteUser (int id) ; public User findUserById (int id) ; public List<User> findAllUser () ; public void transfer (String outUser,String inUser,Integer jifen) ; }
在UserDao 接口中我们定义了一个transfer 的方法,它包含了三个参数分别是outUser、inUser、jifen
接来下我们定义实现类 UserDAOImpl
package com.SpringDemo ;import org.springframework .jdbc .core .BeanPropertyRowMapper ;import org.springframework .jdbc .core .JdbcTemplate ;import org.springframework .jdbc .core .RowMapper ;import org.springframework .transaction .annotation .Isolation ;import org.springframework .transaction .annotation .Propagation ;import org.springframework .transaction .annotation .Transactional ;import java.util .List ;public class UserDaoImpl implements UserDao { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate (JdbcTemplate jdbcTemplate ) { this .jdbcTemplate = jdbcTemplate; } @Override public int addUser (User user ) { String sql="insert into user(username,password) value(?,?)" ; Object [] obj=new Object []{ user.getUsername (), user.getPassword () }; int num=this .jdbcTemplate .update (sql,obj); return num; } @Override public int updateUser (User user ) { String sql="update user set username=?,password=? where id=?" ; Object [] params=new Object []{ user.getUsername (), user.getPassword (), user.getId () }; int num=this .jdbcTemplate .update (sql,params); return num; } @Override public int deleteUser (int id ) { String sql="delete from user where id=?" ; int num=this .jdbcTemplate .update (sql,id); return num; } @Override public User findUserById (int id ) { String sql="select * from user where id=?" ; RowMapper <User > rowMapper=new BeanPropertyRowMapper <User >(User .class ); return this .jdbcTemplate .queryForObject (sql,rowMapper,id); } @Override public List <User > findAllUser ( ) { String sql="select * from user" ; RowMapper <User > rowMapper=new BeanPropertyRowMapper <User >(User .class ); return this .jdbcTemplate .query (sql,rowMapper); } @Override public void transfer (String outUser, String inUser, Integer jifen ) { this .jdbcTemplate .update ("update user set jifen=jifen+? where username=?" ,jifen,inUser); int i =1 /0 ; this .jdbcTemplate .update ("update user set jifen=jifen-? where username=?" ,jifen,outUser); } }
接下来我们定义一个applicationContext.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:aop ="http://www.springframework.org/schema/aop" xmlns:tx ="http://www.springframework.org/schema/tx" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd" ><bean id ="dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://192.168.10.128:3306/spring_db" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" /> </bean > <bean id ="userDao" class ="com.SpringDemo.UserDaoImpl" > <property name ="jdbcTemplate" ref ="jdbcTemplate" /> </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean > <tx:annotation-driven transaction-manager ="transactionManager" > </tx:annotation-driven > </beans >
Spring 的事务管理方式有2种,一种是传统的编程序事务管理,即通过代码来管理事务的开始、执行和异常以及回滚,一种是声明式管理,即通过配置文件的方式,原理是通过AOP技术实现,我们在实际开发过程中推荐使用声明式事务管理,效率会大大提升,因为只需要通过配置即可。
在该接口中我们我们重写transfer的方法,更新数据库将inUser 的积分进行增加,而对应的outUser 积分要进行减少,但是在这里我们要模拟系统运行的一些突然性问题。之后我们加了一个@Transactionl 注解,并设置了propagation、Isolation、readOnly 三个参数
@Override @Transactional (propagation = Propagation .REQUIRED ,isolation = Isolation .DEFAULT , readOnly = false ) public void transfer (String outUser, String inUser, Integer jifen ) { this .jdbcTemplate .update ("update user set jifen=jifen+? where username=?" ,jifen,inUser); int i =1 /0 ; this .jdbcTemplate .update ("update user set jifen=jifen-? where username=?" ,jifen,outUser); }
注解 @Transactional 的参数含义如下
属性名
说明
name
当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation
事务的传播行为,默认值为 REQUIRED。
isolation
事务的隔离度也可以叫隔离级别,默认值采用 DEFAULT。
timeout
事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for
用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for
抛出 no-rollback-for 指定的异常类型,不回滚事务。
@Transactional 注解也可以添加到类级别上。当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。
isolcation 除了 DEFAULT,还有其他属性,我们可以在Isolation 这个类中看到相对于的定位
public enum Isolation { DEFAULT (-1 ), READ_UNCOMMITTED (1 ), READ_COMMITTED (2 ), REPEATABLE_READ (4 ), SERIALIZABLE (8 ); private final int value; private Isolation (int value) { this .value = value; } public int value () { return this .value; } }
Propagation 的属性如下
public enum Propagation { REQUIRED (0 ), SUPPORTS (1 ), MANDATORY (2 ), REQUIRES_NEW (3 ), NOT_SUPPORTED (4 ), NEVER (5 ), NESTED (6 ); private final int value; private Propagation (int value) { this .value = value; } public int value () { return this .value; } }
此外使用@Transactional 必须保证是在public 级别的方法中使用,@Transactional 只能应用到 public 方法才有效,这是因为在使用 Spring AOP 代理时,Spring 在调用 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取 @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。
接下来我们创建一个测试类来进行测试
package com.SpringDemo;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class TransactionTest { public static void main (String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); UserDao userDao = (UserDao) applicationContext.getBean("userDao" ); userDao.transfer("zhangsan" ,"lisi" ,100 ); System.out.println("赠送积分成功" ); } }
我们执行上述程序,可以发现报错了,程序报
Exception in thread "main" java.lang.ArithmeticException: / by zero
如图所示
此时,我们查看数据库中的数据没有发生任何变化
MariaDB [spring_db]> select * from user;+----+ ----------+----------+ -------+| id | username | password | jifen | +----+----------+----------+-------+ | 1 | zhangsan | 123 | 1000 | | 2 | lisi | 1234 | 1000 || 3 | wangwu | 1234 | 1000 | +----+----------+----------+-------+ 3 rows in set (0.000 sec)
而当我们把 int i =1/0; 注释掉再次执行就会发现程序执行没有报错了,并且数据发生了变化
MariaDB [spring_db]> select * from user;+----+ ----------+----------+ -------+| id | username | password | jifen | +----+----------+----------+-------+ | 1 | zhangsan | 123 | 900 | | 2 | lisi | 1234 | 1100 || 3 | wangwu | 1234 | 1000 | +----+----------+----------+-------+ 3 rows in set (0.000 sec)
好了,以上就是关于Spring的事务管理介绍。
文章作者: 阿文
版权声明: 本博客所有文章除特别声明外,均采用
CC BY-NC-SA 4.0 许可协议。转载请注明来自
阿文的博客 !