本地事务系列之一:JDBC操作_czj4451的博客-程序员秘密

技术标签: spring/ejb  jdbc  transaction  

本地事务即对一个数据源进行操作。大多数数据库支持事务。

先看没有事务的时候,导致的数据不一致问题。

准备数据:

-- MySQL
-- Create the database
DROP DATABASE IF EXISTS spring;
CREATE DATABASE spring

-- Drop three tables if exist
DROP TABLE IF EXISTS FRUIT;
DROP TABLE IF EXISTS FRUIT_STOCK;
DROP TABLE IF EXISTS ACCOUNT;

-- 水果表
CREATE TABLE FRUIT (
ID INT NOT NULL,
FRUIT_NAME VARCHAR(100) NOT NULL,
PRICE INT,
PRIMARY KEY (ID)
);

-- 水果存货表
CREATE TABLE FRUIT_STOCK (
ID INT NOT NULL,
STOCK INT NOT NULL,
PRIMARY KEY (ID),
CHECK (STOCK >= 0) -- analyzed but ignored by MySQL
);

-- 账户表
CREATE TABLE ACCOUNT (
USERNAME VARCHAR(50) NOT NULL,
BALANCE INT NOT NULL,
PRIMARY KEY (USERNAME),
CHECK (BALANCE >= 0)
);

-- Add initial data
INSERT INTO FRUIT(ID, FRUIT_NAME, PRICE) VALUES(1, 'Apple', 10);
INSERT INTO FRUIT_STOCK(ID, STOCK) VALUES(1, 10);
INSERT INTO ACCOUNT(USERNAME, BALANCE) VALUES('user1', 20);

DELIMITER $$

-- MySQL不支持check,使用触发器来检查约束,不满足时触发异常:
CREATE TRIGGER ACCOUNT_BALANCEGT0 BEFORE UPDATE ON account
FOR EACH ROW
BEGIN
IF NEW.balance < 0 THEN -- NEW代表更新后的记录
CALL xxx_yyy();
UPDATE _xxx_yyy SET X = 1; -- 引发异常
END IF;
END$$

DELIMITER ;


Maven依赖:

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>


接口:

public interface FruitShop {
// fruitId - 水果ID, userName - 用户号, count - 购买数量
boolean purchase(int fruitId, String userName, int count);
}


实现类:

public class JdbcFruitShop implements FruitShop {

static final Logger LOGGER = LoggerFactory.getLogger(JdbcFruitShop.class);

@Override
public boolean purchase(int fruitId, String userName, int count) {
Connection conn = null;

try {
Class.forName("com.mysql.jdbc.Driver"); // Load the driver
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8", "spring",
"123456"); // Get the connection

// Query the price
PreparedStatement ps1 = conn.prepareStatement("SELECT PRICE FROM FRUIT WHERE ID = ?");
ps1.setInt(1, fruitId);
ResultSet rs = ps1.executeQuery();
int price = 0;
if (rs.next()) {
price = rs.getInt(1);
}
ps1.close();

// Update the stock
PreparedStatement ps2 = conn.prepareStatement("UPDATE FRUIT_STOCK SET STOCK = STOCK - ? WHERE ID = ?");
ps2.setInt(1, count);
ps2.setInt(2, fruitId);
ps2.executeUpdate();
ps2.close();

// Update the balance
PreparedStatement ps3 = conn
.prepareStatement("UPDATE ACCOUNT SET BALANCE = BALANCE - ? WHERE USERNAME = ?");
ps3.setInt(1, price * count);
ps3.setString(2, userName);
ps3.executeUpdate();
ps3.close();

} catch (SQLException e) {
LOGGER.error("Purchase error:", e);
} catch (ClassNotFoundException e) {
LOGGER.error("driver Loading error:", e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
LOGGER.error("Connection closing error:", e);
}
}
}
return true;
}
}


Spring配置文件:

<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="fruitShop" class="com.john.tx.service.impl.JdbcTxFruitShop"/>
</beans>


测试:

@Resource
FruitShop fruitShop;

@Test
public void test() {
int fruitId = 1;
String userName = "user1";
int count = 3;
fruitShop.purchase(fruitId, userName, count);
}

用户user1的余额是20,买了3个单价为10的苹果,余额不够支付,报错。但是数据处于不一致状态:fruit_stock表的苹果存量由10个减为7个,而账户表的余额还是20,需要使用事务。


使用JDBC的事务操作。

public class JdbcTxFruitShop implements FruitShop {

@Override
public boolean purchase(int fruitId, String userName, int count) {
...
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8", "spring", "123456"); // Get the connection

conn.setAutoCommit(false); // 取消自动提交
...
ps3.close();
conn.commit(); // 提交事务
...
}
}


上面的数据源是写在代码里的,每次修改都需要重新编译,可以将其放在Spring配置中:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf8" />
<property name="username" value="spring" />
<property name="password" value="123456" />
</bean>

<bean id="fruitShop" class="com.john.tx.service.impl.JdbcTxFruitShop">
<property name="dataSource" ref="dataSource" />
</bean>



public class JdbcTxFruitShop implements FruitShop {

private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

@Override
public boolean purchase(int fruitId, String userName, int count) {
...
conn = dataSource.getConnection();
...
}
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/czj4451/article/details/84612870

智能推荐

Axios---axios的用法详解_axios返回值解析成json_Cirrod的博客-程序员秘密

一、axios的基本特性axios (官网: https://qithub.com/axios/axios)是一个基于Promise用于浏览器和node.js的HTTP客户端。它具有以下特征:①支持浏览器和node.js②支持promise③能拦截请求和响应④自动转换JSON数据二、axios的基本用法axios.请求的方式( '请求地址' ).then (res=&gt;{//data属性名称是固定的,用于获取后台响应的数据console.log(res.d...

[Cqoi 2018] bzoj5301 异或序列 [莫队]_5301: [cqoi2018]异或序列_pocket_legend的博客-程序员秘密

Description: 已知一个长度为nnn的整数数列a[1],a[2],…,a[n]a[1],a[2],…,a[n]a[1],a[2],…,a[n],给定查询参数l、rl、rl、r,问在 [l,r][l,r][l,r]区间内,有多少连续子 序列满足异或和等于kkk。 也就是说,对于所有的x,y(l≤x≤y≤r)x,y(l≤x≤y≤r)x,y (l≤x≤y≤r),能够满足a[x]a[x]a...

Xml节点解析 Android,【已解决】Android中用DOM方式解析xml时使用getElementsByTagName去获得节点结果为空..._櫻花朽木的博客-程序员秘密

【问题】折腾:时,试了试下面代码:public static ArrayList getXmlItems(InputStream inputStream, String tag){ArrayList xmlItemList = new ArrayList();DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();...

MySQL社区版下载及安装教程_mysql 免費版_KonBAI-Q的博客-程序员秘密

MySQL社区版下载及安装教程1、下载此教程演示为MySQL8社区版压缩包版本的,点击以下链接进入下载页面(需要Oracle账号,没有的可先进行注册)MySQL下载链接完成后,我们就得到一个压缩包,把这个压缩包进行解压(这里我演示是并不是最新版)2、配置环境变量// 变量值为MYSQL根目录变量名:MYSQL_HOME变量值:~\mysql-8.0.xx-winx64 //(这...

Spyder点击无反应,无法正常启动_spyder双击没反应_bottle想暴富的博客-程序员秘密

Spyder无法正常使用今天一起来打算开始好好学习的时候,多次点击spyder都没啥反应,俺以为我的spyder又完了,抱着试试的心态重启电脑,结果还是没用。在网上查了资料之后,发现了一个方法可以知道是啥问题导致的spyder无法启动,我之前那一次就是重新安装,为难死俺了,记录一下万一忘记了。环境:python3.7 win10一开始无法启动我去网上查资料,很多人通过重新安装pyQT5就成...

随便推点

Netty 粘包和拆包详解_架构攻城之路的博客-程序员秘密

1. 前言前面几个章节主要解析了 Netty 的编码、解码问题,那么是否有了编解码器,我们的 Netty 通信就能正常了呢?TCP 协议在传输数据时没有办法判断数据是什么时候结束的,它无法识别一段完整的信息,因此可能会导致接受到的数据和发送时的数据不一致的情况。因此需要人为的指定一种规范的协议,从而保证数据的安全性,比如:我们所熟悉的 HTTP 协议。本节内容,我们主要需要以下两点知识TCP 拆包、粘包的原因; TCP 拆包、粘包的解决方案。2. 学习目的拆包、粘包在 TCP 协

泰勒公式矩阵形式_泰勒公式,雅可比矩阵,海塞矩阵,牛顿法_黑潮制作所的博客-程序员秘密

泰勒公式,雅可比矩阵,海塞矩阵,牛顿法泰勒公式,雅可比矩阵,海塞矩阵,牛顿法泰勒公式是一个在函数上取某点的近似值,如果函数足够平滑的话,在已知函数在某一点的各阶导数值的情况之下,泰勒公式可以用这些导数值做系数构建一个多项式来近似函数在这一点的近似值。泰勒公式还给出了这个多项式和实际的函数值之间的偏差泰勒公式是将一个在x=x0处具有n阶导数的函数f(x)利用关于(x-x0)的n次多项式来逼近函数的方...

ListView下拉刷新和上拉加载原理_下拉加载逻辑是什么_SunnyRivers的博客-程序员秘密

ListView的下拉刷新逻辑:一、获取ListView的头布局的高度并通过给头布局setPading方法,将头布局隐藏二、重写ListView的onTouchEvent方法计算出滑动的偏移量dy三、通过比较偏移量和头布局的高度,进行UI的修改ListView上拉加载逻辑:一、获取ListView的脚布局的高度并通过setPading方法,将头布局隐藏二、调用ListVie

matlab lsqcurvefit初值,数据拟合与模型选择.ppt_Fay li的博客-程序员秘密

还原为 检验拟合效果:很不理想. x2=1780:2000; y2=exp(b(1)+b(2).*x2); plot(x,y,'+',x2,y2) y3=exp(b(1)+b(2).*x); sum((y-y3).^2) %残差 输出残差平方和(很大): Res1=24917 图2:线性化后拟合效果图 这个初值非常重要,因此前面变换数据以后最小二乘方法得到的估计常常作为现在...

关于IT人的职业生涯的一点感想_Leo笑的博客-程序员秘密

作为一名IT人,边缘化的IT人,合理的战略是,找到一个个人不断升值的位置和空间。        但是具体的技术是不能一直升值的,可以不断升值的方式不断在前沿奋斗,但除非一些特殊的岗位和特殊的人才,否则,不可能永远前进。如何让自己越老越值钱?        许多程序员改行做了产品经理;许多人是赚到了可以保证自己财务自由的第一桶金然后转行;许多是改行开公司,雇佣别人来给自己做产品、做服务。但:

终于有人把 Docker 讲清楚了,万字详解!【建议收藏】_程序员大咖的博客-程序员秘密

作者 | 乐章来源 | cnblogs.com/zhangxingeng/p/11236968.html一、简介1、了解Docker的前生LXCLXC为Linux Container的简写。可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性。相当于C++中的NameSpace。容器有效地将由单个操作系统管理的资源划分到孤立的组中,以更好地在孤立的组之间平...