SPring的事物有两种方式,一种是编程式事物,一种是基于注解的申明式事物,编程式事物由纯Java代码编写,侵入性比较强,所以实际项目上应用比较少。本章主要介绍的就是Spring的申明式事物,在介绍Spring事物之前,首先要了解几个基本概念:
本案例中使用的数据库是oracle 11g,客户端工具使用的是PLSQL,首先测试之前要创建好相关的表结构和数据:
CREATE TABLE BOOK(
BID INTEGER PRIMARY KEY,--书本主键
BNAME VARCHAR2(50) NOT NULL,--书本名称
PRICE FLOAT NOT NULL,--价格
STOCK INTEGER DEFAULT 0--库存
);
CREATE TABLE CUSTOMER(
CID INTEGER PRIMARY KEY,--人员编号
CNAME VARCHAR2(25) NOT NULL,--人员名称
BALANCE FLOAT DEFAULT 0--人员余额
);
INSERT INTO BOOK VALUES(1,'三国演义',19.5,10);
INSERT INTO BOOK VALUES(2,'红楼梦',21,5);
INSERT INTO CUSTOMER VALUES(1,'张三',250);
这里创建了一张Customer和Book表,并插入了几条数据做测试数据。接下来Java代码的编写,本项目使用mavne和idea来搭建的:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.3.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>4.3.12.RELEASEversion>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-dbcp2artifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>com.oraclegroupId>
<artifactId>ojdbc6artifactId>
<version>12.1.0.1-atlassian-hostedversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.11version>
dependency>
这里引用了spring-aop,spring-core,spring-beans,spring-context,spring-jdbc,spring-tx,aspectjweaver,commons-logging,commons-dbcp,commons-pools等一些关键的jar。
BookDao.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.Map;
@Repository("bookDao")
public class BookDao {
@Autowired
JdbcTemplate jdbc;
public Map findBookInfoById(int id,String fields) throws Exception{
String sql = "SELECT "+fields+" FROM BOOK WHERE BID="+id+"";
if (jdbc == null)
System.out.println("未获取到jdbc");
Map obj = jdbc.queryForMap(sql);
if (obj == null)
throw new RuntimeException("未找到书籍!");
return obj;
}
public void subBookStock(int id,int amount) throws Exception{
String sql = "UPDATE BOOK SET STOCK = STOCK-"+amount+" WHERE ID = "+id;
jdbc.execute(sql);
}
}
CustomerDao.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.Map;
@Repository("customerDao")
public class CustomerDao {
@Autowired
JdbcTemplate jdbc;
public float findCustomerBalance(int id) throws Exception{
String sql = "SELECT BALANCE FROM CUSTOMER WHERE CID = "+id;
Map obj = jdbc.queryForMap(sql);
if (obj == null)
throw new RuntimeException("没有找到该用户!");
return Integer.parseInt(obj.get("BALANCE").toString());
}
public void subCustomerBalance(int id,float total) throws Exception{
String sql = "UPDATE CUSTOMER SET BALANCE = BALANCE-"+total+"WHERE CID = "+id;
}
}
ShopService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Service("shopService")
public class ShopService {
@Autowired
private BookDao bookDao;
@Autowired
private CustomerDao customerDao;
/**
* 销售数据
* @param bid 书本编号
* @param cid 人员编号
* @param amount 书本数量
*/
@Transactional
public void sellBook(int bid,int cid,int amount) throws Exception {
Map bookInfo = bookDao.findBookInfoById(bid,"PRICE,STOCK");
float balance = customerDao.findCustomerBalance(cid);
float price = Float.parseFloat(bookInfo.get("PRICE").toString());
int stock = Integer.parseInt(bookInfo.get("STOCK").toString());
float total = price*amount;
if (balance < total)
throw new RuntimeException("余额不足!");
customerDao.subCustomerBalance(cid, total);//顾客交钱
if (stock < amount)
throw new RuntimeException("库存不足!");
bookDao.subBookStock(bid, amount);//减书本库存
}
}
App.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App
{
public static void main( String[] args )
{
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
ShopService shopService = (ShopService) context.getBean("shopService");
if (shopService == null)
System.out.println("获取shopService失败");
try {
shopService.sellBook(1,1,11);//可以修改相关的参数和购买数量
System.out.println("出售成功");
}catch (Exception e){
System.out.println("出错--->"+e.getLocalizedMessage());
}
}
}
spring-context.xml
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd ">
<beans>
<context:annotation-config/>
<tx:annotation-driven/>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:ORCL"/>
<property name="username" value="amxing"/>
<property name="password" value="950411"/>
<property name="defaultAutoCommit" value="true"/>
<property name="defaultQueryTimeout" value="10000"/>
<property name="enableAutoCommitOnReturn" value="true"/>
bean>
<bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="bookDao" class="com.amx.BookDao"/>
<bean id="customerDao" class="com.amx.CustomerDao"/>
<bean id="shopService" class="com.amx.ShopService"/>
<tx:advice id="tx_bookservice">
<tx:attributes>
<tx:method name="sellBook" rollback-for="Exception" propagation="MANDATORY"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="sellMethod" expression="execution(* com.amx.ShopService.sellBook(..))"/>
<aop:advisor advice-ref="tx_bookservice" pointcut-ref="sellMethod"/>
aop:config>
beans>
beans>
这就是主要的代码,可以看到spring的申明式事物主要是基于aop来实现的,配置好事物规则,再在相关的方法上添加@Transactional注解即可关于这个注解,有几个参数
显而易见sellBook方法的参数为1,1,11,就是张三这个人要买11本红楼梦,但是呢,红楼梦只有10,所以事物是失败的,但是在sellBook方法中,用户是先交钱,张三的余额有250元是足够买11本红楼梦的,所以这时张三的余额是会减少的,但是接下来库存不够了,书本减少库存这个操作就不能做了,所以如果我们此处不用事物的话,会出现以下的情况:
程序执行失败,然后取查看一下用户的余额:
余额减少了19.5*11元,再去看一下书本的库存:
这个时候就发现问题了,用户的余额减少了,但是商品的库存没有减少,这在逻辑上是完全不正确的,所以这个时候就体现了事物的作用。如果这里我们使用了事物就不会出现这样的情况了。
这是一个最基本的Spring申明式事物的小例子,当然在实际开发的过程中,很多复杂的逻辑会写在存储过程中,这里我也写了一个小例子,仅供参考:
--出售商品
CREATE OR REPLACE PROCEDURE PROC_SELL_BOOK(V_BID IN INTEGER,
V_CID IN INTEGER,
V_AMOUNT IN INTEGER,
V_RESULT OUT VARCHAR2) AS
SELL_EXCEPTION EXCEPTION;
V_SLID INTEGER; --日志ID
V_NAME VARCHAR2(50); --商品名称
V_STOCK INTEGER; --库存数量
V_PRICE FLOAT; --商品价格
V_CNAME VARCHAR2(50); --顾客姓名
V_BALANCE FLOAT; --账户余额
BEGIN
SELECT SELL_LOG_ID.NEXTVAL INTO V_SLID FROM DUAL;
SELECT BNAME, PRICE, STOCK
INTO V_NAME, V_PRICE, V_STOCK
FROM BOOK
WHERE BID = V_BID;
SELECT CNAME, BALANCE
INTO V_CNAME, V_BALANCE
FROM CUSTOMER
WHERE CID = V_CID;
IF V_BALANCE < V_PRICE * V_AMOUNT THEN
V_RESULT := V_CNAME || '的余额不足!';
RAISE SELL_EXCEPTION;
ELSE
UPDATE CUSTOMER
SET BALANCE = BALANCE - V_PRICE * V_AMOUNT
WHERE CID = V_CID;
END IF;
IF V_STOCK < V_AMOUNT THEN
V_RESULT := V_NAME || '的库存不足!';
RAISE SELL_EXCEPTION;
ELSE
UPDATE BOOK SET STOCK = STOCK - V_AMOUNT WHERE BID = V_BID;
END IF;
INSERT INTO SELL_LOG VALUES (V_SLID, V_BID, V_CID, V_RESULT, SYSDATE);
COMMIT;
EXCEPTION
WHEN SELL_EXCEPTION THEN
ROLLBACK;
INSERT INTO SELL_LOG VALUES (V_SLID, V_BID, V_CID, V_RESULT, SYSDATE);
COMMIT;
WHEN NO_DATA_FOUND THEN
V_RESULT := '未找到对应的商品或用户,请检查用户ID是是否正确!';
INSERT INTO SELL_LOG VALUES (V_SLID, V_BID, V_CID, V_RESULT, SYSDATE);
COMMIT;
END;