Spring申明式事物

来源:互联网 发布:vb对皮肤瘙痒有用吗 编辑:程序博客网 时间:2024/05/21 20:24

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;

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

原创粉丝点击