SpringJDBC与声明式事务操作

文章目录

Spring JDBC与事务操作

一、Spring整合JDBC的环境

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

(一)添加依赖坐标

 <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <!--配置相关的依赖坐标-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.14</version>
    </dependency>
    <!--spring的测试-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.3.14</version>
    </dependency>

    <!--aop-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>

    <!--spring-jdbc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.14</version>
    </dependency>

    <!--spring事务-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.14</version>
    </dependency>

    <!--mysql驱动包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.16</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>
 <!--使用lombok-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>RELEASE</version>
      <scope>compile</scope>
    </dependency>

(二)添加jdbc.properties的配置

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/spring_jdbc?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jdbc.username = root
jdbc.password = root
jdbc.maxActive= 50

initialPoolSize=20
maxPoolSize=100
minPoolSize=10
maxIdleTime=600
acquireIncrement=5
maxStatements=5
idleConnectionTestPeriod=60

(三)创建Spring的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns: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
        https://www.springframework.org/schema/context/spring-context.xsd

">
        <context:component-scan base-package="org.apache"/>
    
    <context:property-placeholder location="jdbc.properties"/>

    
</beans>

需要添加相应的命名空间,并且开启自动化扫描组件,使用context的property-placeholder属性,并指定location属性值为指定的配置文件。

(四)配置数据源

​ 由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时,直接到连接池中申请一个即可。用完再放回去。

​ C3P0是一个开源的JDBC连接池,它实现了数据源,支持JDBC3规范和JDBC2的标准扩展,目前使用它的开源项目有Hibernate,Spring等,C3P0具有自动回收空闲连接的功能。

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

    </bean>
  • 模板类配置

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

 <!--配置模板类jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--将数据源注入jdbcTemplate对象中-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

(五)JDBC测试

  • 创建数据库表

创建tb_account表,并插入相应的数据支持。

USE MyBatis;
CREATE TABLE tb_account(
    account_id int(11) NOT NULL  PRIMARY KEY AUTO_INCREMENT,
    account_name varchar(20) NOT NULL,
    account_type  varchar(20) NOT NULL,
    money  double NOT NULL ,
    remark varchar(50),
    create_time datetime NOT NULL,
    update_time  datetime NOT NULL ,
    user_id  int(11)  NOT NULL

);

INSERT INTO tb_account (account_id, account_name, account_type, money, remark, create_time, update_time, user_id)
VALUES (1,'账户1','建设银行',1000,'零花钱','2020-03-19 02:42:42','2020-03-19 02:42:56',1);

INSERT INTO tb_account(account_id, account_name, account_type, money, remark, create_time, update_time, user_id) VALUES
(2,'账户2','招商银行',500,'兼职费','2020-03-20 02:43:56','2020-03-20 02:46:57',1);
  • 使用Junit测试
    @Test
    public void testTemplate1(){
    
    
        //获取Spring的上下文环境
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
        //定义sql语句
        String totalUsers="SELECT count(1) FROM tb_account";

        Integer total = jdbcTemplate.queryForObject(totalUsers, Integer.class);
        System.out.println("共有"+total+"条数据");
    }

    @Test
    public void testJdbcTemplate2(){
    
    
        //获取Spring的上下文环境
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
        String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
        Integer integer = jdbcTemplate.queryForObject(sql, Integer.class,2);
        System.out.println("记录总数:"+integer);
    }
  • junit测试改进
  1. 首先创建基类测试类
@RunWith(SpringJUnit4ClassRunner.class)//将测试运行在Spring的测试环境中
@ContextConfiguration(locations = {
    
    "classpath:spring.xml"})//设置要加载的配置文件,使用注解进行配置。
public class BaseTest {
    
    

}

需要注意的是基类测试类不需要书写任何内容,只需要在类上添加两个注解

@RunWith(SpringJUnit4ClassRunner.class):此处表示将原先的junit测试添加到Spring的测试环境之中去。

扫描二维码关注公众号,回复: 15571735 查看本文章

@ContextConfiguration(locations={xxxx,xxxx}):此处表示设置要加载的配置文件,locations表示配置文件的数组名,之后添加一个数组,数组的内容是需要使用到的xml配置文件。

  1. 创建基本的测试类
public class SpringJDBCTest extends BaseTest{
    
    

   /**使用注解来注入*/
   @Resource
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testTemplate1(){
    
    

        //定义sql语句
        String totalUsers="SELECT count(1) FROM tb_account";

        Integer total = jdbcTemplate.queryForObject(totalUsers, Integer.class);
        System.out.println("共有"+total+"条数据");
    }

    @Test
    public void testJdbcTemplate2(){
    
    
        String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
        Integer integer = jdbcTemplate.queryForObject(sql, Integer.class,2);
        System.out.println("记录总数:"+integer);
    }

}

此处需要注意的是将jdbcTemplate类对象作为属性注入到测试类中,并通过@Resource注解完成属性的注入。并且具体的测试类只需要继承父类BaseTest即可完成对配置文件的加载及junit环境添加到Spring环境之中去的操作。

二、持久层账户模块操作(例)

此处使用Spring JDBC完成增删改查操作

(一)账户接口方法定义

1. 定义账户实体类

Account.java类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
    
    

    private Integer accountId;
    private String accountName;
    private String accountType;
    private Double money;
    private String remark;
    private Date createTime;
    private Date updateTime;
    private Integer userId;


    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;
    }
}

2.定义账户的接口类

IAccountDao.java

/** * @author lambda * 添加、修改、删除、查询账户 
* 1. 添加账户 
* 添加账户记录,返回受影响的行数 
* 添加账户记录,返回主键 *       
* 批量添加账户记录,返回受影响的行数 
* 2. 修改账户 
*  修改账户记录,返回受影响的行数
* 批量修改账户记录,返回受影响的行数 
*3.删除账户
* 删除账户记录,返回受影响的行数
*批量删除账户记录,返回受影响的行数 
 * 4. 查询账户 
 * 查询指定用户账户的总记录数 
* 查询指定账户的账户详情,返回账户对象 
* *多条件查询指定用户的账户列表,返回一个账户集合 *          
* */public interface IAccountDao {
    
     
*    /**     * 添加账户记录,返回受影响的行数  
*    * @param account 
*     * @return   
*   */      
*    int addAccount(Account account);  
*   /**    
*  *   添加账户记录,返回主键  
*    * @param account   
*   * @return   
*   */ 
*    int addAccountPrimaryKey(Account account);   
*  /**  
*    * 批量添加账户记录,返回受影响的行数   
*   * @param accounts  
*    * @return 
*     */   
*  int addAccountBatch(List<Account> accounts);   
*  /** 
*     * 查询指定用户账户的总记录数   
*   * @param userId  
*    * @return    
*  */   
*  int queryAccountCount(int userId);   
*  /**  
*    * 查询指定账户的账户详情,返回账户对象     
* * @param accountId    
*  * @return  
*    */   
*  Account queryAccountByAccountId(int accountId); 
*    /**  
*    *  多条件查询指定用户的账户列表,返回一个账户集合   
*   * @param userId   
*   * @param accountName  
*    * @param accountType   
*   * @param createTime    
*  * @return    
*  */    
* List<Account> queryAccountByParams(int userId,String accountName,String accountType,String createTime);   
*  /**    
*  * 修改账户记录,返回受影响的行数,传入整个账户对象 
*     * @param account  
*    * @return   
*   */ 
*    int updateAccount(Account account);  
*   /** 
*     * 批量修改账户记录,返回受影响的行数   
*   * @param accounts   
*   * @return    
*  */ 
*   int updateAccountBatch(List<Account> accounts);    
* /**  
*    * 删除账户记录,返回受影响的行数  
*    * @param accountId  
*    * @return  
*    */  
*   int deleteAccount(int accountId); 
*    /**   
*   *  批量删除账户记录,返回受影响的行数 
*     * @param ids    
*  * @return  
*    */
*     int deleteAccountBatch(int[] ids);}

3.定义接口的实现类

@Repository
public class AccountDaoImpl implements IAccountDao {
    
        
/**由于操作数据库需要使用Spring-JDBC,因此需要传入jdbcTemplate模板对象,     * 并完成注入到当前类中*/  
@Resource   
private JdbcTemplate jdbcTemplate;  
@Override  
public int addAccount(Account account) {
    
          
  return 0;
 }
 
@Override 
public int addAccountPrimaryKey(Account account) {
    
     
    return 0;
} 

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

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

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

@Override  
public List<Account> queryAccountByParams(int 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(int[] ids) {
    
          
    return 0;
  }
}

由于操作数据库需要使用Spring-JDBC,因此需要传入jdbcTemplate模板对象

(二)、账户记录添加实现

在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记录等情况,这里对于账户记录添加方式为三种,添加单条记录,返回受影响的行数,添加单条记录,返回主键、批量添加多条记录。

1. 添加账户记录

添加账户记录返回首影响的行数

 @Override 
 public int addAccount(Account account) {
    
        
   String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" + ",update_time,user_id) VALUES(?,?,?,?,now(),now(),?)";     
   int update = jdbcTemplate.update(sql, account.getAccountName(), account.getAccountType(), account.getMoney(), account.getRemark(), account.getUserId());    
   return update; 
  }
  
  @Test   
  public void addAccount(){
    
      
  int i = accountDao.addAccount(new Account("账户3", "工商银行", 200.0, "奖金", 1));    
  if (i>0){
    
         
    System.out.println("插入成功!");   
  }
}

jdbcTemplate.update方法需要传入对应的sql语句,并传递相应的参数。参数以可变参数的形式添加到对应的表中。

2.添加账户记录返回主键

 /**    
 * 添加记录返回主键   
 * @param account   
 * @return 
 */
 @Override  
 public int addAccountPrimaryKey(Account account) {
    
       
     String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" +",update_time,user_id)VALUES(?,?,?,?,now(),now(),?)";     
     //定义该对象,用于获取我们记录的主键  
     GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();  
     //需要传入两个参数,一个是PreparedStatementCreator,一个是keyHolder  
     jdbcTemplate.update(con -> {
    
          
     //此处需要利用lambda表达式去预编译sql语句,并设置返回主键(tatement.RETURN_GENERATED_KEYS)      
     PreparedStatement ps=con.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);
     //执行完之后可以通过keyHolder获取主键  
     return keyHolder.getKey().intValue();  
    } 
    
    @Test  
    public void addAccountPrimaryKey(){
    
         
    Account account = new Account("账户4", "中国银行", 300.0, "绩效奖", 4);        int key = accountDao.addAccountPrimaryKey(account);  
    System.out.println("主键为:"+key); 
  }
  • GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();此处表示获取键值对象,后续可以用来获取主键
  • jdbcTemplate.update(PreparedStatementCreator pcs,KeyHolder keyHolder)此处表示执行update方法,并且传入的参数有经过处理的预处理语句(使用lambda表达式来进行设置sql语句的相关参数并返回预处理语句对象ps)和键对象
  • 最后通过keyHolder.getKey().intValue();来获取新添加的对象的主键值。

3.批量添加账户,返回受影响的行数

/**
     * 批量添加账户,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int addAccountBatch(List<Account> accounts) {
    
    
        String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" +
                ",update_time,user_id)VALUES(?,?,?,?,now(),now(),?)";
        //调用batchUpdate批量添加,需要传入sql语句和BatchPreparedStatementSetter pss对象
      int rows=  jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
    
    
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
    
    
                //此处由于传入的是集合,所以需要通过i来获取指定的Account对象(会自动循环)
                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;
      //jdbcTemplate.batchUpdate返回的是数组,通过length属性可以获取数组的大小,即受影响的行数

        return rows;
    }

 @Test
    public void addAccountBatch(){
    
    
        List<Account> accounts=new ArrayList<>();
        accounts.add(new Account("账户5", "广东发展银行", 400.0, "出勤奖", 2));
        accounts.add(new Account("账户6", "浦发银行", 600.0, "年终奖", 2));
        int i = accountDao.addAccountBatch(accounts);
        System.out.println("添加了"+i+"条记录");
    }
  • jdbcTemplate.batchUpdate表示执行批量添加操作,主要参数有要执行的sql语句和BatchPreparedStatementSetter pss对象。
  • 由于需要传入BatchPreparedStatementSetter对象。所以new一个对象,并实现其中的两个方法setValuesgetBatchSize
  • setValues是使用预处理语句来对相应的sql语句进行预处理,并且通过循环取出集合中每一个值进行相应的赋值操作。getBatchSize最终返回的是经过预处理的集合的大小。最终返回数组的大小。

(三)账户记录查询的实现

账户记录查询提供了三种查询的方式,查询指定用户所有账户记录数,查询单条账户记录详情,多条件查询指定用户账户记录。

1. 查询用户的账户记录总数

  @Override   
  public int queryAccountCount(int userId) {
    
       
  //定义sql语句(其中的count中的括号可以填写*表示全部,可以添加1,表示第一个字段的总数,也就是总数)     
  String sql="SELECT COUNT(1) FROM tb_account WHERE user_id=?";  
  //调用查询方法(其中的参数依次表示为sql语句,返回的类型,以及参数值)  
  Integer integer = jdbcTemplate.queryForObject(sql, Integer.class, userId);   
  return integer;
  }
  
  @Test
  public void queryAccountCount(){
    
         
  int i = accountDao.queryAccountCount(1);  
  System.out.println("1号用户的账户一共有:"+i+"条");  
}
  • jdbcTemplate.queryForObject用来执行查询操作,参数为sql语句,返回值的类型,以及方法的参数值。
  • 最终将返回的结果返回即可获取指定用户的总账户数。

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

 /**   
 * 查询指定账户记录详情,返回账户对象 
 * @param accountId
 * @return  
 */
 @Override  
 public Account queryAccountByAccountId(int accountId) {
    
         
 String sql="SELECT * FROM tb_account WHERE account_id=?";    
 //调用查询对象的方法,此处方法的第二个参数是一个函数式接口对象,lambda表达式,返回account对象     
 Account account= jdbcTemplate.queryForObject(sql,(rs,rowNum)->{
    
               //在内存中创建一个账户对象来设置相应的信息以便后续返回       
   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.setCreateTime(rs.getDate("create_time"));   
   acc.setUpdateTime(rs.getDate("update_time")); 
   acc.setUserId(rs.getInt("user_id"));   
   return acc;   
   },accountId);
 return account;   
} 

@Test
public void queryAccountByAccountId(){
    
    
Account account = accountDao.queryAccountByAccountId(1); 
System.out.println(account);
}
  • 此处的queryForObject方法主要有3个参数,一个是sql,一个是RowMapper对象,一个是对应的可变参数
  • RowMaper参数是一个函数式接口,可以使用lambda表达式,该接口中需要传入行集和行数,需要接收返回的Account(Account的各个信息由行集中获取并返回。)最终返回为一个Account对象

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

  @Override
    public List<Account> queryAccountByParams(int 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+="and account_name Like contact('%',?,'%')";
            params.add(accountName);
        }

        if (StringUtils.isNotBlank(accountType)){
    
    
            //如果不为空
            sql+="AND account_type=?";
            params.add(accountType);
        }

        if (StringUtils.isNotBlank(createTime)){
    
    
            //如果创建时间不为空
            sql+="AND create_time<?";
            params.add(createTime);
        }

        //将集合转换为数组
        Object[] objects = params.toArray();
        List<Account> query = jdbcTemplate.query(sql, objects, (rs, 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.setCreateTime(rs.getDate("create_time"));
            acc.setUpdateTime(rs.getDate("update_time"));
            acc.setUserId(rs.getInt("user_id"));
            return acc;

        });
        return query;

    }

   @Test
    public void queryAccountByParams(){
    
    
        List<Account> accounts = accountDao.queryAccountByParams(1, "账户1", "招商银行", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        System.out.println(accounts.toString());
    }

  • 此处多条件查询,需要涉及到AND条件的拼接,注意点在于AND关键字之间需要空格。
  • jdbcTemplate.query方法需要传入三个参数,一个是sql、一个是参数数组,一个是RowMapper<T> rowMapper对象。
    • sql语句需要查询出所有的所需要的数据
    • 参数数组:需要判断里面的参数是否为空,不为空,则添加到参数数组中,并且对sql语句进行相应的拼接操作。
    • RowMapper对象需要使用lambda表达式依次对创建的对象进行相应的赋值,
  • 最终返回查询到的对象
  • 在测试的时候需要使用SimpleDateFormat类来对此进行格式化时间。

(四)账户记录的更新操作

1.更新账户记录

  /**
     * 更新账户,返回受影响的行数
     * @param account
     * @return
     */
    @Override
    public int updateAccount(Account account) {
    
    
        String sql="UPDATE tb_account SET account_name=?, account_type=?,money=?,remark=?," +
                "update_time=now() ,user_id=? WHERE account_id=?";
        int update = jdbcTemplate.update(sql, account.getAccountName(), account.getAccountType(),
                account.getMoney(), account.getRemark(), account.getUpdateTime(), account.getUserId()
                , account.getAccountId());
        return update;
    }

 @Test
    public void updateAccount(){
    
    
        int i = accountDao.updateAccount(new Account());
        System.out.println("更新了"+i+"条数据.");
    }

2.批量更新账户记录

 /**
     *批量更新账户记录
     * @param accounts
     * @return
     */
    @Override
    public int updateAccountBatch(List<Account> accounts) {
    
    
        String sql="UPDATE tb_account SET account_name=?, account_type=?,money=?,remark=?," +
                "update_time=now() ,user_id=? WHERE account_id=?";
        int length = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
    
    
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
    
    
                //此处由于传入的是集合,所以需要通过i来获取指定的Account对象(会自动循环)
                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;
        
        
          @Test
    public void testUpdateBatch(){
    
    
        List<Account>accounts=new ArrayList<>();
        accounts.add(new Account());
        accounts.add(new Account());
        int i = accountDao.updateAccountBatch(accounts);
        System.out.println("更新了"+i+"条数据");

    }

(五)账户记录删除实现

1.删除账户记录

 /**
     *删除账户记录,返回受影响的行数
     * @param accountId
     * @return
     */
    @Override
    public int deleteAccount(int accountId) {
    
    
        String sql="DELETE FROM tb_account WHERE account_id=?";
        int update = jdbcTemplate.update(sql, accountId);
        return update;
    }


 @Test
    public void deleteAccountByAccountId(){
    
    
        int i = accountDao.deleteAccount(1);
        System.out.println("受影响的行数"+i);
    }

对于jdbcTemplate而言,删除使用的是update语句

2.批量删除账户记录

 /**
     *批量删除用户记录,返回受影响的行数
     * @param ids
     * @return
     */
    @Override
    public int deleteAccountBatch(int[] ids) {
    
    
        String sql="DELETE FROM tb_account WHERE account_id=?";
        int length = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
    
    
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
    
    
                ps.setInt(1, ids[i]);
                //i表示行数
            }

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

 @Test
    public void deleteAccountBatch(){
    
    
        int[] ids={
    
    1,2,3};
        int i = accountDao.deleteAccountBatch(ids);
        System.out.println("受影响的行数有:"+ids);
    }
  • 批量删除使用batchupdate语句,传入sql语句与BatchPreparedStatementSetter对象
  • setValues是预处理语句循环赋值,因为是批处理,所以需要通过循环不断地为对应的处理语句赋值,最后返回参数数组的长度,即参数有多少个,赋了多少值。

三、事务的概念

(一)事务的四大特性(ACID)

1. 原子性(Atomicity)

要么全部成功,要么全部失败,指 一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着"同生共死"的感觉

2. 一致性(Consistency)

事务在执行前后,数据库中的数据要保持一致性状态(如转账过程前后需要保持数据的一致性),即只有改变前的状态和改变后的状态,没有所谓的中间状态。

3.隔离性(Isolation)

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

  • READ_UNCOMMITED(读未提交),隔离级别最低的一种事务级别,在这种隔离级别下,会引发脏读、不可重复读和幻读
  • READ_COMMITED(读已提交),读到的是别人提交后的值,在这种隔离级别下,会引发不可重复读和幻读,但是避免了脏读。
  • REPEATABLE_READ(可重复读),在这种隔离级别下,会引发幻读,但是避免了脏读和不可重复读
  • SERIALIZABLE(串行化),最严格的隔离级别,在serializable隔离级别下,所有事务按照次序依次进行,脏读不可重复读和幻读都不会出现。

4. 持久性(Durability)

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

(二)Spring事务核心接口

Spring事务管理的实现有很多细节,如果对整个接口框架有个大体的了解会非常有利于我们理解事务。

Spring并不是直接管理事务,而是提供了多种事务管理器,他们将事务的管理职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现的。

Spring事务管理器的接口是org.springframework.transaction.PlatformTranscationManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都通过供了对应的事务管理器,但是具体的实现可以依据各个平台自己的实际来定。接口内容如下:

public interface PlatformTransactionManager extends TransactionManager {
    
    
    //得到事务对象的方法
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    //提交事务
    void commit(TransactionStatus status) throws TransactionException;

    //回滚事务
    void rollback(TransactionStatus status) throws TransactionException;
}

从这里可知具体的事务管理机制对Spring来说是透明的,它并不关心具体的事务管理机制,哪些是对应各个平台需要关心的。所以Spring事务管理的一个优点就是为不同的事务API提供一个统一的编程模型,如JTA、JDBC、HIbernate、JPA等。

1. JDBC事务

如果应用程序中直接使用JDBC来进行持久化,此时使用DataSourceTransactionManager来处理事务边界,为了使用DataSourceTransactionManager,需要使用如下的xml信息将其装配到程序的上下文定义中去。(MyBatis也是使用DataSourceTransactionManager

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

事实上,DataSourceTransactionManager是通过调用java.sql.connection来管理事务,后者是通过DataSource获取的,通过调用连接的commit方法来提交事务,同样,如果事务失败,则通过调用rollback方法进行回滚。

2. Hibernate事务

使用HIbernate事务需要首先导入Hibernate的依赖坐标

 <!-- https://mvnrepository.com/artifact/org.springframework/spring-hibernate3 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-hibernate3</artifactId>
      <version>2.0.8</version>
    </dependency>

如果应用程序的持久化是通过HIbernate来实现的话,需要使用HibernateTransactionManager,对于HIbernate3,需要在spring的上下文中定义如下声明:

  <!--Hibernate实现事务管理-->
    <bean id="hibernateTransactionManager" 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方法。

3. Java持久化API事务(JPA)

使用JPA需要导入坐标依赖

   <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-jpa</artifactId>
      <version>2.6.1</version>
    </dependency>

Java持久化API作为真正的Java持久化标准进入我们的视野,使用JPA的话,需要使用Spring的JpaTransactionManager来处理事务,可以在xml文件中进入如下配置

 <bean id="jpaTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManager"/>
    </bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现),JpaTransactionManager将由与工厂所产生的JPA EntityManager合作构建事务。

4. Java原生API事务

如果应用程序跨越了多个事务管理器(比如两个或者多个不同的数据源)。此时需要使用JtaTransactionManager

 <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager/"/>
    </bean>

JtaTransactionManager将事务管理责任委托给javax.transaction.UserTransactionjavax.transaction.TransactionManager对象。其中事务成功完成通过UserTransaction.commit()方法提交,事务失败,则通过UserTransaction.rollback()方法回滚。

(三)Spring事务控制的配置

通过jdbc持久化事务,对于事务配置实现由两种方式来实现,即xml配置和注解配置

1. XML配置

(1)添加命名空间

事务

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

配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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
        https://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
">
(2)设置AOP代理
 <!--设置aop代理-->
 <aop:aspectj-autoproxy/>
(3)配置事务管理器
<!--配置JDBC事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
</bean>
(4)配置事务的相关通知
  <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--定义什么方法需要事务处理-->
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="delte*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="query*" read-only="true"/>
            <!--为所有的方法配置通知-->
            <tx:method name="*" propagation="REQUIRED"/>
         </tx:attributes>
    </tx:advice>

一般来说增删改使用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),以逗号分开。
    • no-rollback-for:不是必须的,表示不被触发进行回滚的Exception(s),以逗号分开。

事务传播行为的介绍

对应的属性值 对应的意义
@Transactional(propagation=Propagation.REQUIRED) 如果有事务,那么加入事务,如果或没有事务,默认情况下新建一个。
@Transactional(propagation=Propagation.NOT_SUPPORTED) 容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,再继续执行老的事务。
@Transactional(propagation=Propagation.MANDATORY) 必须在一个已有的事务中执行,否则会抛出异常
@Transactional(propagation=Propagation.NEVER) 必须在一个没有的事务中执行,否则抛出异常(与MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) 如果其他的bean调用这个方法,在其他bean中声明事务,那就用事务。如果在其他bean中没有声明事务,那就不用事务。
@Transactional(propagation=Propagation.NESTED) 支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
(5)配置AOP
   <!--定义AOP切面-->
    <aop:config>
        <!--定义切点-->
        <aop:pointcut id="pointCut" expression="execution(* org.apache.*.*(..))"/>
        <!--配置通知-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>
    </aop:config>

2. 注解配置

(1)配置事务管理器
   <!--配置JDBC事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--绑定数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
(2)配置注解支持
 <!--配置事务的注解支持-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
(3)加入事务注解

在需要使用事务的方法上添加事务的注解

@Override
@Transactional(propagation=Propagation.REQUIRED)
public void saverUser(){
    
    
    xxxxx
}    

(四)Spring事务控制——模拟转账的实现

1.接口方法定义

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

    /**
     * 表示收入方法
     * @param accountId
     * @param money
     * @return
     */
    int inAccount(int accountId,Double money);
2.接口实现
   /**
     * 表示支出
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int outAccount(int accountId, Double money) {
    
    
        String sql="UPDATE tb_account SET money=money-? WHERE account_id=?";
       return jdbcTemplate.update(sql,money,accountId);

    }

    /**
     *表示收入
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int inAccount(int accountId, Double money) {
    
    
        String sql="UPDATE tb_account SET money=money+? WHERE account_id=?";
        return jdbcTemplate.update(sql,money,accountId);

    }
3.业务层方法
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountService {
    
    
    @Autowired
    private IAccountDao accountDao;

    /**
     * 转账的业务操作
     * @param outId
     * @param inId
     * @param money
     * @return
     */
    public int updateAccountByTransfer(Integer outId,Integer inId,Double money){
    
    
        //code表示状态码,0表示失败,1表示成功
        int code=0;

        //支出方法
        int outRow=accountDao.outAccount(outId,money);
        //收入方法
        int inRow=accountDao.inAccount(inId,money);

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

}

@Transactional(propagation = Propagation.REQUIRED)表示用注解对该方法进行事务支持。

4.测试
public class TestTransfer extends BaseTest{
    
    
    @Autowired
    private AccountService accountService;
    @Test
    public void testTransfer(){
    
    
        int i = accountService.updateAccountByTransfer(1, 2, 100.0);
        if (i==1){
    
    
            System.out.println("转账成功");
        }

    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_50824019/article/details/130334971
今日推荐