`

本地事务系列之一:JDBC操作

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

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

准备数据:
-- 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();
   		...
	}
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics