Spring 声明式事务管理讲解:为什么用,及怎么用

原文地址:https://www.cnblogs.com/caoyc/p/5632198.html

案例分析

本案例是图书管理系统精简部分,在数据库中有3张表。分别保存图书库存、图书信息和用户信息。下面是建表SQL语句

DROP TABLE IF EXISTS store;
DROP TABLE IF EXISTS book ;
DROP TABLE IF EXISTS user;

-- 图书表
CREATE TABLE book(
    sn  VARCHAR(20) PRIMARY KEY ,  -- 图书编码
    name VARCHAR(20) NOT NULL,     -- 图书名称
    price NUMERIC(9,2) NOT NULL    -- 图书价格
);

-- 仓库表
CREATE TABLE store(
    sn VARCHAR(20),         -- 图书编码
    stock INT(9) NOT NULL,   -- 图书库存
    CONSTRAINT fk_sn FOREIGN KEY (sn)  REFERENCES book(sn)
);

-- 用户表
CREATE TABLE user(
    id INT(11) PRIMARY KEY AUTO_INCREMENT,              -- id
    name VARCHAR(20) NOT NULL,        -- 姓名
    balance NUMERIC(9,2) NOT NULL DEFAULT 0 -- 余额
);

INSERT INTO book VALUES ('1001','Java从入门到精通',100);
INSERT INTO book VALUES ('1002','Spring从入门到精通',90);
INSERT INTO book VALUES ('1003','J2EE核心框架',80);

INSERT INTO store VALUES ('1001',50);
INSERT INTO store VALUES ('1002',20);
INSERT INTO store VALUES ('1003',10);

INSERT INTO user (name,balance) VALUES ('caoyc',150);

实体类

Book.java

package com.proc.bean;

public class Book {

    private String sn;
    private String name;
    private Double price;

    public String getSn() {
        return sn;
    }

    public void setSn(String sn) {
        this.sn = sn;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String toString() {
        return "Book [sn=" + sn + ", name=" + name + ", price=" + price + "]";
    }
    
}

Store.java

package com.proc.bean;

/**仓库类*/
public class Store {

    private String sn;
    private Integer stock;
    public String getSn() {
        return sn;
    }
    public void setSn(String sn) {
        this.sn = sn;
    }
    public Integer getStock() {
        return stock;
    }
    public void setStock(Integer stock) {
        this.stock = stock;
    }
    @Override
    public String toString() {
        return "Store [sn=" + sn + ", stock=" + stock + "]";
    }
    
    
}

User.java

package com.proc.bean;

/**
 * @author caoyc
 *
 */
public class User {

    private Integer id;
    private String name;
    private Double balance;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getBalance() {
        return balance;
    }
    public void setBalance(Double balance) {
        this.balance = balance;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", balance=" + balance
                + "]";
    }
    
    
}

Spring配置信息

使用db.properties记录数据库配置信息,这样便于后期维护

1 jdbc.user=root
2 jdbc.password=123456
3 jdbc.driverClass=com.mysql.jdbc.Driver
4 jdbc.jdbcUrl=jdbc\:mysql\:///test

配置applicationContext.xml信息

<!-- 配置自动扫描策略 -->
    <context:component-scan base-package="com.proc"/>
    
    <!-- 读取db.properties配置信息 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置一个C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
    </bean>
    
    <!-- 配置一个JdbcTemplate,用来操作数据库 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

这里我们使用自动扫描策略,使用的是C3P0连接池。同时使用了Spring内置的jdbc封装类JdbcTemplate

数据访问层

Java代码:BookDao.java

package com.proc.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.proc.bean.Book;

/**图书Dao*/
@Repository
public class BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**通过图书编号获取图书信息*/
    public Book get(String sn){
        
        String sql="SELECT * FROM book WHERE sn=?";
        RowMapper<Book> rowMapper=new BeanPropertyRowMapper<Book>(Book.class);
        Book book=jdbcTemplate.queryForObject(sql, rowMapper,sn);
        return book;
    }
}

StoreDao

package com.proc.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.proc.bean.Store;

/**图书仓库Dao*/
@Repository
public class StoreDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**通过图书编号获取图书库存信息*/
    public Store get(String sn){
        String sql="SELECT * FROM store WHERE sn=?";
        RowMapper<Store> rowMapper=new BeanPropertyRowMapper<Store>(Store.class);
        Store store=jdbcTemplate.queryForObject(sql, rowMapper,sn);
        return store;
    }
    
    /**通过图书编号,修改图书库存  库存=当前库存-1*/
    public void update(String sn){
        String sql="UPDATE store SET stock=stock-1 WHERE sn=?";
        jdbcTemplate.update(sql, sn);
    }
}

UserDao.java

package com.proc.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.proc.bean.User;

/**用户Dao*/
@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**通过用户ID获取用户信息*/
    public User get(Integer id){
        String sql="SELECT * FROM user WHERE id=?";
        RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
        User user=jdbcTemplate.queryForObject(sql, rowMapper,id);
        return user;
    }
    
    /**修改用户余额*/
    public void update(Integer id,Double price){
        String sql="UPDATE user SET balance=balance-? WHERE id=?";
        jdbcTemplate.update(sql, new Object[]{price,id});
    }
}

这里为每个Dao都注入了一个JdbcTemplate的实例,可以使用JDBC方式操作数据库

异常处理

考虑到有可能会出现用户余额或图书库存不足的情况,这里我们自定义了两个异常

 1、库存不足异常类:

package com.proc.exception;

public class BookStockException extends RuntimeException{
    public BookStockException(String msg) {
        super(msg);
    }
}

2、余额不足异常类

package com.proc.exception;

public class UserBalanceException extends RuntimeException {
    public UserBalanceException(String msg) {
        super(msg);
    }
}

 逻辑业务层

1、定义一个接口

package com.proc.service;

public interface BookShopService {

    /**
     * 购买图书
     * @param userId 购买用户ID
     * @param sn 图书编号
     */
    void purchase(Integer userId,String sn);
}

2、定义一个BookShopService借口实现类

package com.proc.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.proc.bean.Book;
import com.proc.bean.Store;
import com.proc.bean.User;
import com.proc.dao.BookDao;
import com.proc.dao.StoreDao;
import com.proc.dao.UserDao;
import com.proc.exception.BookStockException;
import com.proc.exception.UserBalanceException;

@Service("bookShopService")
public class BookShopServiceJdbcImps implements BookShopService{

    @Autowired
    private UserDao userDao;
    @Autowired
    private BookDao bookDao;
    @Autowired
    private StoreDao storeDao;
    
    
    /**购买图书方法*/
    public void purchase(Integer userId, String sn) {
        
        //1:查收出图库存信息
        Store store= storeDao.get(sn);
        if(store.getStock()<=0){
            throw new BookStockException("图书库存不足:"+store);
        }
        
        //2:查询图书信息
        Book book=bookDao.get(sn);
        
        
        //3:查询用户信息
        User user=userDao.get(userId);
        if(user.getBalance()<book.getPrice()){
            throw new UserBalanceException("用户余额不足:"+user);
        }
        
        //4:修改库存
        storeDao.update(sn);
        
        //5:修改余额
        userDao.update(userId, book.getPrice());
    }

}

测试代码:

package com.proc.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.proc.service.BookShopService;

public class TestShopBook {

    private ApplicationContext ctx;
    private BookShopService bookShopService;
    {
        ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        bookShopService=(BookShopService) ctx.getBean("bookShopService");
    }
    
    @Test
    public void purchase(){
        
        bookShopService.purchase(1, "1001");
    }
}

第一次执行:

可以成功的看到数据是符合要求的

第二次执行:

  我们看到会抛出异常。由于余额50元以不够买价格为100元的1001编号书籍。所有抛出异常。我们看看数据库中结果怎么样

看起来数据是正确的。由于余额不足,那么购买不成功,所有库存和金额都不会变好。那是不是使用了事务呢?

  答案是:没有。这里没有使用事务。只是因为我们先判断了图书库和用户余额是否足够,然后再执行的修改信息。如果要测试代码。我们将我们逻辑业务层代码中第4步放到第2步前面执行

//1:查收出图库存信息
Store store= storeDao.get(sn);
if(store.getStock()<=0){
    throw new BookStockException("图书库存不足:"+store);
}

//4:修改库存
storeDao.update(sn);

//2:查询图书信息
Book book=bookDao.get(sn);


//3:查询用户信息
User user=userDao.get(userId);
if(user.getBalance()<book.getPrice()){
    throw new UserBalanceException("用户余额不足:"+user);
}

再次执行代码:

虽然在此时还是或抛出余额不足的异常。但是库存却改变了。余额没有改变。所有不满足事务的要求。

那么要怎么办呢?

1、在spring配置文件中配置事务管理器

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 使得事务注解生效 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

事务管理器需要注入一个DataSource接口类型的数据源

2、在需要使用事务管理的方法前加上@Transactional注解

@Transactional
    /**购买图书方法*/
    public void purchase(Integer userId, String sn) {
        
        //1:查收出图库存信息
        Store store= storeDao.get(sn);
        if(store.getStock()<=0){
            throw new BookStockException("图书库存不足:"+store);
        }
        
        //4:修改库存
        storeDao.update(sn);
        
        //2:查询图书信息
        Book book=bookDao.get(sn);
        
        
        //3:查询用户信息
        User user=userDao.get(userId);
        if(user.getBalance()<book.getPrice()){
            throw new UserBalanceException("用户余额不足:"+user);
        }
        
        //5:修改余额
        userDao.update(userId, book.getPrice());
    }

再次执行代码:

虽然还是抛出了异常。但是库存和余额都没有发生变化。这里证明是使用了事务

【总结】:基于声明式的事务就是上面用的这种方法

第一步:在spring配置中配置事务管理器

第二步:在需要使用事务的方法前面加上@Transactional注解

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值