09 Spring 事务管理(@Transactional)基本使用

转载请注明来源 赖赖的博客

导语

只要你肯再坚持一会儿,就会发现世界如此简单。

事务管理在持久层(数据库)操作中是不可或缺的,其主要目的是为了保证操作的完整性。
而Spring提供了强大的声明式事务管理的方式,其实现原理是采用了AOP对方法进行增强,使用起来非常方便,再也不用自己去管理事务(TRANSACTION)

实例

项目工程目录结构和代码获取地址

获取地址(版本Log将会注明每一个版本对应的课程)

https://github.com/laiyijie/SpringLearning

目录结构&数据库

工程目录结构
目录结构
数据库
数据库

创建语句:

CREATE TABLE account (
    username varchar(45) NOT NULL,
    password varchar(45) NOT NULL,
    name varchar(45) NOT NULL,
    create_time bigint(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE account
ADD PRIMARY KEY (username);

CREATE TABLE `pocket` (
  `username` varchar(45) NOT NULL,
  `current_money` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `pocket`
  ADD PRIMARY KEY (`username`);

运行工程

运行方式
  • 右键App.java
  • Run as
  • Java Application
运行结果

Exception in thread "main" java.lang.RuntimeException: lailai

数据库新增两条记录:

  • account表中新增一条
account表新增数据
  • pocket表中新增一条
pocket表新增数据

项目详解

App.java

package me.laiyijie.demo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import me.laiyijie.demo.service.UserService;

public class App {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("root-context.xml");

        UserService service = context.getBean(UserService.class);
        
        service.createAccount("laiyijie", "123123", "lai");
        
        service.createAccount("lailai", "123123", "lai");
        
        context.close();
    }

}  

有两条createAccount语句,却只产生了一个账号!问题肯定出现在createAccount方法中

我们来具体看下UserService 的实现类

UserServiceImpl.java

package me.laiyijie.demo.service;

import java.sql.Connection;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private BasicDataSource dataSource;

    @Transactional
    public void createAccount(String username, String password, String name) throws Exception {

        Connection connection = DataSourceUtils.getConnection(dataSource);

        connection.createStatement().execute("INSERT INTO account (username, password, name,create_time) VALUES('"
                + username + "','" + password + "','" + name + "','" + System.currentTimeMillis() + "')");

        if (username == "lailai") {
            throw new RuntimeException("lailai");
        }

        connection.createStatement()
                .execute("INSERT INTO pocket (username,current_money) VALUES('" + username + "','" + 0 + "')");

    }

}

函数执行过程:

  • 通过DataSourceUtils.getConnectiondataSource中获取connection

Connection connection = DataSourceUtils.getConnection(dataSource);

  • 执行插入account表的insert语句

connection.createStatement().execute("INSERT INTO account (username, password, name,create_time) VALUES('" + username + "','" + password + "','" + name + "','" + System.currentTimeMillis() + "')");

  • 判断是否用户名为lailai,是的话抛出运行时错误

if (username == "lailai") {
throw new RuntimeException("lailai");
}

  • 执行插入pocket表的insert语句

connection.createStatement().execute("INSERT INTO pocket (username,current_money) VALUES('" + username + "','" + 0 + "')");

那么很明显,在main函数中执行第二次createAccount的过程中,抛出了错误,而抛出错误的时间是在执行完插入account表之后!

理论上讲,在抛错之前的所有语句都应该被执行,也就是说,我们应该向数据库中插入了三行数据,两条account数据,一条pocket数据,然而却只有一条account数据和一条pocket数据。

让我们看看lailai这条account数据去哪儿了。

@Transactional 注解(spring的事务管理)

细心的读者可以发现,在createAccount函数之前我们增加了一个注解@Transactional,这个是Spring事务管理的声明,其寓意为:

当执行createAccount的时候,将创建一个事务,函数执行成功,则正常提交,当函数抛出RuntimeExeption的时候则回滚

这里有几个隐含的条件非常重要:

  • 数据库连接的获取一定要通过 DataSourceUtils.getConnection 来获取。每一个事务都是绑定在一个连接上的,如果还是通过 datasource.getConnection方法获取连接则有可能是获取到别的连接(由于连接池的存在)而DataSourceUtils.getConnection 获取的连接是当前事务的连接
  • 只有将异常抛出才能产生回滚,如果使用try catch处理了异常是不能回滚的
  • 默认情况下只有RuntimeException的抛出会产生回滚,如果需要自行定义可以回滚的异常,可以通过增加RollbackFor属性:
    • @Transactional(rollbackFor={Exception.class})
      
  • 如果使用mysql数据库,请不要用 MyISAM引擎,因为不支持事务,请使用InnoDB引擎

说了这么多限制,那么到底应该如何在Spring中配置事务呢:

root-context.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:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">


    <bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://127.0.0.1:3306/myspring"
        p:username="test" p:password="o34rWayJPPHgudtL" />

    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        p:dataSource-ref="mysqlDataSource" />
        
    <tx:annotation-driven transaction-manager="transactionManager" />

    <context:component-scan base-package="me.laiyijie.demo"></context:component-scan>

</beans>  

增加了两个配置:

  • 配置TransactionManager

      <bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      p:dataSource-ref="mysqlDataSource" />  
    
  • 开启注解驱动

      <tx:annotation-driven transaction-manager="transactionManager" />
    

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>me.laiyijie</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>



    </dependencies>
</project>

无需增加额外依赖! spring-context中自带!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,587评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,724评论 6 342
  • application的配置属性。 这些属性是否生效取决于对应的组件是否声明为Spring应用程序上下文里的Bea...
    新签名阅读 5,352评论 1 27
  • 这些属性是否生效取决于对应的组件是否声明为 Spring 应用程序上下文里的 Bean(基本是自动配置的),为一个...
    发光的鱼阅读 1,416评论 0 14
  • 今天下午的户外活动是亲近大自然之小溪玩水乐趣多。这是一条神奇的小溪,这里有温泉,可以泡泡脚,这里有小鱼小虾,可以体...
    萧贺阅读 261评论 0 0