vlambda博客
学习文章列表

一文搞懂 Spring 数据库事务操作!

一文搞懂 Spring 数据库事务操作!

作者 | 阿文,责编 | 郭芮

图 | CSDN 下载自东方IC

出品 | CSDN(ID:CSDNnews)

今天我们一起了解下Spring的数据库事务操作。在操作数据库时,我们经常会使用到事务,为此Spring 提供了专门的用于处理事务的API方便开发者调用,那么本文就着重来讲解下Spring 对于事务的相关功能。

一文搞懂 Spring 数据库事务操作!

Spring 事务的核心接口


Spring 通过一个名为spring-tx-4.3.6-RELEASE 的JAR包来管理事务,在这个JAR包中的org.Springframework.transaction 包中包含了三个接口文件:
  • PlatformTramsactionManager 主要用于管理事务,包括获取事务的状态、提交事务和回滚事务;

  • TramsactionDefinition 该接口是事务定义的对象,包括了获取事务的名称、隔离级别、事务的传播行为、超时时间、事务是否只读等;

  • TramsactionStatus 该接口是事务的状态,描述了某一个时间点事务状态信息,包括刷新事务、获取是否存在保存点、是否是新事务、是否回滚、设置事务回滚。



一文搞懂 Spring 数据库事务操作!

实例讲解


接下来我们将通过实例的方式来讲解如何使用注解的方式来通过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(100NOT NULL,
                `password` VARCHAR(40NOT NULL,
                `jifen` int(10NOT 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);
    //通过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"
>

<!--1.配置数据源 -->
<bean id="dataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">

    <!--数据库驱动 -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <!--连接数据库的ur1 -->
    <property name="url" value="jdbc:mysql://192.168.10.128:3306/spring_db" />
    <!--连接数据库的用户名 -->
    <property name="username" value="root" />
    <!--连接数据库的密码 -->
    <property name="password" value="123456" />
</bean>
<!--2.配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--默认必须使用数据源 -->
    <property name="dataSource" ref="dataSource" />
</bean>
<!--3.定义id为userDao的Bean -->
<bean id="userDao" class="com.SpringDemo.UserDaoImpl">
    <!--将 jdbcTemplate注入到 userDao实例中 -->
    <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!--4.事务管理器,依赖于数据源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--5.注册事务管理驱动 -->
<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 的参数含义如下:

一文搞懂 Spring 数据库事务操作!


@Transactional 除了 DEFAULT,还有其他属性,我们可以在Isolation 这个类中看到相对于的定位
@Transactional 注解也可以添加到类级别上。当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。
  
    
    
  
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
如图所示:
一文搞懂 Spring 数据库事务操作!
此时,我们查看数据库中的数据没有发生任何变化:
  
    
    
  
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的事务管理介绍。
一文搞懂 Spring 数据库事务操作!

【END】

一文搞懂 Spring 数据库事务操作!

更多精彩推荐


今日福利:评论区留言入选,可获得价值299元的「2020 AI开发者万人大会」在线直播门票一张。  快来动动手指,写下你想说的话吧。

点击阅读原文,精彩继续!

你点的每个“在看”,我都认真当成了喜欢