编程 14

编制程序式和证明式,帮助的数据库事务传播属性和作业隔断等第

By admin in 编程 on 2019年10月4日

面试题:

 一、 事务概述

●在JavaEE企业级开发的应用领域,为了保证数据的完整性一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。

●事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行要么都不执行

●事务的四个关键属性(ACID)

原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。

一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚

隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰

持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

 

Spring Boot 默认集成事务,所以无须手动开启使用
@EnableTransactionManagement 注解,就可以用
@Transactional注解进行事务管理,使用了 @EnableTransactionManagement
后,Spring 会自动扫描注解 @Transactional 的方法和类。

请介绍下 Spring 支持的常用数据库事务传播属性和事务隔离级别?

二、 Spring事务管理

启动类增加@EnableTransactionManagement注解,类或者方法上添加@Transactional注解即可。

参考答案:

2.1 编程式事务管理

例如:@Transactional(value = “transactionManager”, isolation =
Isolation.DEFAULT, propagation = Propagation.REQUIRED,rollbackFor =
Exception.class,timeout=36000)   括号中的参数都是可选的,可不填

事务传播属性:

2.1.1使用原生的JDBC API进行事务管理

[1]获取数据库连接Connection对象

[2]取消事务的自动提交

[3]执行操作

[4]正常完成操作时手动提交事务

[5]执行失败时回滚事务

[6]关闭相关资源

例:

//1.获取Connection对象

Connection conn = JDBCUtils.getConnection();

try {

    //2.开启事务:取消自动提交

    conn.setAutoCommit(false);

    //3.执行操作

    //4.正常完成操作,手动提交事务

    conn.commit();

}catch(Exception e) {

    //5.回滚事务

    conn.rollBack();

}finally{

    //6.释放资源

}

*noRollbackFor 修饰表明不做事务回滚,rollbackFor
修饰的表明需要事务回滚。*

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。(一个方法运行在了一个开启事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务)例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定,Spring
定义了 7 种传播行为。

2.1.2评价

使用原生的JDBC
API
实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余

 

默认情况下只有RuntimeException异常才回滚,为了让Exception也回滚,需增加
rollbackFor = Exception.class.

传播属性 描述
REQUIRED 默认值,如果有事务正在运行,当前的方法就在这个事务内运行,否则就开启一个新的事务,并在自己的事务内运行。
REQUIRED_NEW 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将原来的事务挂起。
SUPPORTS 如果有事务正在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。
NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有运行的事务,将这个事务挂起。
MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。
NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。

2.2 声明式事务管理

大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理

Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。

Spring既支持编程式事务管理,也支持声明式的事务管理。

 

@Rollback标签是需要配合@Transcational标签共同使用的,加了Transactional标签的方法,会默认视为@Rollback(true),如果让这个方法不回滚,那么就要手动设置@Rollback(false)

事务传播属性可以在 @Transactional 注解的 propagation 属性中定义。

2.3 Spring提供的事务管理器

Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

 

事物特性:

原子性、隔离性、一致性、持久性

spring 事物管理高层抽象出三个接口

1、PlatfromTransactionManagement    事物管理器

2、TransactionDefinition 
事物定义信息:隔离级别、传播特性、超时、只读

3、TransactionStatus   事物具体运行状态

spring为不同的持久层提供了不同的PlatfromTransactionManagement接口实现类

spring jdbc或者mybatis   —>>    
 **.jdbc.datasource.DataSourceTransactionManagement(实现类的路径)

hibernate3                       —>>     
**.orm.hibernate3.HibernateTransactionManagement

jpa                                    —>>    
**.orm.jpa.JpaTransactionManagement

我们如果使用到 spring-boot-starter-jdbc 、 spring-boot-starter-data-jpa
     Spring Boot 会自动默认分别注入DataSourceTransactionManager ,
JpaTransactionManager

使用 @Transactional
注解在方法或类上表明这个方法或类需要事务支持,此时,Spring
拦截器会在这个方法调用时,开启一个新的事务,当方法运行结束且无异常的情况下,提交这个事务。

注解@EnableTransactionManagement@Transactional来自于spring-tx.jar这个包,在引入mybatis依赖时,已自动引入,所以只要把mybatis-spring-boot-starter依赖引入进来,那事务就不需要再引入包了。

举个例子,小明有 100 元,有两本书,一本 50 元,一本 60 元。

2.4 事务管理器的主要实现

①DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。

②JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理

③HibernateTransactionManager:用Hibernate框架存取数据库

 编程 1

 

隔离级别

隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

我们可以看org.springframework.transaction.annotation.Isolation枚举类中定义了五个表示隔离级别的值:

public enum Isolation {

DEFAULT(-1),

READ_UNCOMMITTED(1),

READ_COMMITTED(2),

REPEATABLE_READ(4),

SERIALIZABLE(8);

}

DEFAULT
 *
(DEFAULT)*:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED。

READ_UNCOMMITTED* 
(READ_UNCOMMITED)*:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。

READ_COMMITTED
 *
(READ_COMMITED)*:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。

REPEATABLE_READ 
(REPEATABLE_READ):该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。

SERIALIZABLE
 *
(SERIALIZABLE)*:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

指定方法:通过使用isolation属性设置,例如:

@Transactional(isolation = Isolation.DEFAULT)

把购买任意一本书的行为看成一个方法
purchase(),把购买后的结账行为看成另一个方法
checkout()。这两个方法都有添加了 @Transactional 注解。

3 测试数据准备

传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

我们可以看org.springframework.transaction.annotation.Propagation枚举类中定义了6个表示传播行为的枚举值:

public enum Propagation {

    REQUIRED(0),

    SUPPORTS(1),

    MANDATORY(2),

    REQUIRES_NEW(3),

    NOT_SUPPORTED(4),

    NEVER(5),

    NESTED(6);

}

REQUIRED
*(PROPAGATION_REQUIRED)*:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

SUPPORTS ***PROPAGATION_SUPPORTS)***:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

MANDATORY ***PROPAGATION_MANDATORY)***:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

REQUIRES_NEW ***PROPAGATION_REQUIRES_NEW)***:创建一个新的事务,如果当前存在事务,则把当前事务挂起。

NOT_SUPPORTED ***PROPAGATION_NOT_SUPPORTED)***:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

NEVER ***PROPAGATION_编程,NEVER)***:以非事务方式运行,如果当前存在事务,则抛出异常。

NESTED ***PROPAGATION_NESTED)***:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。

指定方法:通过使用propagation属性设置,例如:

@Transactional(propagation = Propagation.REQUIRED)

在 checkout() 方法中根据购买的书籍来调用对应的 purchase() 方法。

3.1 需求

 编程 2

 

REQUIRED 传播行为:

3.2 数据库表

CREATE TABLE book (

  isbn VARCHAR (50) PRIMARY KEY,

  book_name VARCHAR (100),

  price INT

) ;

 

CREATE TABLE book_stock (

  isbn VARCHAR (50) PRIMARY KEY,

  stock INT,

  CHECK (stock > 0)

) ;

 

CREATE TABLE account (

  username VARCHAR (50) PRIMARY KEY,

  balance INT,

  CHECK (balance > 0)

) ;

 

INSERT INTO account (`username`,`balance`) VALUES (‘Tom’,100000);

INSERT INTO account (`username`,`balance`) VALUES (‘Jerry’,150000);

 

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES (‘ISBN-001′,’book01’,100);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES (‘ISBN-002′,’book02’,200);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES (‘ISBN-003′,’book03’,300);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES (‘ISBN-004′,’book04’,400);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES (‘ISBN-005′,’book05’,500);

 

INSERT INTO book_stock (`isbn`,`stock`) VALUES (‘ISBN-001’,1000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES (‘ISBN-002’,2000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES (‘ISBN-003’,3000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES (‘ISBN-004’,4000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES (‘ISBN-005’,5000);

 

编程 3REQUIRED传播行为.png

4 初步实现

此时小明一本书也买不成功。

4.1 配置文件

<!– 配置事务管理器 –>

<bean id="transactionManager" 

  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"/>   

</bean>

 

<!– 启用事务注解 –>

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

 

REQUIRED_NEW 传播行为:

4.2 在需要进行事务控制的方法上加注解

@Transactional

 

编程 4REQUIRED_NEW传播行为.png

5 事务的传播行为

此时小明可以买成功 1 本书。

5.1 简介

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。系统默认属性为:REQUIRED。

 编程 5

事务传播属性可以在@Transactional注解的propagation属性中定义。

 

事务隔离级别:

5.2 测试

 编程 6

数据库事务并发问题:

 

假设现在有两个事务,Transaction1 和 Transaction2 并发执行。

5.3 说明

①REQUIRED传播行为

当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。

 编程 7

 

②REQUIRES_NEW传播行为

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

 编程 8

 

1)脏读:读到的是未提交的值

5.4 补充

在Spring 2.x事务通知中,可以像下面这样在<tx:method>元素中设定传播事务属性。

 编程 9

 

 

① Transaction1 将某条记录的值从 20 修改为了 30。

6 事务的隔离级别

② Transaction2 读取了 Transaction1 更新后的值。

6.1 数据库事务并发问题

假设现在有两个事务:Transaction01和Transaction02并发执行。

③ Transaction1 回滚,值恢复到了 20。

脏读

[1]Transaction01将某条记录的AGE值从20修改为30。

[2]Transaction02读取了Transaction01更新后的值:30。

[3]Transaction01回滚,AGE值恢复到了20。

[4]Transaction02读取到的30就是一个无效的值。

④ Transaction2 读取到的 30 就是一个无效值。

不可重复读

[1]Transaction01读取了AGE值为20。

[2]Transaction02将AGE值修改为30。

[3]Transaction01再次读取AGE值为30,和第一次读取不一致。

2)不可重复读:两次读取的结果不一致

幻读

[1]Transaction01读取了STUDENT表中的一部分数据。

[2]Transaction02向STUDENT表中插入了新的行。

[3]Transaction01读取了STUDENT表时,多出了一些行。

① Transaction1 读取值为 20。

6.2 隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

② Transaction2 将值修改为 30。

读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改。

③ Transaction1 再次读取为 30,和第一次读取的结果不一致。

读已提交:READ COMMITTED

要求Transaction01只能读取Transaction02已提交的修改。

3)幻读

可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

① Transaction1 读取表中的一部分数据。

串行化:SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

② Transaction2 向表中插入了新的行。

⑤各个隔离级别解决并发问题的能力见下表

 

脏读

不可重复读

幻读

READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE

③ Transaction1 读取表中数据时,多出了一些行。

⑥各种数据库产品对事务隔离级别的支持程度

 

Oracle

MySQL

READ UNCOMMITTED

×

READ COMMITTED

REPEATABLE READ

×

√(默认)

SERIALIZABLE

 

 

数据库系统必须具有隔离并发运行各个事务的能力,使他们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

6.3 在Spring中指定事务隔离级别

1)读未提交:READ UNCOMMITED

①注解

用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别

允许 Transaction1 读取 Transaction2 未提交的修改。

②XML

在Spring 2.x事务通知中,可以在<tx:method>元素中指定隔离级别

 编程 10

 

 

2)读已提交:READ COMMITED

7 触发事务回滚的异常

要求 Transaction1 只能读取 Transaction2 已提交的修改。

7.1 默认情况

捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。

3)可重复读:REPEATABLE READ

7.2 设置途经

确保 Transaction1 可以多次从一个字段中读取到相同的值,即 Transaction1
执行期间禁止其它事务对这个字段进行更新。

①注解

@Transactional 注解

[1]rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个

[2]noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个

 编程 11

 

4)串行化:SERIALIZABLE

②XML

在Spring 2.x事务通知中,可以在<tx:method>元素中指定回滚规则。如果有不止一种异常则用逗号分隔。

 编程 12

 

确保 Transaction1 可以多次从一个表中读取到相同的行,在 Transaction1
执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

8 事务的超时和只读属性

各个隔离级别解决并发问题的能力见下表:

8.1 简介

由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。

如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。

超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。

只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

级别 脏读 不可重复读 幻读
READ UNCOMMITED
READ COMMITED
REPEATABLE READ
SERIALIZABLE

8.2 设置

①注解

@Transaction注解

 编程 13

 

 

②XML

在Spring 2.x事务通知中,超时和只读属性可以在<tx:method>元素中进行指定

 编程 14

 

各种数据库产品对事务隔离级别的支持程度:

9 基于XML文档的声明式事务配置

<!– 配置事务切面 –>

<aop:config>

<aop:pointcut

expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))"

id="txPointCut"/>

<!– 将切入点表达式和事务属性配置关联到一起 –>

<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>

</aop:config>

 

<!– 配置基于XML的声明式事务  –>

<tx:advice id="myTx" transaction-manager="transactionManager">

<tx:attributes>

<!– 设置具体方法的事务属性 –>

<tx:method name="find*" read-only="true"/>

<tx:method name="get*" read-only="true"/>

<tx:method name="purchase"

isolation="READ_COMMITTED"

No-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"

propagation="REQUIRES_NEW"

read-only="false"

timeout="10"/>

</tx:attributes>

</tx:advice>

 

 

级别 Oracle MySQL
READ UNCOMMITED ×
READ COMMITED
REPEATABLE READ ×
SERIALIZABLE

10.声明式事务例:

①导入SQL文件:declaration_transaction.sql

该文件内容如下,可直接复制到记事本,文件名后缀为.sql,通过数据库(如:Navicat
Premium等)运行该sql文件就好。

 

编程 15编程 16

/*
SQLyog Ultimate v9.20 
MySQL - 5.1.37-community : Database - tx
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`tx` /*!40100 DEFAULT CHARACTER SET gb2312 */;

USE `tx`;

/*Table structure for table `account` */

DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
  `username` varchar(50) NOT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;

/*Data for the table `account` */

insert  into `account`(`username`,`balance`) values ('Jerry',800),('Tom',100000);

/*Table structure for table `book` */

DROP TABLE IF EXISTS `book`;

CREATE TABLE `book` (
  `isbn` varchar(50) NOT NULL,
  `book_name` varchar(100) DEFAULT NULL,
  `price` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;

/*Data for the table `book` */

insert  into `book`(`isbn`,`book_name`,`price`) values ('ISBN-001','book01',100),('ISBN-002','book02',200),('ISBN-003','book03',300),('ISBN-004','book04',400),('ISBN-005','book05',500);

/*Table structure for table `book_stock` */

DROP TABLE IF EXISTS `book_stock`;

CREATE TABLE `book_stock` (
  `isbn` varchar(50) NOT NULL,
  `stock` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;

/*Data for the table `book_stock` */

insert  into `book_stock`(`isbn`,`stock`) values ('ISBN-001',1000),('ISBN-002',2000),('ISBN-003',3000),('ISBN-004',4000),('ISBN-005',5000);

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

declaration_transaction.sql

 

②创建一个动态web工程

1.加入jar包

com.springsource.net.sf.cglib-2.2.0.jar

com.springsource.org.aopalliance-1.0.0.jar

com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

commons-logging-1.1.3.jar

 

spring-aop-4.0.0.RELEASE.jar

spring-aspects-4.0.0.RELEASE.jar

spring-beans-4.0.0.RELEASE.jar

spring-context-4.0.0.RELEASE.jar

spring-core-4.0.0.RELEASE.jar

spring-expression-4.0.0.RELEASE.jar

 

spring-jdbc-4.0.0.RELEASE.jar

spring-orm-4.0.0.RELEASE.jar

spring-tx-4.0.0.RELEASE.jar

 

然后是mysql驱动包即C3P0的jar包:

c3p0-0.9.1.2.jar

mysql-connector-java-5.1.37-bin.jar

 2.创建一份jdbc.properties文件

 

jdbc.user=root

jdbc.passowrd=123456

jdbc.url=jdbc:mysql://localhost:3306/tx

jdbc.driver=com.mysql.jdbc.Driver

 

  3.在spring配置文件中配置数据源

<!-- 引入外部属性文件 -->

<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 配置数据源 -->

<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="user" value="${jdbc.user}"></property>

<property name="password" value="${jdbc.passowrd}"></property>

<property name="jdbcUrl" value="${jdbc.url}"></property>

<property name="driverClass" value="${jdbc.driver}"></property>

</bean>

 

  4.测试数据源:

public class TestDataSource {

  private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");

  @Test

  public void test() throws SQLException {
    //获取数据源类
    DataSource bean = ioc.getBean(DataSource.class);
    //检验是否能够连接
    System.out.println(bean.getConnection());

  }
}

  

  5.配置jdbcTemplate:

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  <property name="dataSource" ref="comboPooledDataSource"></property>
</bean>

 

  6.创建Dao类

<!-- 在xml文件中设置扫描的包 -->

<context:component-scan base-package="com.neuedu"></context:component-scan>

  

@Repository
public class BookDao {

@Autowired
private JdbcTemplate  jdbcTemplate;

//[1]根据isbn的值查询书的价格
public Integer findPriceByIsbn(String isbn){
  String sql = "SELECT price FROM book WHERE isbn = ?";
  Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
  return price;
}

//[2]根据isbn的值减少书的库存,假设每次都只买1本书
public void updateStockByIsbn(String isbn){
  String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=? ";
  jdbcTemplate.update(sql, isbn);
}

//[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
public void updateBalance(String username,int price){
  String sql = "UPDATE account SET balance=balance-? WHERE username=? ";
  jdbcTemplate.update(sql,price, username);
}

public void updatePriceByIsbn(String isbn,int price){
  String sql="UPDATE book SET price=? WHERE isbn=? ";
  jdbcTemplate.update(sql, price,isbn);
}


public int findPriceByIsbn(String isbn){
  String sql = "SELECT price FROM book WHERE isbn = ?";
  Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
  return price;
}
}

  

 

  测试上面的该方法:

 public class TestDataSource {

  private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
  private BookDao bean = ioc.getBean(BookDao.class);
  @Test
  public void test01() throws SQLException {
    int findPriceByIsbn = bean.findPriceByIsbn("ISBN-005");
    System.out.println(findPriceByIsbn);
  }
}

 

  然后在dao类中继续编写如下方法:

 

//[2]根据isbn的值减少书的库存,假设每次都只买1本书
public void updateStockByIsbn(String isbn){
  String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
  jdbcTemplate.update(sql, isbn);
}

 

  

 

  继续测试:

 public class TestDataSource {

private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class);

@Test
public void test02() throws SQLException {
bean.updateStockByIsbn("ISBN-004");
}
}

  

  继续编写Dao类中的方法:

//[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
public void updateBalance(String userName,int price){
  String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
  jdbcTemplate.update(sql, price,userName);
}

  

  继续测试:

@Test
public void test03() throws SQLException {
  bean.updateBalance("Tom", 1000);
}

  

 

7.创建Service层:

@Service
public class BookService {
  @Autowired
  private BookDao bookDao;

  public void doCash(String isbn,String username){
    int price = bookDao.findPriceByIsbn(isbn);
    bookDao.updateStockByIsbn(isbn);
    bookDao.updateBalance(username, price);
  }
}

  

   8.注意哦:上面doCash方法中调用的三个方法应该在同一个事务中,要么同时成功,要么同时失败!

   先统一设置一下:

UPDATE account SET balance = 10000;
UPDATE book_stock SET stock = 1000;

然后先正常测试一下service中的doCash方法:

public class TestDataSource {
  private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
  private BookDao bean = ioc.getBean(BookDao.class);
  private BookService bookService  = ioc.getBean(BookService.class);
   
  @Test
  public void test04() throws SQLException {
    bookService.doCash("ISBN-001","Tom");
  }
}

  

        由于此时该方法没在事务中,如果doCash方法调用dao层的方法的时候,在中间位置出现了错误,此时就会造成一部分数据改掉了,而

 另一部分数据没有改掉,这就麻烦了,所以此时应该加入事务机制!

  9.如果想开启事务,就需要在spring的配置文件中配置事务管理器

 <!-- 配置事务管理器,并为事务管理器配置数据源!-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
<!-- 开启基于注解的声明式事务功能,需要设置transaction-manager属性-->
<!-- 如果 事务管理器的id正好是transaction-manager的默认值transactionManager,则可以省略-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

然后在service层的doCash方法上加上:@Transactional注解这样就开启了事务!需要注意的是事务一般是加在service层的!

③数据库操作

[1]根据isbn的值查询书的价格

[2]根据isbn的值减少书的库存,假设每次都只买1本书

[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 澳门新葡亰官网app 版权所有