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

一文搞懂 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 进行事务的处理,手续我们在mavenpom.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

<!–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 . valuevalue ;

}

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 . valuevalue ;

}

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 数据库事务操作!

更多精彩推荐

斩获GitHub 2000+ Star阿里云开源的 Alink 机器学习平台如何跑赢双11数据“博弈”? | AI 技术生态论

2020 年,AI 芯片内存哪家强?

拜托,别再问我什么是 B+ 树了

程序员为什么应该旗帜鲜明地反对“最佳实践”?

半小时训练亿级规模知识图谱,亚马逊AI开源知识图谱嵌入表示框架DGL-KE

“出道” 5 年采用率达 78%,Kubernetes 的成功秘诀是什么?

警惕!新骗术出现:这些虚假二维码生成器已成功盗取 4.6 万美元!

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

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

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

原文 

http://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650742339&idx=4&sn=771a85496efa0723edb0ed0139d1b7b7

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 一文搞懂 Spring 数据库事务操作!

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址