Spring申明式事物

SPring的事物有两种方式,一种是编程式事物,一种是基于注解的申明式事物,编程式事物由纯Java代码编写,侵入性比较强,所以实际项目上应用比较少。本章主要介绍的就是Spring的申明式事物,在介绍Spring事物之前,首先要了解几个基本概念:

  1. 事物是什么?
    官方一点的说法,事物就是数据库的一次原子操作。何为原子操作?就是逻辑上一个不可分割的操作单元,比如本章中举的例子,取商店买书,你要给老板钱(减余额),然后老板给你书(减库存),这是两个不可少的步骤,其中一个操作不成功,那么本次操作就算失败了,这就是事物
  2. 事物的基本特性
    原子性,隔离性,一致性,持久性。(具体本章不再讨论,不清楚的请自行百度)。

本案例中使用的数据库是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来搭建的:

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.3.12.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.3.12.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-dbcp2</artifactId>
      <version>2.1.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.oracle/ojdbc6 -->
    <dependency>
      <groupId>com.oracle</groupId>
      <artifactId>ojdbc6</artifactId>
      <version>12.1.0.1-atlassian-hosted</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.11</version>
    </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<String,Object> findBookInfoById(int id,String fields) throws Exception{
        String sql = "SELECT "+fields+" FROM BOOK WHERE BID="+id+"";
        if (jdbc == null)
            System.out.println("未获取到jdbc");
        Map<String,Object> 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<String,Object> 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<String,Object> 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

<?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"
       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注解即可关于这个注解,有几个参数

  • value String 可选的限定描述符,指定使用的事务管理器
  • propagation enum: Propagation 可选的事务传播行为设置
  • isolation enum: Isolation 可选的事务隔离级别设置
  • readOnly boolean 读写或只读事务,默认读写
  • timeout int (in seconds granularity) 事务超时时间设置
  • rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
  • rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
  • noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
  • noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

显而易见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;

测试结果如下:
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值