首页 > 其他分享 >Spring JDBC和 事务控制

Spring JDBC和 事务控制

时间:2023-03-11 18:33:07浏览次数:35  
标签:事务 JDBC Spring sql account springframework import org public

Spring JDBC和 事务控制

1.主要内容

2.Spring整合 JDBC环境

     Spring 框架除了提供 IOC 与 AOP 核⼼功能外,同样提供了基于JDBC 的数据访问功能,使得访问持

久层数据更加⽅便。使⽤ Spring JDBC 环境,⾸先需要⼀套 Spring 整合 JDBC 的环境。

2.1.添加依赖坐标

 <!-- 添加相关的依赖坐标 -->
     <!-- spring 框架坐标依赖添加 -->
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
       <version>5.2.4.RELEASE</version>
     </dependency>
     <!-- spring 测试环境 -->
     <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 -->
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</artifactId>
       <version>5.2.4.RELEASE</version>
     </dependency>
     <!-- spring事物 -->
     <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 连接池 -->
     <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&serverTimezone=GMT%2B8&useSSL=false
 # 数据库⽤户名称
 jdbc.user=(数据库账号)
 # 数据库⽤户密码
 jdbc.password=(数据库密码)

以下为可选配置

 # 指定连接池的初始化连接数。取值应在minPoolSize 与 maxPoolSize 之间.Default:3
 2.3. 修改 spring 配置⽂件
 spring.xml
 2.4. 配置数据源
 由于建⽴数据库连接是⼀个⾮常耗时耗资源的⾏为,所以通过连接池预先同数据库建⽴⼀些连接,放
 在内存中,应⽤程序需要建⽴数据库连接时直接到连接池中申请⼀个就⾏,⽤完后再放回去。
 C3P0 与 DBCP ⼆选⼀即可
 DBCP(DataBase connection pool),数据库连接池。是 apache 上的⼀个 java 连接池项⽬,也是
 tomcat 使⽤的连接池组件。单独使⽤dbcp需要2个包:commons-dbcp.jar,commons-pool.jar
 dbcp,没有⾃动回收空闲连接的功能。
 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"
        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">
 ​
     <!-- Spring扫描注解的配置 -->
     <context:component-scan base-package="com.xxx" />
 ​
     <!-- 加载properties 配置⽂件,可以读取jdbc.properties配置文件中的数据 -->
     <context:property-placeholder location="jdbc.properties" />
 ​
 </beans>

 

2.4.配置数据源

由于建⽴数据库连接是⼀个⾮常耗时耗资源的⾏为,所以通过连接池预先同数据库建⽴⼀些连接,放

在内存中,应⽤程序需要建⽴数据库连接时直接到连接池中申请⼀个就⾏,⽤完后再放回去。

C3P0 与 DBCP ⼆选⼀即可

DBCP(DataBase connection pool),数据库连接池。是 apache 上的⼀个 java 连接池项⽬,也是

tomcat 使⽤的连接池组件。单独使⽤dbcp需要2个包:commons-dbcp.jar,commons-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的属性值对应的是properties配置的值-->
         <property name="driverClass" value="${jdbc.driver}"></property>
         <property name="jdbcUrl" value="${jdbc.url}"></property>
         <property name="user" value="${jdbc.user}"></property>
         <property name="password" value="${jdbc.password}"></property>
     </bean>

C3P0 其他额外配置(对应的值在jdbc.properties⽂件中指定)

 <!-- 指定连接池中保留的最⼤连接数。 Default:15-->
 <property name="maxPoolSize" value="${maxPoolSize}"/>
 <!-- 指定连接池中保留的最⼩连接数。-->
 <property name="minPoolSize" value="${minPoolSize}"/>
 <!-- 指定连接池的初始化连接数。取值应在minPoolSize 与 maxPoolSize 之间.Default:3-->
 <property name="initialPoolSize" value="${initialPoolSize}"/>
 <!-- 最⼤空闲时间,60秒内未使⽤则连接被丢弃。若为0则永不丢弃。 Default:0-->
 <property name="maxIdleTime" value="${maxIdleTime}"/>
 <!-- 当连接池中的连接耗尽的时候c3p0⼀次同时获取的连接数。 Default:3-->
 <property name="acquireIncrement" value="${acquireIncrement}"/>
 <!-- JDBC的标准,⽤以控制数据源内加载的PreparedStatements数ᰁ。 
 但由于预缓存的statements属于单个connection,⽽不是整个连接池所以设置这个参数需要考虑到多⽅
 ⾯的因数。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。
 Default:0-->
 <property name="maxStatements" value="${maxStatements}"/>
 <!-- 每60秒检查所有连接池中的空闲连接。Default:0 -->
 <property name="idleConnectionTestPeriod"
 value="${idleConnectionTestPeriod}"/>

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.

  <!--配置jdbc  Template模板对象,并注入一个数据源-->
     <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测试

package com.xxx.test;

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

public class SpringJdbcTest01 {
    @Test
    public void testJdbc(){
        //获取Spring的上下文环境
        ApplicationContext ac =new ClassPathXmlApplicationContext("spring.xml");
        //得到模板类JdbcTemPlate
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
        //crud操作
        //定义sql语句
        String sql ="select count(1) from tb_account";
        //执行查询操作  (无参数)
        Integer total =jdbcTemplate.queryForObject(sql,Integer.class);
        System.out.println("总记录数:"+total);
    }

    @Test
    public void testJdbc02(){
        //获取Spring的上下文环境
        ApplicationContext ac =new ClassPathXmlApplicationContext("spring.xml");
        //得到模板类JdbcTemPlate
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
        //crud操作
        //定义sql语句
        String sql ="select count(1) from tb_account where user_id=?";
        //执行查询操作  (无参数)
        Integer total =jdbcTemplate.queryForObject(sql,Integer.class,2);
        System.out.println("总记录数:"+total);
    }
}

2.6.3.2.简单封装

package com.xxx.test;

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

public class SpringJdbcTest02 {
    private JdbcTemplate jdbcTemplate;
    @Before
    public void init(){
        //获取Spring的上下文环境
        ApplicationContext ac =new ClassPathXmlApplicationContext("spring.xml");
        //得到模板类JdbcTemPlate
        jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
    }
    @Test
    public void testJdbc(){

        //crud操作
        //定义sql语句
        String sql ="select count(1) from tb_account";
        //执行查询操作  (无参数)
        Integer total =jdbcTemplate.queryForObject(sql,Integer.class);
        System.out.println("总记录数:"+total);
    }

    @Test
    public void testJdbc02(){

        //crud操作
        //定义sql语句
        String sql ="select count(1) from tb_account where user_id=?";
        //执行查询操作  (无参数)
        Integer total =jdbcTemplate.queryForObject(sql,Integer.class,2);
        System.out.println("总记录数:"+total);
    }
}

2.6.3.3.注解封装

@RunWith
 就是⼀个运⾏器
 @RunWith(JUnit4.class) 就是指⽤JUnit4来运⾏
 @RunWith(SpringJUnit4ClassRunner.class) 让测试运⾏于Spring测试环境
@ContextConfiguration
 Spring整合JUnit4测试时,使⽤注解引⼊多个配置⽂件
 @ContextConfiguration(Locations="classpath:applicationContext.xml") 
 @ContextConfiguration(locations = {"classpath:spring.xml",
"classpath:bean.xml"})

 

package com.xxx.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

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

    @Test
    public void testJdbc(){

        //crud操作
        //定义sql语句
        String sql ="select count(1) from tb_account";
        //执行查询操作  (无参数)
        Integer total =jdbcTemplate.queryForObject(sql,Integer.class);
        System.out.println("总记录数:"+total);
    }

    @Test
    public void testJdbc02(){

        //crud操作
        //定义sql语句
        String sql ="select count(1) from tb_account where user_id=?";
        //执行查询操作  (无参数)
        Integer total =jdbcTemplate.queryForObject(sql,Integer.class,2);
        System.out.println("总记录数:"+total);
    }
}

2.6.3.4.通⽤封装

1.定义⼀个⽗类,设置通⽤的配置信息

 package com.xxx.test;
 ​
 import org.junit.runner.RunWith;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 ​
 @RunWith(SpringJUnit4ClassRunner.class)   //将测试运行在spring测试环境中
 @ContextConfiguration(locations = {"classpath:spring.xml"})  //设置要加载的配置文件
 public class BaseTest {
 ​
 }
 ​

2.继承通⽤的测试类

 package com.xxx.test;
 ​
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 ​
 import javax.annotation.Resource;
 ​
 public class SpringJdbcTest04 extends BaseTest{
     @Resource
     private JdbcTemplate jdbcTemplate;
 ​
     @Test
     public void testJdbc(){
 ​
         //crud操作
         //定义sql语句
         String sql ="select count(1) from tb_account";
         //执行查询操作  (无参数)
         Integer total =jdbcTemplate.queryForObject(sql,Integer.class);
         System.out.println("总记录数:"+total);
     }
 ​
     @Test
     public void testJdbc02(){
 ​
         //crud操作
         //定义sql语句
         String sql ="select count(1) from tb_account where user_id=?";
         //执行查询操作  (无参数)
         Integer total =jdbcTemplate.queryForObject(sql,Integer.class,2);
         System.out.println("总记录数:"+total);
     }
 }

 

 

3.持久层账户模块操作

当完成 Spring Jdbc 环境集成后,这⾥使⽤spring jdbc 完成账户单表crud 操作.

3.1.账户接口方法定义

3.1.1. 定义实体类

Account.java

 package com.xxx.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;
     }
 ​
     @Override
     public String toString() {
         return "Account{" +
                 "accountId=" + accountId +
                 ", accountName='" + accountName + '\'' +
                 ", accountType='" + accountType + '\'' +
                 ", money=" + money +
                 ", remark='" + remark + '\'' +
                 ", createTime=" + createTime +
                 ", updateTime=" + updateTime +
                 ", 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;
     }
 }
 ​

 

3.1.2.定义接⼝类

IAccountDao.java

package com.xxx.dao;

import com.xxx.po.Account;

import java.util.List;

/**
 * 账户模块的接口定义
 *      1.添加账户
 *          添加账户记录,返回受影响的行数
 *          添加账户记录,返回主键
 *          批量添加账户记录,返回受影响的行数
 *      2.修改账户
 *          修改账户记录,返回受影响的行数
 *          批量修改账户记录,返回受影响的行数
 *      3.删除账户
 *          删除账户记录,返回受影响的行数
 *  *       批量删除账户记录,返回受影响的行数
 *      4.查询账户
 *          查询指定用户的账户的总记录数,返回总记录数
 *          查询指定账户的账户详情,返回账户对象
 *          多条件查询指定用户的账户列表,返回账户集合
 */
public interface IAccountDao {

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

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

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

    /**
     * 查询账户
     *      查询指定用户的账户的总记录数,返回总记录数
     * @param userId
     * @return
     */
    public int queryAccountCount(int userId);

    /**
     * 查询账户
     *      查询指定账户的账户详情,返回账户对象
     * @param accountId
     * @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);
}

 

3.1.3.定义接⼝实现类

package com.xxx.dao.impl;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {
    
    @Override
    public int addAccount(Account account) {
        return 0;
    }

    @Override
    public int addAccountHasKey(Account account) {
        return 0;
    }

    @Override
    public int addAccountBatch(List<Account> accounts) {
        return 0;
    }

    @Override
    public int queryAccountCount(int userId) {
        return 0;
    }

    @Override
    public Account queryAccountById(int accountId) {
        return null;
    }

    @Override
    public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {
        return null;
    }

    @Override
    public int updateAccount(Account account) {
        return 0;
    }

    @Override
    public int updateAccountBatch(List<Account> accounts) {
        return 0;
    }

    @Override
    public int deleteAccount(int accountId) {
        return 0;
    }

    @Override
    public int deleteAccountBatch(Integer[] ids) {
        return 0;
    }
}

 

3.2.账户记录添加实现

在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记

录等情况。这里对于账户记录添加方式分为三种⽅式:添加单条记录返回受影响行数、添加单条记录返

回主键、批量添加多条记录。

3.2.1.添加账户记录

package com.xxx.dao.impl;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {

    // 注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 添加账户记录,返回受影响的行数
     * @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;
    }

}

测试方法

package com.xxx.test;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import com.xxx.test.BaseTest;
import org.junit.Test;

import javax.annotation.Resource;
import java.sql.SQLOutput;


/**
 * 账户模块添加操作测试类
 */
public class SpringJdbcAddTest extends BaseTest {

    // 注入
    @Resource
    private IAccountDao accountDao;

    /**
     * 添加账户记录,返回受影响的行数
     */
    @Test
    public void testAddAccount(){
        // 准备要添加的数据
        Account account = new Account("账户3","工商银行",200.0,"奖金",1);
        // 调用对象中的添加方法,返回受影响的行数
        int row = accountDao.addAccount(account);
        System.out.println("添加账户,受影响的行数:" + row);
    }

}

3.2.2.添加记录返回主键

package com.xxx.dao.impl;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {

    // 注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 添加账户记录,返回主键
     * @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;
    }

}

测试方法

package com.xxx.test;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import com.xxx.test.BaseTest;
import org.junit.Test;

import javax.annotation.Resource;
import java.sql.SQLOutput;


/**
 * 账户模块添加操作测试类
 */
public class SpringJdbcAddTest extends BaseTest {

    // 注入
    @Resource
    private IAccountDao accountDao;

    /**
     * 添加账户记录,返回主键
     */
    @Test
    public void testAddAccountHasKey(){
        // 准备要添加的数据
        Account account = new Account("账户4","中国银行",600.0,"绩效",2);
        // 调用对象中的添加方法,返回受影响的行数主键
        int key = accountDao.addAccountHasKey(account);

        System.out.println("添加账户,返回主键:"+key);
    }

}

3.2.3.批量添加账户记录

 package com.xxx.dao.impl;
 ​
 import com.xxx.dao.IAccountDao;
 import com.xxx.po.Account;
 import org.springframework.jdbc.core.BatchPreparedStatementSetter;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementCreator;
 import org.springframework.jdbc.support.GeneratedKeyHolder;
 import org.springframework.jdbc.support.KeyHolder;
 import org.springframework.stereotype.Repository;
 import javax.annotation.Resource;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.List;
 ​
 /**
  * 账户模块接口的实现类
  */
 @Repository
 public class AccountDaoImpl implements IAccountDao {
 ​
     // 注入JdbcTemplate模板类
     @Resource
     private JdbcTemplate jdbcTemplate;
 ​
     /**
      * 批量添加账户记录,返回受影响的行数
      * @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;
     }
 }
 ​

测试方法

package com.xxx.test;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import com.xxx.test.BaseTest;
import org.junit.Test;

import javax.annotation.Resource;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.List;


/**
 * 账户模块添加操作测试类
 */
public class SpringJdbcAddTest extends BaseTest {

    // 注入
    @Resource
    private IAccountDao accountDao;

    /**
     * 批量添加账户记录,返回受影响的行数
     */
    @Test
    public void testBatchAddAccount(){
        // 准备要添加的数据
        Account account = new Account("账户4","农业银行",700.0,"奖金",3);
        Account account2 = new Account("账户5","工商银行",400.0,"早餐",3);
        Account account3 = new Account("账户6","中国银行",2000.0,"绩效",3);
        List<Account> accounts =new ArrayList<>();
        accounts.add(account);
        accounts.add(account2);
        accounts.add(account3);

        int rows =accountDao.addAccountBatch(accounts);

        System.out.println("批量添加账户记录,返回受影响的行数:" +rows);
    }

}

 

3.3.账户记录查询实现

账户记录查询这⾥提供了三种查询⽅式,查询指定⽤户所有账户记录数,查询单条账户记录详情,多

条件查询指定⽤户账户记录。

3.3.1.查询⽤户的账户总记录数

package com.xxx.dao.impl;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {

    // 注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 查询指定用户的账户总记录数,返回总数量
     * @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;
    }
}

测试方法

package com.xxx.test;

import com.xxx.dao.IAccountDao;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.annotation.Resource;

public class SpringJdbcQueryTest extends BaseTest {

    @Resource
    private IAccountDao accountDao;

    /**
     * 查询指定用户的账户总记录数,返回总数量
     */
    @Test
    public void testQueryAccount(){
        int total = accountDao.queryAccountCount(1);
        System.out.println("查询指定用户的账户总记录数,返回总数量:"+total);
    }
}

3.3.2.查询指定账户记录详情

 package com.xxx.dao.impl;
 ​
 import com.xxx.dao.IAccountDao;
 import com.xxx.po.Account;
 import org.springframework.jdbc.core.BatchPreparedStatementSetter;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementCreator;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.jdbc.support.GeneratedKeyHolder;
 import org.springframework.jdbc.support.KeyHolder;
 import org.springframework.stereotype.Repository;
 import javax.annotation.Resource;
 import java.sql.*;
 import java.util.List;
 ​
 /**
  * 账户模块接口的实现类
  */
 @Repository
 public class AccountDaoImpl implements IAccountDao {
 ​
     // 注入JdbcTemplate模板类
     @Resource
     private JdbcTemplate jdbcTemplate;
 ​
     /**
      * 查询指定账户记录详情,返回对象
      * @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;
     }
 ​
 }
 ​

测试方法

 package com.xxx.test;
 ​
 import com.xxx.dao.IAccountDao;
 import com.xxx.po.Account;
 import org.junit.Test;
 import org.springframework.jdbc.core.JdbcTemplate;
 ​
 import javax.annotation.Resource;
 ​
 public class SpringJdbcQueryTest extends BaseTest {
 ​
     @Resource
     private IAccountDao accountDao;
 ​
     /**
      * 查询指定账户记录详情,返回账户对象
      */
     @Test
     public void testQueryAccountById(){
         Account account = accountDao.queryAccountById(1);
         System.out.println("账户详情:"+account.toString());
     }
 }
 ​

 

3.3.3.多条件查询用户账户记录

package com.xxx.dao.impl;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {

    // 注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 多条件查询指定用户的账户记录列表,返回账户集合
     * @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;
    }
}

测试方法

package com.xxx.test;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.annotation.Resource;
import java.util.List;

public class SpringJdbcQueryTest extends BaseTest {

    @Resource
    private IAccountDao accountDao;
    /**
     * 多条件查询,返回账号列表
     */
    @Test
    public void testQueryAccountList(){
        //List<Account> accountList =accountDao.queryAccountByParams(3,null,null,null);
        //System.out.println(accountList.toString());

        //List<Account> accountList2 =accountDao.queryAccountByParams(3,"5",null,null);
        //System.out.println(accountList2.toString());

        List<Account> accountList3 =accountDao.queryAccountByParams(3,"5","中国银行",null);
        System.out.println(accountList3.toString());

    }
}

 

3.4.账户记录更新实现

3.4.1.更新账户记录

package com.xxx.dao.impl;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {

    // 注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 修改账户,返回受影响的行数
     * @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;
    }

    
}

测试⽅法

package com.xxx.test;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.junit.Test;

import javax.annotation.Resource;

public class SpringJdbcUpdateTest extends BaseTest {
    @Resource
    private IAccountDao accountDao;
    /**
     * 修改账户记录,返回受影响的行数
     */
    @Test
    public void testUpdateAccount(){
        Account account = new Account("账户1","农业银行",700.0,"奖金",1);
        account.setAccountId(1);

        int row = accountDao.updateAccount(account);
        System.out.println("修改账户记录,返回受影响的行数:"+row);
    }
}

3.4.2.批量更新账户记录

 package com.xxx.dao.impl;
 ​
 import com.xxx.dao.IAccountDao;
 import com.xxx.po.Account;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.jdbc.core.BatchPreparedStatementSetter;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementCreator;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.jdbc.support.GeneratedKeyHolder;
 import org.springframework.jdbc.support.KeyHolder;
 import org.springframework.stereotype.Repository;
 import javax.annotation.Resource;
 import java.sql.*;
 import java.util.ArrayList;
 import java.util.List;
 ​
 /**
  * 账户模块接口的实现类
  */
 @Repository
 public class AccountDaoImpl implements IAccountDao {
 ​
     // 注入JdbcTemplate模板类
     @Resource
     private JdbcTemplate jdbcTemplate;
 ​
     /**
      * 批量修改账户记录,返回受影响的行数
      * @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;
     }
 }
 ​

测试方法

package com.xxx.test;

import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.junit.Test;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

public class SpringJdbcUpdateTest extends BaseTest {
    @Resource
    private IAccountDao accountDao;
    /**
     * 批量修改账户记录,返回受影响的行数
     */
    @Test
    public void testBatchUpdateAccount(){
        // 准备要添加的数据
        Account account = new Account("账户55","农业银行",555.0,"奖金",3);
        account.setAccountId(5);
        Account account2 = new Account("账户66","工商银行",666.0,"早餐",3);
        account2.setAccountId(6);
        Account account3 = new Account("账户77","中国银行",777.0,"绩效",3);
        account3.setAccountId(7);

        List<Account> accounts =new ArrayList<>();
        accounts.add(account);
        accounts.add(account2);
        accounts.add(account3);

        int rows = accountDao.updateAccountBatch(accounts);
        System.out.println("批量更新账户记录,返回受影响的行数:"+rows);

    }
}

3.5.账户记录删除实现

3.5.1.删除账户记录

 package com.xxx.dao.impl;
 ​
 import com.xxx.dao.IAccountDao;
 import com.xxx.po.Account;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.jdbc.core.BatchPreparedStatementSetter;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementCreator;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.jdbc.support.GeneratedKeyHolder;
 import org.springframework.jdbc.support.KeyHolder;
 import org.springframework.stereotype.Repository;
 import javax.annotation.Resource;
 import java.sql.*;
 import java.util.ArrayList;
 import java.util.List;
 ​
 /**
  * 账户模块接口的实现类
  */
 @Repository
 public class AccountDaoImpl implements IAccountDao {
 ​
     // 注入JdbcTemplate模板类
     @Resource
     private JdbcTemplate jdbcTemplate;
 ​
     /**
      * 删除账户记录,返回受影响的行数
      */
     @Override
     public int deleteAccount(int accountId) {
         //定义sql语句
         String sql = "delete from tb_account where account_id=?";
         //设置参数
         int row = jdbcTemplate.update(sql,accountId);
         return row;
     }
 }
 ​

测试方法

 package com.xxx.test;
 ​
 import com.xxx.dao.IAccountDao;
 import org.junit.Test;
 ​
 import javax.annotation.Resource;
 ​
 public class SpringJdbcDeleteTest extends BaseTest {
     @Resource
     private IAccountDao accountDao;
 ​
     /**
      * 删除账户记录,返回受影响的行数
      */
     @Test
     public void testDeleteAccount(){
         int row = accountDao.deleteAccount(1);
         System.out.println("删除账户记录:"+row);
     }
 }
 ​

3.5.2.批量删除账户记录

 package com.xxx.dao.impl;
 ​
 import com.xxx.dao.IAccountDao;
 import com.xxx.po.Account;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.jdbc.core.BatchPreparedStatementSetter;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementCreator;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.jdbc.support.GeneratedKeyHolder;
 import org.springframework.jdbc.support.KeyHolder;
 import org.springframework.stereotype.Repository;
 import javax.annotation.Resource;
 import java.sql.*;
 import java.util.ArrayList;
 import java.util.List;
 ​
 /**
  * 账户模块接口的实现类
  */
 @Repository
 public class AccountDaoImpl implements IAccountDao {
 ​
     // 注入JdbcTemplate模板类
     @Resource
     private JdbcTemplate jdbcTemplate;
 ​
     /**
      * 批量删除账户记录,返回受影响的行数
      * @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;
     }
 }
 ​

测试方法

 package com.xxx.test;
 ​
 import com.xxx.dao.IAccountDao;
 import org.junit.Test;
 ​
 import javax.annotation.Resource;
 ​
 public class SpringJdbcDeleteTest extends BaseTest {
     @Resource
     private IAccountDao accountDao;
 ​
     /**
      * 批量删除账户记录,返回受影响的行数
      */
     @Test
     public void testDeleteBatch(){
         Integer[] ids ={2,3,4};
         int rows =accountDao.deleteAccountBatch(ids);
         System.out.println("批量删除:"+rows);
     }
 }
 ​

 

4.Spring事务控制

4.1.转账场景模拟实现

4.1.1.接口方法定义

 package com.xxx.dao;
 ​
 import com.xxx.po.Account;
 ​
 import java.util.List;
 ​
 public interface IAccountDao {
 ​
     /**
      * 支出
      * @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.实现对应接口

对于转账涉及到双方账户以及对应转账⾦额,所以有⼊账和出账两个方法。

 package com.xxx.dao.impl;
 ​
 import com.xxx.dao.IAccountDao;
 import com.xxx.po.Account;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.jdbc.core.BatchPreparedStatementSetter;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.PreparedStatementCreator;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.jdbc.support.GeneratedKeyHolder;
 import org.springframework.jdbc.support.KeyHolder;
 import org.springframework.stereotype.Repository;
 import javax.annotation.Resource;
 import java.sql.*;
 import java.util.ArrayList;
 import java.util.List;
 ​
 /**
  * 账户模块接口的实现类
  */
 @Repository
 public class AccountDaoImpl implements IAccountDao {
 ​
     // 注入JdbcTemplate模板类
     @Resource
     private JdbcTemplate jdbcTemplate;
 ​
     /**
      * 支出
      * @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.转账方法实现

 package com.xxx.service;
 ​
 import com.xxx.dao.IAccountDao;
 import org.springframework.stereotype.Service;
 ​
 import javax.annotation.Resource;
 ​
 @Service
 public class AccountService {
     @Resource
     private IAccountDao accountDao;
 ​
     /**
      * 转账的业务实现
      * @param outId  支出账户
      * @param inId   收入账户
      * @param money  金额
      * @return      成功或失败  1=成功,0=失败
      */
     public int updateAccountByTranfer(Integer outId,Integer inId,Double money){
         int code =0;  //成功或失败  1=成功,0=失败
         /**
          * 账户A向账户B转账100元
          *      账户A:金额-100
          *      账户B:金额+100
          */
 ​
         //账户A  支出,修改账户金额,返回受影响的行数
         int outRow = accountDao.outAccount(outId,money);
         //账户B  收入,修改账户金额,返回受影响的行数
         int inRow =accountDao.inAccount(inId,money);
         //如果支出和收入两个操作都执行成功,表示转账成功
         if(outRow==1&&inRow==1){
             code=1;     //成功
         }
         return code;
     }
 }
 ​

仔细思考代码会发现,在程序运行中无法保证 service 层业务代码不发生异常,如果通过 jdbc 的方式

处理事务,此时需要手动方式控制事务,这样的话凡是涉及到事务控制的业务方法均需要开发⼈员手动

来进行事务处理,无法满足生产的需要。

 

4.2. Spring事务概念

4.2.1. 事务的四大特性(ACID)

原子性(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.DataSourceTransactionManager">
  <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.HibernateTransactionManager">
  <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事务(JPA)

Hibernate 多年来⼀直是 Java 持久化标准,但是现在 Java 持久化 API 作为真正的 Java 持久化标准进

⼊⼤家的视野。如果你计划使用 JPA 的话,那你需要使用Spring 的 JpaTransactionManager 来处理事

务。你需要在 Spring 中这样配置 JpaTransactionManager:

 <bean id="transactionManager"
 class="org.springframework.orm.jpa.JpaTransactionManager">
  <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.JtaTransactionManager">
  <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的命名空间

事务

 xmlns:tx="http://www.springframework.org/schema/tx"
     
 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx.xsd

AOP

 xmlns:aop="http://www.springframework.org/schema/aop"
     
 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd

配置如下

 <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">
 </beans>

4.3.1.2.设置aop代理

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

4.3.1.3.配置事务管理器

 <!--配置事务管理器-->
     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <!--数据源  ref代表的是C3P0数据源-->
         <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" transaction-manager="txManager">
         <!--定义什么方法需要使用事务处理  以add update delete query开头的方法都使用事务-->
         <tx:attributes>
             <!--name属性代表的方法名(或者方法匹配)-->
             <tx:method name="add*" propagation="REQUIRED"/>
             <tx:method name="update*" propagation="REQUIRED"/>
             <tx:method name="delete*" propagation="REQUIRED"/>
             <tx:method name="query*" read-only="true"/>
         </tx:attributes>
     </tx:advice>
     

 

 事务传播⾏为介绍:
  @Transactional(propagation=Propagation.REQUIRED)
  如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)
  @Transactional(propagation=Propagation.NOT_SUPPORTED)
  容器不为这个⽅法开启事务
  @Transactional(propagation=Propagation.REQUIRES_NEW)
  不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务
  @Transactional(propagation=Propagation.MANDATORY)
  必须在⼀个已有的事务中执⾏,否则抛出异常
  @Transactional(propagation=Propagation.NEVER)
  必须在⼀个没有的事务中执⾏,否则抛出异常(与 Propagation.MANDATORY 相反)
  @Transactional(propagation=Propagation.SUPPORTS)
  如果其他 bean 调⽤这个⽅法,在其他 bean 中声明事务,那就⽤事务.
  如果其他 bean 没有声明事务,那就不⽤事务.
  @Transactional(propagation=Propagation.NESTED) 
  ⽀持当前事务,如果当前事务存在,则执⾏⼀个嵌套事务,如果当前没有事务,就新建⼀个事务

4.3.1.5.配置aop

 <!--配置Aop:定义Aop切面(切入点和通知)-->
     <aop:config>
         <!--设置切入点,设置需要被拦截的方法-->
         <aop:pointcut id="cut" expression="execution(* com.xxx.service..*.*(..))"/>
         <!--设置通知  事务通知-->
         <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"></aop:advisor>
     </aop:config>

4.3.2.注解配置

4.3.2.1.配置事务管理器

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

 

4.3.2.2.配置注解⽀持

 <tx:annotation-driven transaction-manager="txManager"/>

4.3.2.3.方法上加⼊事务注解

Service 方法上在需要添加事务的方法上加入事务注解

 package com.xxx.service;
 ​
 import com.xxx.dao.IAccountDao;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 ​
 import javax.annotation.Resource;
 ​
 @Service
 public class AccountService {
     @Resource
     private IAccountDao accountDao;
 ​
     /**
      * 转账的业务实现
      * @param outId  支出账户
      * @param inId   收入账户
      * @param money  金额
      * @return      成功或失败  1=成功,0=失败
      */
     @Transactional(propagation = Propagation.REQUIRED)
     public int toupdateAccountByTranfer(Integer outId,Integer inId,Double money){
         int code =0;  //成功或失败  1=成功,0=失败
         /**
          * 账户A向账户B转账100元
          *      账户A:金额-100
          *      账户B:金额+100
          */
 ​
         //账户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;
     }
 }
 ​

 

备注:默认 spring 事务只在发生未被捕获的 runtimeexcetpion 时才回滚。

spring aop 异常捕获原理:

被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到⽅法的异常,才能进行回

滚,默认情况下 aop 只捕获 runtimeexception 的异常,但可以通过配置来捕获特定的异常并回滚换

句话说在 service 的方法中不使用 try catch 或者在 catch 中最后加上 throw new

RunTimeexcetpion(),这样程序异常时才能被 aop 捕获进而回滚.

 

 

 

 

 

 

 

标签:事务,JDBC,Spring,sql,account,springframework,import,org,public
From: https://www.cnblogs.com/MeltSky/p/17206677.html

相关文章

  • spring事务失效的场景
    spring事务的七种传播机制事务方法发生了相互调用,事务如何传播:a调用bREQUIRED(spring默认的事务传播级别):如果当前(a)没有事务,则(b)新建一个事务,如果当前存在事务,则加入这......
  • SpringMVC-day01
    SpringMVC-day01SpringMVC,底层是把Servlet、Filter、Listener再次封装课程内容SpringMVC介绍请求与响应RESTful风格请求交互SSM整合(注解版)拦截器学习目标掌握......
  • SpringMVC-day02
    SpringMVC-day0201_基于SSM的三层架构目标了解基于SSM框架的三层架构技术体系路径SSM框架的三层架构技术体系SSM框架的三层架构技术体系在三层架构开发时会......
  • Spring-day01
    Spring-day01#前置课程1.javase 1).面向对象:封装,继承,多态(面向接口) 2).反射 3).动态代理2.javaweb 1).servlet编程规范 2).网络编程3.数据库 1).......
  • Spring-day02
    Spring-day0201_开启Spring注解目标能够设置Spring的注解扫描路径注解开发的意义在配置文件中开启Spring注解扫描注解开发的意义在Spring中除了可以使用xml......
  • Spring-day03
    Spring-day0301_Spring的AOP概述目标了解AOP存在的意义及方式路径AOP介绍AOP作用AOP优势Spring中AOP的实现方式在前面我们提到:Spring技术的两大核心就是Ioc(......
  • filter过滤器、事务管理、listener监听器相关知识
    资料来源于:B站尚硅谷JavaWeb教程(全新技术栈,全程实战),本人才疏学浅,记录笔记以供日后回顾由于是多个视频内容混合在一起,因此只放了第一个链接视频链接知识点1.......
  • 事务原理
    事务基础1).事务事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。......
  • Kuboard安装并部署springcloud微服务项目
    Kuboard安装并部署springcloud微服务项目Kuboard是一款k8s的管理界面,我认为它有页面美观(颜值即正义)、操作简洁、系统配置要求不高、对新手友好的特点。开发者认为他们......
  • Spring-初始Spring
    Spring1、简介Spring:春天:----给软件行业带来了春天2002年,首次推出了Spring框架的雏形:interface21框架2004年3月24号诞生RodJohnson,SpringFramework创始人,......