Spring4-3-使用JdbcTemplate和事务处理

一.使用JdbcTemplate操作数据库

  • 创建db.properties配置文件
jdbc.user=common
jdbc.password=common
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///db_spring

jdbc.initPoolSize=5
jdbc.maxPoolSize=10
  • 配置Spring配置文件applicationContext.xml
    
    
     
     
    
    
       
        
        
        
        
        
        
        
    
    
    
    
        
    
    
    
  • 单元测试JDBCTest.java
package lxf.spring.jdbc;
/**
 * 测试jdbc操作
 * @author lxf
 */
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.sql.DataSource;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import lxf.spring.jdbc.bean.Goods;
import lxf.spring.jdbc.dao.GoodsDao;

public class JDBCTest {
    private ApplicationContext  ctx= null;
    private JdbcTemplate jdbcTemplate;
    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
    }
    /**
     * 获取单个列值,或做统计查询
     */
    @Test
    public void testQueryTotal()
    {
        String sql = "SELECT COUNT(goods_id) FROM goods";
        long count = jdbcTemplate.queryForObject(sql,long.class);
        System.out.println(count);
    }
    /**
     * 查到实体类的集合
     */
    @Test
    public void testQueryForList()
    {
        String sql = "SELECT * FROM goods WHERE goods_id > ?";
        RowMapper rowMapper = new BeanPropertyRowMapper<>(Goods.class);
        List goods = jdbcTemplate.query(sql, rowMapper,1);
        System.out.println(goods);      
        
    }   
    /**
     * 从数据库获取一条记录,实际得到对应的对象
     */
    @Test
    public void testQueryForObject()
    {
        String sql = "SELECT *  FROM  goods WHERE goods_id = ?";
        RowMapper rowMapper = new BeanPropertyRowMapper<>(Goods.class);
        Goods goods = jdbcTemplate.queryForObject(sql, rowMapper,1);
        System.out.println(goods);      
    }
    
    /**
     * 执行批量更新,批量insert update delete
     */
    @Test
    public void testBatchUpate()
    {
        String sql = "INSERT INTO goods (goods_name,cost_price,selling_price,manufactuer) VALUES (?,?,?,?)";
        List batchArgs = new ArrayList();
        batchArgs.add(new Object[]{"AA","100","101","shanghai"});
        batchArgs.add(new Object[]{"BB","200","201","beijing"});
        batchArgs.add(new Object[]{"CC","300","301","guangzhou"});
        int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);
        System.out.println(Arrays.asList(res));
    }
    /**
     * 修改数据
     */
    @Test
    public void testUpdate()
    {
        String sql = "UPDATE goods SET goods_name = ? WHERE goods_id = ?";
        jdbcTemplate.update(sql,"aaa",1);
    }   
    /**
     * 测试获取数据库连接
     */
    @Test
    public void testDataSource()
    {
        DataSource dataSource = (DataSource) ctx.getBean("dataSource");
        try {
            System.out.println(dataSource.getConnection());
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    /**
     * 单元测试dao层查询单条记录
     */
    @Test
    public void testGetOne()
    {
        GoodsDao gD = (GoodsDao) ctx.getBean("goodsDao");
        Goods goodsOne = gD.getOne(1);
        System.out.println(goodsOne);
    }
}
  • GoodsDao的编写
package lxf.spring.jdbc.dao;

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

import lxf.spring.jdbc.bean.Goods;
/**
 * GoodsDao操作数据库
 * @author lxf
 *
 */
@Repository
public class GoodsDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * 根据主键获取goods表单条记录
     * @param id
     * @return
     */
    public Goods getOne(Integer id)
    {
        String sql = "SELECT *  FROM  goods WHERE goods_id = ?";
        RowMapper rowMapper = new BeanPropertyRowMapper<>(Goods.class);
        Goods goods=null;
        try {
            goods = jdbcTemplate.queryForObject(sql, rowMapper,id);
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return goods;
    }
}

二.使用Jdbc具名参数模板NamedParameterJdbcTemplate操作数据库

  • 原来的jdbc在sql语句中预加载的是?,后续需要对应位置;
  • 而具名参数就是将预加载的 ?参数名称 代替
    具体使用方式如下:
    (1)在Spring配置文件中配置NamedParameterJdbcTemplate的Bean
    
    
                  
      

(2)使用:

    private JdbcTemplate jdbcTemplate;
    //具名参数jdbcTemplate
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
        namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
    }
    /**
     * 使用具名参数时,可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作
     * 1.SQL语句中的参数名和类的属性名保持一致
     * 2.使用SqlParameterSource接口的实现类BeanPropertySqlParameterSource作为参数
     */
    @Test
    public void testNameParamJdbc2()
    {
        String sql = "INSERT INTO goods  (goods_name,cost_price,selling_price,manufactuer) " + 
                            "VALUES (:goods_name,:cost_price,:selling_price,:manufactuer)";
        Goods goods = new Goods();
        goods.setGoods_name("耐克运动鞋");
        goods.setCost_price(800);
        goods.setSelling_price(699);
        goods.setManufactuer("产地山东");
        SqlParameterSource paramSource = new BeanPropertySqlParameterSource(goods);
        int res = namedParameterJdbcTemplate.update(sql, paramSource);
        System.out.println(res);
    }
    
    /**
     * 测试jdbc具名参数的新增数据库操作
     * 好处:若有多个参数则不用在去对应位置,直接对应参数名,便于维护
     */
    @Test
    public void testNameParamJdbc()
    {
        String sql = "INSERT INTO goods  (goods_name,cost_price,selling_price,manufactuer) VALUES (:gname,:cprice,:sprice,:manf)";
        Map paramMap = new HashMap();
        paramMap.put("gname", "阿迪达斯运动鞋");
        paramMap.put("cprice", "500");
        paramMap.put("sprice", "459");
        paramMap.put("manf", "产地河北");
        int res = namedParameterJdbcTemplate.update(sql, paramMap);
        System.out.println(res);
    }

三,事务(使用注解的方式配置事务)

Paste_Image.png
Paste_Image.png
  • Spring配置文件配置
      
      
        
      
      
      
  • 在需要加事务的业务逻辑方法上加注解:@Transactional
package lxf.spring.tx;
import javax.management.RuntimeErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookShopServiceImpl {
    
    @Autowired
    private BookShopDao bookShopDao;
    /**
     * 用户买书方法
     * @param userId 用户账户表account表主键
     * @param bookId   书books表主键
     */
    //添加事务注解,只在在执行的方法中执行多个业务罗辑之间有一个抛出异常则会执行事务
    //如果有异常但是不抛出,则事务不起作用
    @Transactional
    public void buyBook  (int userId, int bookId) 
    {
        //1.获取对应数的单价
        double price = bookShopDao.findBookPriceBookId(bookId);
        //2.减库存
        try {
            bookShopDao.updateBookStock(bookId);
        } catch (Error e1) {
            System.out.println("book stock error");
            //e1.printStackTrace();
            throw new RuntimeException(e1);
        }
        //3.扣除用户账户余额
        try {
            bookShopDao.updateUserAccount(userId, price);
        } catch (AccountException e) {
            throw new RuntimeException(e);
        }
    }
}

BookShopDao

package lxf.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/**
 * 书店dao
 * @author lxf
 */
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public double findBookPriceBookId(Integer bookId) {
        String sql = "SELECT price from books WHERE book_id = ?";
        return jdbcTemplate.queryForObject(sql, double.class, bookId);
    }

    @Override
    public void updateBookStock(Integer bookId) {
        String sql1 = "SELECT stock from book_stock WHERE book_id= ?";
        double stock = jdbcTemplate.queryForObject(sql1, double.class, bookId);
        //如果库存不足则抛出异常
        if(stock<=0)
        {
            throw new BookStockException("图书库存不足");
        }      
       String sql = "UPDATE book_stock SET stock = stock-1 WHERE book_id = ?";
       jdbcTemplate.update(sql, bookId);
    }

    @Override
    public void updateUserAccount(Integer userId, double price) {
        String sql1 = "SELECT balance from acount WHERE id= ?";
        int balance = jdbcTemplate.queryForObject(sql1, Integer.class, userId);
        //如果用户余额不足则抛出异常
        if(balance<=0)
        {
            throw new AccountException("用户余额不足!");
        }
        String sql = "UPDATE acount SET balance=balance-? WHERE id = ?";
        jdbcTemplate.update(sql, price, userId);
    }
}
  • 用户余额不足异常定义
package lxf.spring.tx;
/**
 * 用户余额不足异常
 * @author lxf
 */
public class AccountException extends RuntimeException{
    public AccountException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public AccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
        // TODO Auto-generated constructor stub
    }

    public AccountException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public AccountException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }

    public AccountException(Throwable cause) {
        super(cause);
        // TODO Auto-generated constructor stub
    }  
}

四.事务传播行为

Paste_Image.png

五.Spring事务传播行为

Paste_Image.png
Paste_Image.png

六.REQUIED默认的传播行为

  • Tom用户的钱只够买一本书,当A用户选择两本书购买的时候,因为第二本书钱不够了,所以 两本书都没有买成功
    Paste_Image.png

七.REQUIRED_NEW传播行为

  • Tom用户的钱只够买一本书,当A用户选择两本书购买的时候,第一本书的事务提交,账户钱成功扣除,库存成功-1; 第二本书因为钱不够事务回滚,账户钱和库存均不变;(第一本买成功,第二本没买成功
    Paste_Image.png

八.事务传播行为代码实现

(1)以上代码BookShopServiceImpl修改

@Service
public class BookShopServiceImpl {
    
    @Autowired
    private BookShopDao bookShopDao;
    /**
     * 用户买书方法
     * @param userId 用户账户表account表主键
     * @param bookId   书books表主键
     */
    /**
     * 添加事务注解,只在在执行的方法中执行多个业务罗辑之间有一个抛出异常则会执行事务
     * 如果有异常但是不抛出,则事务不起作用
     * 使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时
     * 如何使用事务,默认取值为REQUIRED,即使用调用方法的事务
     * REQUIRED_NEW:事务自己的事务,调用的事务方法的事务被挂起;
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void buyBook  (int userId, int bookId) 
    {
        //1.获取对应数的单价
        double price = bookShopDao.findBookPriceBookId(bookId);
        //2.减库存
        try {
            bookShopDao.updateBookStock(bookId);
        } catch (Error e1) {
            System.out.println("book stock error");
            //e1.printStackTrace();
            throw new RuntimeException(e1);
        }
        //3.扣除用户账户余额
        try {
            bookShopDao.updateUserAccount(userId, price);
        } catch (AccountException e) {
            throw new RuntimeException(e);
        }
    }
}

(2)新增收银台接口负责处理用户同事买多本书的桂芬

package lxf.spring.tx;
import java.util.List;

/**
 * 收银台接口
 * @author lxf
 */
public interface Cashier { 
    public void checkout(int userId, ListbooksID);
}

(3)Cashier收银台实现类CashierImpl

package lxf.spring.tx;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * 收银台实现类,测试事务的传播行为
 * @author lxf
 */
@Service("cashier")
public class CashierImpl implements Cashier {
    @Autowired
    private BookShopServiceImpl bookShopServiceImpl;
    /**
     * 一个用户买多本书的情况,新增事务
     */
    @Transactional
    @Override
    public void checkout(int userId, List booksID) {
        for (Integer bookId : booksID) {
            bookShopServiceImpl.buyBook(userId, bookId);
        }
    }
}

(4)单元测试

ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
 cashier = (Cashier)ctx.getBean("cashier");
    /**
     * 单元测试事务传播行为
     */
    @Test
    public void testTransChuanbo()
    {
        int userId= 1;
        List booksID = Arrays.asList(1,2);
        cashier.checkout(userId, booksID);
    }

事务属性说明

    /**
     * 添加事务注解,只在在执行的方法中执行多个业务罗辑之间有一个抛出异常则会执行事务
     * 如果有异常但是不抛出,则事务不起作用
     * 1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时
     *     如何使用事务,默认取值为REQUIRED,即使用调用方法的事务
     *     REQUIRED_NEW:事务自己的事务,调用的事务方法的事务被挂起;
     * 2.指定事务的隔离级别属性,最常用的取值是:isolation=Isolation.READ_COMMITTED
     * 3.默认情况下Spring的声明事务对所有的运行时异常进行回滚,
     *        也可以通过对应的属性进行设置,通常情况下取默认值即可,
     *        比如:指定发生AccountException异常不回滚,noRollbackFor={AccountException.class}
     * 4.使用readOnly指定事务是否是可读,若只读取数据库的方法,
     *         应设置readOnly=true,可以帮助数据库引擎优化事务.
     * 5.使用timeout属性指定强制回滚之间事务可以占用的时间,单位为:秒
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
                                isolation=Isolation.READ_COMMITTED,
                                readOnly=false,
                                timeout=5)

九.使用xml配置文件配置事务 applicationContext-xmlconfig-tx.xml



    
    
     
     
    
    
       
        
        
        
        
        
        
        
    
    
    
    
        
    
    
     
     
        
     
     
        
     
     
        
     
      
      
      
        
      
      
      
        
            
            
            
        
      
      
      
        
        
        
        
     

十, 手动回滚

  • 在需要回滚的地方使用如下代码:
     TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    
  • 或先设置回滚点
    Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 
    
    然后回滚到具体回滚点
    //回滚到savePoint。
    TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
    
  • 参考文章

代码演示点击

你可能感兴趣的:(Spring4-3-使用JdbcTemplate和事务处理)