Spring JDBC 和事务控制

目录

1.主要内容

2.Spring整合JDBC环境 

2.1.添加依赖坐标

2.2.添加jdbc文件

2.3.修改spring文件

2.4.配置数据源

2.4.1.C3P0数据源配置

2.4.2.DBCP数据源配置

2.5.模板类配置

2.6.JDBC测试

2.6.1.创建指定数据库

2.6.2.创建数据表

2.6.3.使用JUnit测试

3.持久层账户模块操作

3.1.账户接口方法定义

3.1.1.定义实体类

3.1.2.定义接口类

3.1.3.定义接口实现类

 

4.Spring事务控制

4.1.转账场景模拟实现

4.1.1.接口方法定义

4.1.2.实现对应接口

4.1.3.转账方法实现

4.2.Spring事务概念

4.2.1.事务的四大特性

4.2.2.Spring事务核心接口

4.3.Spring事务控制配置

4.3.1.XML配置

4.3.2.注解配置


1.主要内容

 

2.Spring整合JDBC环境 

Spring框架出路提供IOC与AOP核心功能外,同样提供了基于JDBC的数据访问功能,使得访问持久层数据更加方便。使用SpringJDBC环境,首先需要一套Spring整合JDBC的环境。

2.1.添加依赖坐标

<!--  spring 框架坐标依赖添加-->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!--  spring 测试环境 -->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.4.RELEASE</version>
      <scope>test</scope>
    </dependency>
    <!-- aop -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>

    <!-- spring jdbc-->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- spring 事务-->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- mysql 驱动包 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.19</version>
    </dependency>


    <!--  c3p0-->
    <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.5</version>
    </dependency>
    

2.2.添加jdbc文件

src/main/resources目录下新建jdbc.properties配置文件,并设
置对应的配置信息
# 驱动名
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库连接
jdbc.url=jdbc:mysql://localhost:3306/(数据库名称)?
useUnicode=true&characterEncoding=utf8&serverTime
zone=GMT%2B8&useSSL=false
# 数据库用户名称
jdbc.user=(数据库账号)
# 数据库用户密码
jdbc.password=(数据库密码)

以下为可选配置

# 指定连接池的初始化连接数。取值应在minPoolSize 与
maxPoolSize 之间.Default:3
initialPoolSize=20
# 指定连接池中保留的最大连接数. Default:15
maxPoolSize=100
# 指定连接池中保留的最小连接数
minPoolSize=10
# 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢
弃。 Default:0
maxIdleTime=600
# 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数.
Default:3
acquireIncrement=5
# JDBC的标准,用以控制数据源内加载的PreparedStatements数量。
maxStatements=5
# 每60秒检查所有连接池中的空闲连接.Default:0
idleConnectionTestPeriod=60

2.3.修改spring文件

<!-- 加载properties 配置文件,用来读取jdbc.properties
文件中的数据 -->
<context:property-placeholder
location="jdbc.properties" />

spring.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.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">

    <!-- Spring自动扫描注解的配置 -->
    <context:component-scan base-package="com.liuxidong"/>

    <!-- 加载properties 配置文件 -->
    <context:property-placeholder location="jdbc.properties"/>
</beans>

2.4.配置数据源

由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接
池预先同数据库建立一些连接,放在内存中,应用程序需要建立数
据库连接时直接到连接池中申请一个就行,用完后再放回去。
C3P0 DBCP 二选一即可
DBCP(DataBase connection pool),数据库连接池。是 apache
的一个 java 连接池项目,也是 tomcat 使用的连接池组件。单独使
dbcp需要2个包:commons-dbcp.jarcommons-pool.jar
dbcp,没有自动回收空闲连接的功能。
C3P0是一个开源的JDBC连接池,它实现了数据源,支持JDBC3
范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate
Spring等。c3p0有自动回收空闲连接功能。

2.4.1.C3P0数据源配置

  <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- property标签的value属性对应的是jdbc.properties中的值 -->
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

2.4.2.DBCP数据源配置

<!-- 配置dbcp数据源-->
<bean id="myDataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
>
     <property name="driverClassName"
value="${jdbc.driver}" />
     <property name="url" value="${jdbc.url}"/>
     <property name="username"
value="${jdbc.user}"/>
     <property name="password"
value="${jdbc.password}"/>
     <!-- 连接池启动时的初始值 -->  
     <property name="initialSize" value="1"/>  
     <!-- 最大空闲值.当经过一个高峰时间后,连接池可以将
已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --
>  
     <property name="maxIdle" value="2"/>  
     <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就
会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 --
>  
     <property name="minIdle" value="1"/>  
</bean>

2.5.模板类配置

Spring把JDBC中重复的操作建立成了一个模板类:org.springframework.jdbc.core.JdbcTemplate

<!-- 配置JdbcTemplate实例,并注入一个dataSource数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"> </property>
    </bean>

2.6.JDBC测试

2.6.1.创建指定数据库

 

2.6.2.创建数据表

 

2.6.3.使用JUnit测试

通过junit测试jdbcTemplate bean是否获取到

2.6.3.1.JUnit测试

public class SpringJdbcTest01 {
    @Test
    public void test01(){
        // 得到上下文环境
        ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("spring.xml");
        // 得到模版类
        JdbcTemplate jdbcTemplate = (JdbcTemplate)c.getBean("jdbcTemplate");
        // 定义sql
        String sql = "select count(1) from  tb_account where user_id = 1";
        // 执行查询操作
        Integer total = jdbcTemplate. queryForObject(sql, Integer.class);
        System.out.println("总记录数: " + total);

    }
}

2.6.3.2.简单封装

public class SpringJdbcTest02 {

    private JdbcTemplate jdbcTemplate;

    @Before
    public void init(){
        // 得到上下文环境
        ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("spring.xml");
        // 得到模版类
        jdbcTemplate = (JdbcTemplate)c.getBean("jdbcTemplate");
    }

    @Test
    public void test01(){
        // 定义sql
        String sql = "select count(1) from  tb_account where user_id = 1";
        // 执行查询操作
        Integer total = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数: " + total);

    }
}

2.6.3.3.注解封装

@RunWith
 就是一个运行器
 @RunWith(JUnit4.class) 就是指用JUnit4来运行
 @RunWith(SpringJUnit4ClassRunner.class) 让测试
运行于Spring测试环境
@ContextConfiguration
 Spring整合JUnit4测试时,使用注解引入多个配置文件

@RunWith(SpringJUnit4ClassRunner.class)  // 将测试运行在spring测试环境中
@ContextConfiguration(locations = {"classpath:spring.xml"})  // 设置要加载的配置文件
public class SpringJdbcTest03 {

    @Resource
    private JdbcTemplate jdbcTemplate;



    @Test
    public void test01(){
        // 定义sql
        String sql = "select count(1) from  tb_account where user_id = 1";
        // 执行查询操作
        Integer total = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数: " + total);

    }
}

2.6.3.4.通用封装

定义一个父类,设置通用的配置信息

@RunWith(SpringJUnit4ClassRunner.class)  // 将测试运行在spring测试环境中
@ContextConfiguration(locations = {"classpath:spring.xml"})  // 设置要加载的配置文件
public class BaseTest {
}

继承通用的测试类

public class SpringJdbcTest04 extends BaseTest {

    @Resource
    private JdbcTemplate jdbcTemplate;


    @Test
    public void test01(){
        // 定义sql
        String sql = "select count(1) from  tb_account where user_id = 1";
        // 执行查询操作
        Integer total = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数: " + total);

    }
}

3.持久层账户模块操作

当完成Spring JDBC环境集成后,这里使用Spring JDBC完成账户单表crud操作。

3.1.账户接口方法定义

3.1.1.定义实体类

Account.java
package com.liuxidong.po;


import java.util.Date;

/*
*   用户账户类
* */
public class Account {
    private Integer accountID;  // 账户ID 主键
    private String accountName;  // 账户名称
    private String accountType;  // 账户类型
    private Double money;        // 账户金额
    private String remark;       // 账户备注
    private Date createTime;     // 创建时间
    private Date updateTime;     // 修改时间
    private Integer userId;      // 用户ID

    public Account() {
    }

    public Account(String accountName, String accountType, Double money, String remark, Integer userId) {
        this.accountName = accountName;
        this.accountType = accountType;
        this.money = money;
        this.remark = remark;
        this.userId = userId;
    }

    public Integer getAccountID() {
        return accountID;
    }

    public void setAccountID(Integer accountID) {
        this.accountID = accountID;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public String getAccountType() {
        return accountType;
    }

    public void setAccountType(String accountType) {
        this.accountType = accountType;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "Account{" +
                "accountID=" + accountID +
                ", accountName='" + accountName + '\'' +
                ", accountType='" + accountType + '\'' +
                ", money=" + money +
                ", remark='" + remark + '\'' +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                ", userId=" + userId +
                '}';
    }


}

3.1.2.定义接口类

IAccountDao.java
package com.liuxidong.dao;

import com.liuxidong.po.Account;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author lxd
 * #Description IAccountDao
 * #Date: 2023/4/12 20:15
 */
/**
*   账户模块
*       1.添加账户
*           添加账户记录 返回受影响的行数
*           添加账户记录 返回主键
*           批量添加账户记录 返回受影响的行数
*       2.修改账户
*           修改账户记录 返回受影响的行数
*           批量修改账户记录 返回受影响的行数
*       3.删除账户
*           删除账户记录 返回受影响的行数
*           批量删除账户记录 返回受影响的行数
*       4.查询账户
*           查询指定账户的总记录数 返回总记录数
*           查询指定账户的账户详情 返回账户对象
*           多条件查询指定用户的账户列表 返回账户集合
* */
public interface IAccountDao {

    /**
     *  添加账户
     *      添加账户记录 返回受影响的行数
     *  @Author lxd
     * @Param account
     * @return int
     **/
    public int addAccount(Account account);

    /**
     *  添加账户
     *      添加账户记录 返回主键
     * @Author lxd
     * @Param account
     * @return int
     **/
    public int addAccountHasKey(Account account);

    /**
     *  添加账户
     *      批量添加账户记录 返回受影响的行数
     * @Author lxd
     * @Param accounts
     * @return int
     **/
    public int addAccountBatch(List<Account> accounts);

    /**
     *  查询账户
     *      查询指定用户的账户的总记录数 返回总记录对象
     * @Author lxd
     * @Param userId
     * @return
     **/
    public int queryAccountCount(int userId);

    /**
     *  查询用户
     *     查找指定账户的账户详情 返回账户对象
     * @Author lxd
     * @Param account
     * @return
     **/
    public Account queryAccountById(int accountId);

    /**
     * 查询账户
     *      多条件查询指定用户的账户列表,返回账户集合
     * @param userId    指定用户的ID
     * @param accountName   账户名称(模糊查询)
     * @param accountType   账户类型
     * @param createTime    创建时间(大于当前时间)
     * @return
     */
    public List<Account> queryAccountByParams(Integer userId, String accountName,String accountType, String createTime);

    /**
     * 修改账户
     *      修改账户记录,返回受影响的行数
     * @param account
     * @return
     */
    public int updateAccount(Account account);

    /**
     * 修改账户
     *      批量修改账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    public int updateAccountBatch(List<Account> accounts);

    /**
     * 删除账户
     *      删除账户记录,返回受影响的行数
     * @param accountId
     * @return
     */
    public int deleteAccount(int accountId);

    /**
     * 删除账户
     *      批量删除账户记录,返回受影响的行数
     * @param ids
     * @return
     */
    public int deleteAccountBatch(Integer[] ids);

    /**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    public int outAccount(Integer accountId, Double money);

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    public int inAccount(Integer accountId, Double money);

}

3.1.3.定义接口实现类

AccountDaoImpl.java

package com.liuxidong.dao.impl;

import com.liuxidong.dao.IAccountDao;
import com.liuxidong.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

/**
 * @author lxd
 * #Description AccountDaoImpl
 * #Date: 2023/4/12 20:45
 */
@Repository
public class AccountDaoImpl  implements IAccountDao {
    // 注入JdbcTemplate模板

    @Resource
    private JdbcTemplate jdbcTemplate;

    /** @Author lxd
     *  添加账户记录返回受影响的行数
     * @Param account
     * @return
     **/
    @Override
    public int addAccount(Account account) {
        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";
        // 设置参数
        Object[] objs = {account.getAccountName(),account.getAccountType(),account.getMoney(),
        account.getRemark(),account.getUserId()};

        int row = jdbcTemplate.update(sql,objs);

        return row;
    }

    /**
     * 添加账户记录,返回主键
     * @param account
     * @return
     */
    @Override
    public int addAccountHasKey(Account account) {

        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";

        // 定义KeyHolder对象  获取记录的主键值
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(connection -> {
            // 预编译sql语句,并设置返回主键
            PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            // 设置参数
            ps.setString(1,account.getAccountName());
            ps.setString(2,account.getAccountType());
            ps.setDouble(3,account.getMoney());
            ps.setString(4,account.getRemark());
            ps.setInt(5,account.getUserId());
            // 返回预编译对象
            return ps;
        }, keyHolder);

        // 得到返回的主键
        int key = keyHolder.getKey().intValue();
        return key;
    }


    /**
     * 批量添加账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int addAccountBatch(List<Account> accounts) {
        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";

        int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account account = accounts.get(i);
                // 设置参数
                ps.setString(1,account.getAccountName());
                ps.setString(2,account.getAccountType());
                ps.setDouble(3,account.getMoney());
                ps.setString(4,account.getRemark());
                ps.setInt(5,account.getUserId());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;

        return rows;
    }


    /**
     * 查询指定用户的账户总记录数,返回总数量
     * @param userId
     * @return
     */
    @Override
    public int queryAccountCount(int userId) {
        // 定义sql语句
        String sql = "select count(1) from tb_account where user_id = ? ";
        // 查询方法
        int count = jdbcTemplate.queryForObject(sql, Integer.class, userId);
        return count;
    }


    /**
     * 查询指定账户记录详情,返回账户对象
     * @param accountId
     * @return
     */
    @Override
    public Account queryAccountById(int accountId) {
        // 定义sql语句
        String sql = "select * from tb_account where account_id = ? ";
        // 查询对象
        Account account = jdbcTemplate.queryForObject(sql, (ResultSet rs, int i) ->  {
            Account acc = new Account();
            acc.setAccountID(accountId);
            acc.setAccountName(rs.getString("account_name"));
            acc.setAccountType(rs.getString("account_type"));
            acc.setMoney(rs.getDouble("money"));
            acc.setRemark(rs.getString("remark"));
            acc.setUserId(rs.getInt("user_id"));
            acc.setCreateTime(rs.getDate("create_time"));
            acc.setUpdateTime(rs.getDate("update_time"));
            return acc;
        },accountId);
        return account;
    }

    /**
     * 多条件查询指定用户的账户记录列表,返回账户集合
     * @param userId    指定用户的ID
     * @param accountName   账户名称(模糊查询)
     * @param accountType   账户类型
     * @param createTime    创建时间(大于当前时间)
     * @return
     */
    @Override
    public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {
        // 定义sql语句
        String sql = "select * from tb_account where user_id = ? ";
        // 定义参数列表
        List<Object> params = new ArrayList<>();
        params.add(userId);

        // 判断参数是否为空,如果不为空,拼接sql语句及设置对应的参数
        // 账户名称
        if (StringUtils.isNotBlank(accountName)){
            // 拼接sql语句
            sql += " and account_name like concat('%',?,'%') ";
            // 设置参数
            params.add(accountName);
        }
        // 账户类型
        if (StringUtils.isNotBlank(accountType)){
            // 拼接sql语句
            sql += " and account_type = ? ";
            // 设置参数
            params.add(accountType);
        }
        // 创建时间
        if (StringUtils.isNotBlank(createTime)){
            // 拼接sql语句
            sql += " and create_time < ? ";
            // 设置参数
            params.add(createTime);
        }

        // 将集合转换为数组
        Object[] objs = params.toArray();
        // 查询集合
        List<Account> accountList = jdbcTemplate.query(sql, objs, (ResultSet rs, int i) ->  {
            Account acc = new Account();
            acc.setAccountID(rs.getInt("account_id"));
            acc.setAccountName(rs.getString("account_name"));
            acc.setAccountType(rs.getString("account_type"));
            acc.setMoney(rs.getDouble("money"));
            acc.setRemark(rs.getString("remark"));
            acc.setUserId(rs.getInt("user_id"));
            acc.setCreateTime(rs.getTimestamp("create_time"));
            acc.setUpdateTime(rs.getTimestamp("update_time"));
            return acc;
        });
        return accountList;
    }

    /**
     * 修改账户,返回受影响的行数
     * @param account
     * @return
     */
    @Override
    public int updateAccount(Account account) {
        // 定义sql
        String sql = "update tb_account set account_name = ? , account_type = ? , money = ? , remark = ? , " +
                " update_time = now(), user_id = ? where account_id = ?";
        // 设置参数
        Object[] objs = {account.getAccountName(),account.getAccountType(),account.getMoney(),
                account.getRemark(),account.getUserId(),account.getAccountID()};
        int row = jdbcTemplate.update(sql, objs);
        return row;
    }

    /**
     * 批量修改账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int updateAccountBatch(List<Account> accounts) {
        // 定义sql语句
        String sql = "update tb_account set account_name = ? , account_type = ? , money = ? , remark = ? , " +
                " update_time = now(), user_id = ? where account_id = ?";
        int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account account = accounts.get(i);
                // 设置参数
                ps.setString(1,account.getAccountName());
                ps.setString(2,account.getAccountType());
                ps.setDouble(3,account.getMoney());
                ps.setString(4,account.getRemark());
                ps.setInt(5,account.getUserId());
                ps.setInt(6,account.getAccountID());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;
        return rows;
    }

    /**
     * 删除账户记录,返回受影响的行数
     * @param accountId
     * @return
     */
    @Override
    public int deleteAccount(int accountId) {
        // 定义SQL语句
        String sql = "delete from tb_account where account_id = ?";
        int row = jdbcTemplate.update(sql,accountId);
        return row;
    }


    /**
     * 批量删除账户记录,返回受影响的行数
     * @param ids
     * @return
     */
    @Override
    public int deleteAccountBatch(Integer[] ids) {
        // 定义SQL语句
        String sql = "delete from tb_account where account_id = ?";
        int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setInt(1, ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        }).length;
        return rows;
    }


    /**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int outAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money - ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int inAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money + ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }
}

 

4.Spring事务控制

4.1.转账场景模拟实现

4.1.1.接口方法定义

   /**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    public int outAccount(Integer accountId, Double money);

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    public int inAccount(Integer accountId, Double money);

4.1.2.实现对应接口

对于转账涉及到双方账户以及对应转账金额,所以有入账和出账两
个方法。
/**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int outAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money - ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int inAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money + ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }

4.1.3.转账方法实现

@Service
public class UserService {

    @Resource
    private IAccountDao accountDao;

    @Transactional(propagation = Propagation.REQUIRED)
    public int transferAccounts(Integer outId , Integer inId , Double money){
        int code = 0;  // 1 成功  0 失败

        // 账户A 支出,修改账户金额,返回受影响的行数
        int outRow = accountDao.outAccount(outId, money);

        int i=1/0;

        // 账户B 收入,修改账户金额,返回受影响的行数
        int inRow = accountDao.inAccount(inId,money);

        if (outRow == 1 && inRow == 1){
            code = 1;
        }
        return code;

    }
}
在程序运行中无法保证 service 层业务代码
不发生异常,如果通过 jdbc 的方式处理事务,此时需要手动方式
控制事务,这样的话凡是涉及到事务控制的业务方法均需要开发人
员手动来进行事务处理,无法满足生产的需要。

4.2.Spring事务概念

4.2.1.事务的四大特性

  • 原子性(Atomicity)

共生死,要么全部成功,要么全部失败

  • 一致性(Consistency)

事务在执行前后,数据库中数据要保持一致性状态。(如转账的过程 账户操作后数据必须保持一致)

  • 隔离性(Isolation)

事务与事务之间的执行应当是相互隔离互不影响的。(多个角色对统一记录进行操作必须保证没有任何干扰),当然没有影响是不可能的,为了让影响级别降到最低,通过隔离级别加以限制:

1. READ_UNCOMMITTED (读未提交)
隔离级别最低的一种事务级别。在这种隔离级别下,会引发脏
读、不可重复读和幻读。
2. READ_COMMITTED (读已提交)
读到的都是别人提交后的值。这种隔离级别下,会引发不可重
复读和幻读,但避免了脏读。
3. REPEATABLE_READ (可重复读)
这种隔离级别下,会引发幻读,但避免了脏读、不可重复读。
4. SERIALIZABLE (串行化)
最严格的隔离级别。在Serializable隔离级别下,所有事务按照
次序依次执行。
脏读、不可重复读、幻读都不会出现。
  • 持久性(Durability

事务提交完毕后,数据库中的数据的改变是永久的

4.2.2.Spring事务核心接口

Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。

Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事
务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相
关平台框架的事务来实现。
Spring 事务管理器的接口是
org.springframework.transaction.PlatformTransactionManager
,通过这个接口, Spring 为各个平台如 JDBC Hibernate 等都提
供了对应的事务管理器,但是具体的实现就是各个平台自己的事情
了。此接口的内容如下:
public interface PlatformTransactionManager(){
  // 由 TransactionDefinition 得到TransactionStatus 对象
    TransactionStatus
getTransaction(TransactionDefinition definition)
throws TransactionException;
    // 提交
   void commit(TransactionStatus status) throws
TransactionException;
    // 回滚
    void rollback(TransactionStatus status)
throws TransactionException;
 }
从这里可知具体的具体的事务管理机制对 Spring 来说是透明的,
它并不关心那些,那些是对应各个平台需要关心的,所以 Spring
事务管理的一个优点就是为不同的事务 API 提供一致的编程模型,
JTA JDBC Hibernate JPA 。下面分别介绍各个平台框架实现
事务管理的机制。

4.2.2.1.JDBC事务

  如果应用程序中直接使用 JDBC 来进行持久化,此时使用
DataSourceTransactionManager 来处理事务边界。为了使用
DataSourceTransactionManager,需要使用如下的 XML 将其装配
到应用程序的上下文定义中:  
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSo
urceTransactionManager">
 <property name="dataSource" ref="dataSource"
/>
</bean
  实际上,DataSourceTransactionManager 是通过调用
java.sql.Connection 来管理事务,而后者是通过 DataSource 获取
到的。通过调用连接的 commit() 方法来提交事务,同样,事务失
败则通过调用 rollback() 方法进行回滚。  

4.2.2.2.Hibernate事务

如果应用程序的持久化是通过 Hibernate 实现的,那么你需要使用
HibernateTransactionManager 。对于 Hibernate3 ,需要在
Spring 上下文定义中添加如下的声明:
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.Hiberna
teTransactionManager">
 <property name="sessionFactory"
ref="sessionFactory" />
</bean>

sessionFactory 属性需要装配一个 Hibernate session 工厂,
HibernateTransactionManager 的实现细节是它将事务管理的职
责委托给 org.hibernate.Transaction 对象,而后者是从 Hibernate
Session 中获取到的。当事务成功完成时,
HibernateTransactionManager 将会调用 Transaction 对象的
commit() 方法,反之,将会调用 rollback() 方法。

4.2.2.3.JAVA持久化API事务

Hibernate 多年来一直是 Java 持久化标准,但是现在 Java 持久化
API 作为真正的 Java 持久化标准进入大家的视野。如果你计划使用
JPA 的话,那你需要使用 Spring JpaTransactionManager 来处
理事务。你需要在 Spring 中这样配置 JpaTransactionManager
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransaction
Manager">
 <property name="sessionFactory"
ref="sessionFactory" />
</bean>
JpaTransactionManager 只需要装配一个 JPA 实体管理工厂
javax.persistence.EntityManagerFactory 接口的任意实现)。
JpaTransactionManager 将与由工厂所产生的 JPA EntityManager
合作来构建事务。

4.2.2.4.Java原生API事务

如果应用程序没有使用以上所述的事务管理,或者是跨越了多个事
务管理源(比如两个或者是多个不同的数据源),此时需要使用
JtaTransactionManager
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTra
nsactionManager">
 <property name="transactionManagerName"
value="java:/TransactionManager" />
</bean>
JtaTransactionManager 将事务管理的责任委托给
javax.transaction.UserTransaction
javax.transaction.TransactionManager 对象,其中事务成功完成
通过 UserTransaction.commit() 方法提交,事务失败通过
UserTransaction.rollback() 方法回滚。

4.3.Spring事务控制配置

通过jdbc持久化事务,对于事务配置实现由两种方式:XML配置,注解配置

4.3.1.XML配置

4.3.1.1.添加命名空间

spring.xml 配置文件的添加事务和 aop 的命名空间
事务
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

4.3.1.2.设置aop代理

<!-- 开启AOP代理 -->
<aop:aspectj-autoproxy />

4.3.1.3.配置事务管理器

<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataS
ourceTransactionManager">
    <!--数据源 -->
    <property name="dataSource" ref="dataSource">
</property>
</bean>

4.3.1.4.配置事务相关通知

一般来说增删改方法 propagation=Required ,对于查询方法使用
read-only="true"
<!-- 配置事务通知   transaction-manager属性表示这个事
务通知是哪个事务管理器管理的-->
<!--
    tx:method的属性:
       name
 是必须的,表示与事务属性关联的方法名(业务方
法名),对切入点进行细化。
   通配符(*)可以用来指定一批关联到相同的事务
属性的方法。
       
如:'get*'、'handle*'、'on*Event'等等.
       propagation
 不是必须的,默认值是REQUIRED
       表示事务传播行为, 包括: 
 
REQUIRED,SUPPORTS,MANDATORY,NEVER
 
REQUIRES_NEW,NOT_SUPPORTED,NESTED
isolation  
 不是必须的,默认值DEFAULT
            表示事务隔离级别(数据库的隔离级别)
       timeout
 不是必须的,默认值-1(永不超时)
           表示事务超时的时间(以秒为单位)
       read-only
 不是必须的,默认值false不是只读的
           表示事务是否只读
       rollback-for
 不是必须的
           表示将被触发进行回滚的 Exception(s);以逗号分开。
          
如:'com.foo.MyBusinessException,ServletException
'
      no-rollback-for
 不是必须的
           表示不被触发进行回滚的 Exception(s);以逗号分开。
          
如:'com.foo.MyBusinessException,ServletException
'
          任何 RuntimeException 将触发事务回滚
    -->
 <tx:advice id="txAdvice" transactionmanager="txManager">
    <!--对以add update delete query开头的所有方法进
行事务处理-->
   <tx:attributes>
        <!--定义什么方法需要使用事务 name代表的是方法
名(或方法匹配)-->
        <!-- 匹配以 add 开头的所有方法均加入事务 -->
        <tx:method name="add*propagation="REQUIRED" />
        <!-- 匹配以 update 开头的所有方法均加入事务 -->
        <tx:method name="update*propagation="REQUIRED" />
        <!-- 匹配以 delete 开头的所有方法均加入事务 -->
       <tx:method name="delete*propagation="REQUIRED" />
      <!-- 匹配以 query 开头的所有方法均加入事务 -->
        <tx:method name="query*" readonly="true" />
 </tx:attributes>
 </tx:advice>

4.3.1.5.配置aop

<!--    配置AOP-->
    <aop:config>
        <aop:pointcut id="cut" expression="execution(* com.liuxidong.service..*.*(..))"/>
        <!--    设置通知-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/>
    </aop:config>

4.3.2.注解配置

4.3.2.1.配置事务管理器

<!-- spring 注解式事务声明 -->
<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSo
urceTransactionManager">
 <property name="dataSource" ref="dataSource">
</property>
</bean>

4.3.2.2.配置注解支持

<tx:annotation-driven transactionmanager="txManager"/>

4.3.2.3.方法上加入事务注解

Service 方法上在需要添加事务的方法上加入事务注解
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void saveUser(String userName,String
userPwd){
    User user1=new User();
    user1.setUserName(userName);
    user1.setUserPwd(userPwd);
    userDao.saveUser(user1);
    userDao.delUserById(2);
}
备注:默认 spring 事务只在发生未被捕获的 runtimeexcetpion
时才回滚。
spring aop 异常捕获原理:
被拦截的方法需显式抛出异常,并不能经任何处理,这样 aop 代理
才能捕获到方法的异常,才能进行回滚,默认情况下 aop 只捕获
runtimeexception 的异常,但可以通过配置来捕获特定的异常并
回滚换句话说在 service 的方法中不使用 try catch 或者在 catch
中最后加上 throw new RunTimeexcetpion() ,这样程序异常时
才能被 aop 捕获进而回滚 .
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值