Spring JDBC and transaction control

Table of contents

Spring JDBC and transaction control main content

(img-5c8BLbvZ-1682531464765)(03-Spring JDBC and transaction control.assets/SpringJDBC-19.png)]

Spring integrates JDBC environment

In addition to providing IOC and AOP core functions, the Spring framework also provides JDBC-based data access functions, making it easier to access data in the persistence layer. If you want to use the Spring JDBC environment, you need to integrate JDBC into Spring.

Build the project to add dependency coordinates

Build the project: just a normal java project

<!-- 添加相关的依赖坐标 -->
<!-- spring 依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.5</version>
</dependency>
<!-- spring jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.5</version>
</dependency>
<!-- 用mysql数据库,就需要 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>

Add jdbc configuration file

Create a new db.properties configuration file in the src/main/resources directory and set the corresponding configuration information

The purpose of putting it in the configuration file: you can directly modify it in the configuration file when you make subsequent modifications.

# 驱动名
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库连接
jdbc.url=jdbc:mysql://127.0.0.1:3306/(数据库名称)?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
# 数据库用户名称
jdbc.user=(数据库账号)
# 数据库用户密码
jdbc.password=(数据库密码)

The following are optional configurations

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

Write spring configuration file

Write the spring configuration file: applicationcontext.xml

<?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">

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

</beans>

Configure data source

​ Since establishing a database connection is a very time-consuming and resource-consuming behavior, some connections are established in advance with the database through the connection pool and stored in memory. When the application needs to establish a database connection, it is sufficient to apply for one directly in the connection pool. Put it back again.

​ C3P0 and DBCP can choose one of the two

​ DBCP (DataBase connection pool), database connection pool. It is a java connection pool project on apache and also a connection pool component used by tomcat. Using dbcp alone requires 2 packages: commons-dbcp.jar, commons-pool.jar dbcp, there is no function of automatically recycling idle connections.

​ C3P0 is an open source JDBC connection pool that implements data sources and supports JDBC3 specifications and JDBC2 standard extensions. The open source projects currently using it include Hibernate, Spring, etc. c3p0 has the function of automatically reclaiming idle connections.

​ We generally use: C3P0.

C3P0 data source configuration

Add the following configuration to the applicationContext.xml file:

<!-- 配置 c3p0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!-- property标签的value属性对应的是jdbc.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 other additional configurations (corresponding values ​​are specified in the jdbc.properties file)

<!-- 指定连接池中保留的最大连接数。 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}"/>

DBCP data source configuration

Add the following configuration to the applicationContext.xml file:

<!-- 配置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>

Template class configuration

Create a JDBC template, and we will perform JDBC-related operations through the template.

​ Spring creates a template class for repeated operations in JDBC: org.springframework.jdbc.core.JdbcTemplate.

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

Spring JDBC Tests (Getting Started)

Create a specified database

Select the connection, right-click and select "New Database", set the name and encoding format of the database

insert image description here

Create data table

Create account table: tb_account:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-3lGPLbL5-1682531464768)(03-Spring JDBC and transaction control.assets/SpringJDBC-02.png)]

Add data to the table after creating the table:
insert image description here

Test with JUnit

Perform junit test, parse applicationContext.xml, and get jdbcTemplate bean

If you use junit, you need to add junit dependencies to pom.xml:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope><!--只能在test目录下进行单元测试,在main->java目录下就无法使用-->
</dependency>

JUnit tests

Create a new com.msb.test package in the test/java directory to build test classes and methods:

package com.msb.test;

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

/**
 * @Author: zhaoss
 */
public class SpringJdbcTest01 {
    
    
    @Test
    public void testQueryCount() {
    
    
        // 获取spring上下文环境
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 得到模板类 JdbcTemplate对象
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.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 testQueryCountByUserId() {
    
    
        // 获取spring上下文环境
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 得到模板类 JdbcTemplate对象
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");

        // 通过模板进行CRUD操作
        // 定义sql语句:案例:查询指定用户有多少个账户
        String sql = " select count(1) from tb_account where user_id = ?";
        // 执行查询操作(有参数)第一个参数
        Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);
        System.out.println("总记录数:" + total);
    }
}

How to use the JdbcTemplate template can be viewed through the official website:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-EBAqZVNu-1682531464769) (03-Spring JDBC and transaction control.assets/SpringJDBC-04.png)]

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-BlFdDBLD-1682531464770) (03-Spring JDBC and transaction control.assets/SpringJDBC-05.png)]

Optimize JUnit unit test - traditional unit test method

In the above introductory test case, you can see that: the testQueryCount() method and the testQueryCountByUserId() method have a lot of repetitions, so can the repetitions be simply encapsulated? Based on the characteristics of the unit test, you can use the @Before annotation: (code that can be executed before executing the unit test method)

package com.msb.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;

/**
 * @Author: zhaoss
 */
public class SpringJdbcTest01 {
    
    
    private JdbcTemplate jdbcTemplate;

    @Before
    public void init() {
    
    
        // 获取spring上下文环境
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 得到模板类 JdbcTemplate对象
        jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
    }
    @Test
    public void testQueryCount() {
    
    
        // 通过模板进行CRUD操作
        // 定义sql语句:案例:查询总计有多少个账户
        String sql = "select count(1) from tb_account";
        // 执行查询操作(无参数)
        Integer total= jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数:" + total);
    }

    @Test
    public void testQueryCountByUserId() {
    
    
        // 通过模板进行CRUD操作
        // 定义sql语句:案例:查询指定用户有多少个账户
        String sql = " select count(1) from tb_account where user_id = ?";
        // 执行查询操作(有参数)第一个参数
        Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);
        System.out.println("总记录数:" + total);
    }
}

Optimize JUnit unit test - Spring Test module

Spring can make unit testing easier. Spring provides the Spring Test module, which integrates some common unit testing tools, such as Junit.

After integration, you can directly use the content in the Spring container in the test class, which is equivalent to putting the test class into the Spring container, and you can directly use annotations to inject some bean objects into the test class.

At the same time, you can also specify the configuration file path through the @ContextConfigration annotation, so that the test method can directly load the configuration file when it starts.

add dependencies

​ spring-context core dependencies must be imported no matter which module is used. (this we have already added)

​ It is necessary to import junit dependencies, and now we are using the junit integrated by the Spring Test module. (this we have already added)

​ spring-test represents the dependency of the spring test module, and the @ContextConfiguration annotations can only be added after importing (this needs to be added now)

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.5</version>
    <scope>test</scope>
</dependency>

Add comments to the code
package com.msb.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * @Author: zhaoss
 */
// 使用Spring整合Junit4的类启动当前测试类 (这个注解必须要用4.12以上版本的junit)
// RunWith、ContextConfiguration这些注解不用扫描,@RunWith就会自动去构建测试类对象
@RunWith(SpringJUnit4ClassRunner.class)
// 启动时加载的配置文件,里面要包含classpath
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringJdbcTest02 {
    
    
    // 将容器中的jdbcTemplate注入进来
    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Test
    public void testQueryCount() {
    
    
        // 通过模板进行CRUD操作
        // 定义sql语句:案例:查询总计有多少个账户
        String sql = "select count(1) from tb_account";
        // 执行查询操作(无参数)
        Integer total= jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数:" + total);
    }

    @Test
    public void testQueryCountByUserId() {
    
    
        // 通过模板进行CRUD操作
        // 定义sql语句:案例:查询指定用户有多少个账户
        String sql = " select count(1) from tb_account where user_id = ?";
        // 执行查询操作(有参数)第一个参数
        Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);
        System.out.println("总记录数:" + total);
    }
}

PS: The above annotations take effect, and the scanning is not configured in applicationContext.xml, because the Spring integrated Junit4 class will help us deal with it, and we don't need to consider the scanning problem.

Optimize JUnit unit test-Spring Test module-general encapsulation

If there are more than n test classes, @RunWith and @ContextConfiguration annotations must be written every time. If the configuration file is modified, the classpath in each file must be modified, which is very troublesome, so we can extract it again:

  1. Define a parent class and set common configuration information

    /**
     * @Author: zhaoss
     */
    // 使用Spring整合Junit4的类启动当前测试类 (这个注解必须要用4.12以上版本的junit)
    // RunWith、ContextConfiguration这些注解不用扫描,@RunWith就会自动去构建测试类对象
    @RunWith(SpringJUnit4ClassRunner.class)
    // 启动时加载的配置文件,里面要包含classpath
    @ContextConfiguration(locations = "classpath:applicationContext.xml")
    public class BaseTest {
          
          
        
    }
    
  2. Inherit the common test class

    public class SpringJdbcTest03 extends BaseTest {
          
          
    
        // 将容器中的jdbcTemplate注入进来
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
    
        @Test
        public void testQueryCount() {
          
          
            // 通过模板进行CRUD操作
            // 定义sql语句:案例:查询总计有多少个账户
            String sql = "select count(1) from tb_account";
            // 执行查询操作(无参数)
            Integer total= jdbcTemplate.queryForObject(sql, Integer.class);
            System.out.println("总记录数:" + total);
        }
    
        @Test
        public void testQueryCountByUserId() {
          
          
            // 通过模板进行CRUD操作
            // 定义sql语句:案例:查询指定用户有多少个账户
            String sql = " select count(1) from tb_account where user_id = ?";
            // 执行查询操作(有参数)第一个参数
            Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);
            System.out.println("总记录数:" + total);
        }     
    }
    

Persistence layer (dao layer) - account module - operation

​ After the Spring Jdbc environment integration is completed, spring jdbc is used here to complete the crud operation of the account form tb_account. But in actual development, frameworks are still used, such as Mybatis.

Define entity class

Create com.msb.pojo package, define Account.java

package com.msb.entity;

import java.util.Date;

/**
 * 用户账户类
 */
public class Account {
    
    

    private Integer accountId; // 账户ID,主键
    private String accountName; // 账户名称
    private String accountType; // 账户类型
    private Double money; // 账户金额
    private String remark; // 账户备注
    private Integer userId; // 用户ID,账户所属用户
    private Date createTime; // 创建时间
    private Date updateTime; // 修改时间

    public Account() {
    
    

    }
	// 构造器中不需要加:账户ID(利用主键自增即可)、创建时间、修改时间(这个时间通过sql操作即可)
    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 + '\'' +
                ", userId=" + userId +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                '}';
    }

    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 Integer getUserId() {
    
    
        return userId;
    }

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

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

Account interface and method definition in dao layer

define interface class

AccountDao.java

package com.msb.dao;
import com.msb.pojo.Account;
import java.util.List;


/**
 * @Author: zhaoss
 * 用户模块 接口定义
 *      1. 添加账户
 *          添加账户记录,返回受影响的行数
 *          添加账户记录,返回记录的主键
 *          批量添加账户记录,返回受影响的行数
 *      2. 查询账户
 *          查询指定用户的账户总记录数,返回该记录数
 *          通过账户主键id,查询指定账户的记录详情,返回账户信息
 *          多条件查询指定用户的账户列表,返回账户集合
 *      3. 更新账户
 *          更新账户记录,返回受影响的行数
 *          批量更新账户记录,返回受影响的行数
 *      4. 删除账户
 *          删除账户记录,返回受影响的行数
 *          批量删除账户记录,返回受影响的行数
 */
public interface AccountDao {
    
    
    // 添加操作:添加账户记录,返回受影响的行数:
    int addAccount(Account account);
    // 添加操作:添加账户记录,返回记录的主键:
    int addAccountReturnKey(Account account);
    // 添加操作:批量添加账户记录,返回受影响的行数:
    int addAccountBatch(List<Account> accounts);
    // 查询操作:查询指定用户的账户总记录数,返回账户总记录数:
    int queryAccountCount(Integer userId);
    // 查询操作:通过账户的主键id,查询对应的账户的信息:
    Account queryAccountById(Integer accountId);
    /**
     查询操作:多条件查询指定用户的账户列表,返回账户集合:
     @param userId 指定的账户的id
     @param accountName 账户名称 (利用模糊查询)
     @param createTime 创建时间  (查询大于当前时间)
     */
    List<Account> queryAccountByParams(Integer userId,String accountName,String createTime);
    // 更新操作:更新账户记录,返回受影响的行数:
    int updateAccountById(Account account);
    // 更新操作:批量更新账户记录,返回受影响的行数:
    int updateAccountBatch(List<Account> accounts);
    // 删除操作:根据主键id删除对应的账户,返回受影响的行数:
    int deleteAccountById(Integer accountId);
    // 删除操作:批量删除-根据主键id删除对应的账户,返回受影响的行数:
    int deleteAccountBatch(Integer[] accountIds);

}

Define the interface implementation class

package com.msb.dao.impl;

import com.msb.dao.AccountDao;
import com.msb.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author: zhaoss
 * AccountDaoImpl对象的构建,我们交给ioc容器去管理,加入@Repository注解:
 */
@Repository
public class AccountDaoImpl implements AccountDao {
    
    
    // 注入JdbcTemplate模板对象:
    @Autowired
    JdbcTemplate jt;
    @Override
    public int addAccount(Account account) {
    
    
        return 0;
    }

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

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

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

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

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

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

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

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

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

If you want @Repository annotations and @Autowired annotations to work, you must scan in applicationContext.xml and add configuration:

<?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:property-placeholder location="db.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <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>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--扫描注解-->
    <context:component-scan base-package="com.msb.dao"></context:component-scan>
</beans>

Account record addition implementation

​ During enterprise project development, adding records may involve multiple adding methods, such as adding a single record, adding multiple records in batches, and so on. There are three ways to add account records here: add a single record and return the number of affected rows, add a single record and return the primary key, and add multiple records in batches.

Add account record

Rewrite method in AccountDaoImpl.java:

/**
  * 添加单条记录,返回受影响的行数
  * @param account
  * @return
  */
@Override
public int addAccount(Account account) {
    
    
    String sql = "insert into tb_account(account_name,account_type,money,remark," +
        "user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
    Object[] objs = {
    
    account.getAccountName(),account.getAccountType(),
                     account.getMoney(),account.getRemark(),account.getUserId()};
    return jdbcTemplate.update(sql,objs);
}       

Test Methods

package com.msb.test;

import com.msb.dao.AccountDao;
import com.msb.pojo.Account;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @Author: zhaoss
 */
public class TestSpringJDBCAddTest extends BaseTest{
    
    
    // 注入持久层对象(dao层)对象
    @Autowired
    AccountDao accountDao;
    @Test
    public void testAddAccount(){
    
    
        // 准备你要添加的数据:
        Account account = new Account("账号3","工商银行",400.0,"奖金",1);
        // 调用AccountDaoImpl对象的addAccount方法,传入上面要添加的数据account:
        int row = accountDao.addAccount(account);
        System.out.println("添加账户,受影响的行数为:" + row);
    }
}

reference:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-hZAckNJ5-1682531464771) (03-Spring JDBC and transaction control.assets/SpringJDBC-10.png)]

Add record returns primary key

@Override
    public int addAccountReturnKey(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();
        // 利用模板调用方法:
        /*jt.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                // 预编译sql语句,并设置返回主键:
                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);*/

		// 利用lambda表达式的写法:
        jt.update(con ->  {
    
    
                // 预编译sql语句,并设置返回主键:
                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对象来获取主键:
        int key = keyHolder.getKey().intValue();

        return key;
    }

Test Methods

/**
  * 添加账户记录,返回主键
  */
	@Test
    public void testAddAccount2(){
    
    
        // 准备你要添加的数据:
        Account account = new Account("账号4","招商银行",1400.0,"兼职费用",2);
        // 调用AccountDaoImpl对象的addAccount方法,传入上面要添加的数据account:
        int key = accountDao.addAccountReturnKey(account);
        System.out.println("对应的主键为:" + key);
    }

reference:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-IGbwOY7y-1682531464772) (03-Spring JDBC and transaction control.assets/SpringJDBC-07.png)]

Add account records in batches

/**
  * 添加多条记录,返回受影响的行数
  * @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[] nums = jt.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();
        }
    });
    // 获取影响几行记录:
    return nums.length;
}

Test Methods

/**
  * 批量添加数据,返回受影响的行数
  */
@Test
public void testAddAccountBatch() {
    
    
    // 准备你要添加的数据:
    Account account1 = new Account("账号8","招商银行",13450.0,"兼职费用",3);
    Account account2 = new Account("账号9","工商银行",1670.0,"奖金",3);
    Account account3 = new Account("账号10","建设银行",7400.0,"工资",3);

    // 上面的三个账户添加到集合中,以便以后传入批量添加的方法中:
    List<Account> accouts = new ArrayList<>();
    accouts.add(account1);
    accouts.add(account2);
    accouts.add(account3);

    // 调用AccountDaoImpl对象的addAccountBatch方法,传入上面要添加的数据accouts:
    int row = accountDao.addAccountBatch(accouts);
    System.out.println("添加账户,受影响的行数为:" + row);
}

reference:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-vKWWcl24-1682531464773) (03-Spring JDBC and transaction control.assets/SpringJDBC-08.png)]

Account record query implementation

​ Account record query Three query methods are provided here, query the number of all account records of a specified user, query the details of a single account record, and query the account records of a specified user with multiple conditions.

Query the total number of records of the user's account

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

Test Methods

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

reference:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-PYw7TQ8e-1682531464774) (03-Spring JDBC and transaction control.assets/SpringJDBC-09.png)]

Query the details of the specified account record

/**
  * 查询某个账户记录详情,返回账户对象
  * @param accountId
  * @return
  */
@Override
    public Account queryAccountById(Integer accountId) {
    
    
        // 查询操作:通过账户的主键id,查询对应的账户的信息:
        // 定义sql:
        String sql = "select * from tb_account where account_id = ?";
        // 利用模板调用方法操作:
        /*Account account = jt.queryForObject(sql, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                // 创建一个Account对象,接收数据库中查询的数据:
                Account a = new Account();
                a.setAccountId(accountId);
                a.setAccountName(rs.getString("account_name"));
                a.setAccountType(rs.getString("account_type"));
                a.setMoney(rs.getDouble("money"));
                a.setRemark(rs.getString("remark"));
                a.setCreateTime(rs.getDate("create_time"));
                a.setUpdateTime(rs.getDate("update_time"));
                a.setUserId(rs.getInt("user_id"));
                return a;
            }
        }, accountId);*/
        // 利用lambda表达式简化:
        Account account = jt.queryForObject(sql, (ResultSet rs, int rowNum) -> {
    
    
                // 创建一个Account对象,接收数据库中查询的数据:
                Account a = new Account();
                a.setAccountId(accountId);
                a.setAccountName(rs.getString("account_name"));
                a.setAccountType(rs.getString("account_type"));
                a.setMoney(rs.getDouble("money"));
                a.setRemark(rs.getString("remark"));
                a.setCreateTime(rs.getDate("create_time"));
                a.setUpdateTime(rs.getDate("update_time"));
                a.setUserId(rs.getInt("user_id"));
                return a;
            }, accountId);
      
        return account;
    }

Test Methods

/**
  * 查询指定账户的记录详情,返回账户对象
  */
@Test
    public void testquery02(){
    
    
        // 调用accountDao中的queryAccountById:查询操作:通过账户的主键id,查询对应的账户的信息:
        Account account = accountDao.queryAccountById(10);
        System.out.println("该用户的账户信息为:" + account.toString());
    }

reference:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-VqzW89ZG-1682531464775) (03-Spring JDBC and transaction control.assets/SpringJDBC-11.png)]

Multi-condition query user account records

/**
     查询操作:多条件查询指定用户的账户列表,返回账户集合:
     @param userId 指定的账户的用户id
     @param accountName 账户名称 (利用模糊查询)
     @param createTime 创建时间  (查询大于当前时间)
     */
    @Override
    public List<Account> queryAccountByParams(Integer userId, String accountName, String createTime) {
    
    
        // 定义sql :
        String sql = "select * from tb_account where user_id = ?";
        // 定义参数列表:案例我们不用那么多参数,只用userId先测试,参数列表可以先不定义。
        // 如果案例中有多个参数,那么就需要定义数组,然后传入可变参数位置:--->直接定义数组方案不可取,因为数组长度不能改变:
        // 将数组改为集合:
        // Object[] params = {userId};
        List params = new ArrayList();
        params.add(userId);

        // 根据参数是否为空将sql进行拼接:
        if(accountName != null && !"".equals(accountName)){
    
    
            // 模糊查询  注意:and前面一定要加空格,否则报错
            sql += " and account_name like concat('%',?,'%')";
            params.add(accountName);
        }
        if(createTime != null && !"".equals(createTime)){
    
    
            sql += " and create_time < ?";
            params.add(createTime);
        }

        // 将集合转为数组:
        Object[] objects = params.toArray();

        // 使用模板:
        List<Account> accountList = jt.query(sql, (ResultSet rs, int rowNum) -> {
    
    
            // 创建一个Account对象,接收数据库中查询的数据:
            // 查询出来的每个数据,利用一个Account对象做接收,然后query方法内部会将对象放入List集合中返回
            Account a = new Account();
            a.setAccountId(rs.getInt("account_id"));
            a.setAccountName(rs.getString("account_name"));
            a.setAccountType(rs.getString("account_type"));
            a.setMoney(rs.getDouble("money"));
            a.setRemark(rs.getString("remark"));
            a.setCreateTime(rs.getDate("create_time"));
            a.setUpdateTime(rs.getDate("update_time"));
            a.setUserId(rs.getInt("user_id"));
            return a;
        }, objects);


        return accountList;
    }

Test Methods

@Test
    public void testquery03(){
    
    
        // 调用accountDao中的queryAccountByParams:
        List<Account> accountList = accountDao.queryAccountByParams(1,null,null);

        /*System.out.println(accountList.toString());
        System.out.println(accountList.size());*/

        List<Account> accountList2 = accountDao.queryAccountByParams(1,"1",null);

        /*System.out.println(accountList2.toString());
        System.out.println(accountList2.size());*/

        List<Account> accountList3 = accountDao.queryAccountByParams(1,null,"2023-3-20");

        System.out.println(accountList3.toString());
        System.out.println(accountList3.size());


    }

Reference: If there are parameters, pass in the parameters.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-Ot3an8UX-1682531464776) (03-Spring JDBC and transaction control.assets/SpringJDBC-12.png)]

Account record update implementation

update account records

/**
  * 更新指定账户记录,返回受影响的行数
  * @param account
  * @return
  */
@Override
public int updateAccountById(Account account) {
    
    
     String sql = "update tb_account set account_name = ?, account_type = ?, " +
                " money = ? ,remark = ?,user_id = ? ,update_time = now() " +
                " where account_id = ? ";
     Object[] objs = {
    
    account.getAccountName(),account.getAccountType(),
                     account.getMoney(), account.getRemark(),account.getUserId(),
                     account.getAccountId()};
     return jdbcTemplate.update(sql,objs);
}

Test Methods

/**
  * 更新指定账户记录,返回受影响的行数
  */
@Test
public void testUpdateAccount(){
    
    
    // 准备要修改的数据
    Account account = new Account("张三1","建设银行1",500.0,"零花钱加倍",1);
    account.setAccountId(1);
    int row = accountDao.updateAccountById(account);
    System.out.println("修改账户返回受影响的行数:" + row);
}

reference:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-oLvkCG4B-1682531464777) (03-Spring JDBC and transaction control.assets/SpringJDBC-13.png)]

Batch update account records

/**
  * 批量新账户记录,返回受影响的行数
  * @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[] nums = jt.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();
        }
    });
    // 获取影响几行记录:
    return nums.length;
}

Test Methods

/**
  * 批量更新账户记录,返回受影响的行数
  */
@Test
public void testupdate02(){
    
    
    // 调用accountDao中的updateAccountBatch
    // 封装Account对象:
    Account ac1 = new Account("账户66","农业银行",220.0,"奖金",1);
    ac1.setAccountId(9);
    Account ac2 = new Account("账户77","农业银行",240.0,"奖金",1);
    ac2.setAccountId(10);
    Account ac3 = new Account("账户88","农业银行",290.0,"奖金",1);
    ac3.setAccountId(11);

    // 上面的数据要放入List集合中:
    List list = new ArrayList();
    list.add(ac1);
    list.add(ac2);
    list.add(ac3);


    int row = accountDao.updateAccountBatch(list);
    System.out.println("更新操作影响行数为:" + row);


}

reference:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-o8fofDMa-1682531464778) (03-Spring JDBC and transaction control.assets/SpringJDBC-14.png)]

Account record deletion implementation

delete account records

/**
  * 删除账户记录,返回受影响的行数
  * @param accountId
  * @return
  */
@Override
public Integer deleteAccoutById(Integer accountId) {
    
    
    String sql = "delete from tb_account where account_id= ? ";
    Object[] objs = {
    
    accountId};
    return jdbcTemplate.update(sql,objs);
}

Test Methods

/**
  * 删除账户记录,返回受影响的行数
  */
@Test
public void testDeleteAccount(){
    
    
    // 删除ID为1的账户记录
    int row = accountDao.deleteAccoutById(1);
    System.out.println("删除账户记录返回受影响的行数:" + row);
}

Batch delete account records

 /**
   * 批量删除账户记录,返回受影响的行数
   * @param ids
   * @return
   */
@Override
public int deleteAccountBatch(Integer[] ids) {
    
    
    String sql = "delete from tb_account where account_id = ?";
    int row = 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 row;
}

Test Methods

/**
  * 批量删除账户记录,返回受影响的行数
  */
@Test
public void testDeleteAccountBatch(){
    
    
    // 删除多个id的账户记录
    Integer[] ids = new Integer[]{
    
    2,3};
    int rows = accountDao.deleteAccountBatch(ids);
    System.out.println("批量删除账户记录返回受影响的行数:" + rows);
}

Spring transaction control

Introduce transaction management through transfer operation cases.

Realization of transfer scenario simulation

The project construction is consistent with that in SpringJDBC, and will not be repeated here.

interface method definition

The dao layer defines the interface: The transfer involves the accounts of both parties and the corresponding transfer amount, so there are two methods of depositing and withdrawing.

package com.msb.dao;

/**
 * @Author: zhaoss
 */
public interface AccountDao {
    
    
    /**
      * 收入
      * @param tarAid 收入金额的账户ID
      * @param money 收入金额
      * @return
      */
    public abstract int inAccount(Integer accountId,Double money);
    /**
      * 支出
      * @param outAid 支出金额的账户ID
      * @param money  支出金额
      * @return
      */
    public abstract int outAccount(Integer accountId,Double money);
}

Implement the corresponding interface

package com.msb.dao.impl;

import com.msb.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * @Author: zhaoss
 */
@Repository
public class AccountDaoImpl implements AccountDao {
    
    

    // 注入JdbcTemplate模板对象:
    @Autowired
    JdbcTemplate jt;
    @Override
    public int inAccount(Integer accountId, Double money) {
    
    
        // 定义sql:
        String sql = "update tb_account set money = money + ? where account_id = ?";
        // 参数:
        Object[] params = {
    
    money,accountId};
        // 使用模板:
        int i = jt.update(sql, params);
        return i;
    }

    @Override
    public int outAccount(Integer accountId, Double money) {
    
    
        // 定义sql:
        String sql = "update tb_account set money = money - ? where account_id = ?";
        // 参数:
        Object[] params = {
    
    money,accountId};
        // 使用模板:
        int i = jt.update(sql, params);
        return i;
    }
}

Transfer method business layer implementation

Implemented in the service layer:

package com.msb.service;

import com.msb.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: zhaoss
 */
@Service
public class AccountService {
    
    
    @Autowired
    private AccountDao ad;

    // 定义业务方法:转账操作:返回值为int类型,在方法内部可以定义一个状态码:成功返回1,失败返回0
    public int updateAccount(Integer outid,Integer inid,Double money){
    
    
        // 定义状态码:成功1,失败0,初始化状态下 code是0
        int code = 0;
        // 账户A(id为6)给账户B(id为10)转100 :
        // 账户A支出100:
        int outRow = ad.outAccount(outid, money);
        // 账户B收入100:
        int inRow = ad.inAccount(inid, money);
        // 如果收入和支出两个操作都执行成功的话,表示转账成功:
        if (outRow == 1 && inRow == 1){
    
    
            code = 1;// 转账成功
        }
        // 状态码作为方法的返回值:
        return code;
    }
}

Need to join transaction control

We artificially made mistakes in the above code, so that account A's expenditure was successful, but account B's income failed:

package com.msb.service;

import com.msb.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: zhaoss
 */
@Service
public class AccountService {
    
    
    @Autowired
    private AccountDao ad;

    // 定义业务方法:转账操作:返回值为int类型,在方法内部可以定义一个状态码:成功返回1,失败返回0
    public int updateAccount(Integer outid,Integer inid,Double money){
    
    
        // 定义状态码:成功1,失败0,初始化状态下 code是0
        int code = 0;
        // 账户A(id为6)给账户B(id为10)转100 :
        // 账户A支出100:
        int outRow = ad.outAccount(outid, money);
        // 加入异常代码:
        int a = 1 / 0;
        // 账户B收入100:
        int inRow = ad.inAccount(inid, money);
        // 如果收入和支出两个操作都执行成功的话,表示转账成功:
        if (outRow == 1 && inRow == 1){
    
    
            code = 1;// 转账成功
        }
        // 状态码作为方法的返回值:
        return code;
    }
}

Executing the test class again found that the program reported an error, account A lost 100, but account B did not add 100 yuan.

Reasons for the above phenomenon:

By default, MySQL transactions are automatically committed. That is to say, when a DML statement is executed, MySQL will immediately and implicitly commit the transaction. That is to say, when the transfer operation of account A is executed, the transaction is committed, and then an exception occurs, and the transfer of account B operation not performed

PS: Transaction: It is a collection of a group of operations. It is an indivisible unit of work. A transaction will submit or revoke operation requests to the system together with all operations as a whole, that is, these operations either succeed at the same time or fail at the same time.

solve:

Put the transfer operation into the same transaction for processing.

**Method 1:** Programmatic transactions. (The transaction code is written by the programmer himself)

Intercept previous code:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-6eks3oY5-1682531464779) (03-Spring JDBC and transaction control.assets/SpringJDBC-15.png)]

If you think about the code carefully, you will find that there is no guarantee that the business code of the service layer will not be abnormal during the running of the program. If the transaction is processed through jdbc, the transaction needs to be controlled manually at this time. In this case, all business methods involving transaction control require developers. Manual transaction processing cannot meet the needs of production.

(It is not appropriate to add it at the business layer or at the dao layer. Even if it is added, all methods related to transaction control need to be processed. It is too troublesome, so method 2 is required)

**Method 2:** Declarative transaction. (The framework is responsible for the transaction code, and the programmer can declare and configure it simply.)

Spring provides a transaction processing method, which encapsulates all the required content, and programmers only need simple configuration. Where is the package? as follows:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-XtKVv0Z0-1682531464779) (03-Spring JDBC and transaction control.assets/SpringJDBC-17.png)]

How to understand it?

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-Ji1BFs6F-1682531464780) (03-Spring JDBC and transaction control.assets/SpringJDBC-18.png)]

Spring transaction core interface

​ Spring does not directly manage transactions, but provides a variety of transaction managers. They delegate the responsibility of transaction management to the transactions of related platform frameworks provided by persistence mechanisms such as Hibernate or JTA.

​ The interface of the Spring transaction manager is org.springframework.transaction.PlatformTransactionManager. Through this interface, Spring provides corresponding transaction managers for various platforms such as JDBC, Hibernate, etc., but the specific implementation is the business of each platform. The content of this interface is as follows:

public interface PlatformTransactionManager(){
    
     
    // 由 TransactionDefinition 得到 TransactionStatus 对象 
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交 
    void commit(TransactionStatus status) throws TransactionException; 
    // 回滚 
    void rollback(TransactionStatus status) throws TransactionException; 
}

​ From here, we can see that the specific specific transaction management mechanism is transparent to Spring, and it does not care about those, which need to be concerned about corresponding to each platform, so one of the advantages of Spring transaction management is to provide consistent services for different transaction APIs. Programming models such as JTA, JDBC, Hibernate, JPA. The following introduces the mechanisms of each platform framework to implement transaction management.

JDBC transactions

​ If the application directly uses JDBC for persistence, use DataSourceTransactionManager to handle transaction boundaries. In order to use the DataSourceTransactionManager, it needs to be assembled into the application context definition using the following XML:

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>

​ In fact, DataSourceTransactionManager manages transactions by calling java.sql.Connection, which is obtained through DataSource. Commit the transaction by calling the commit() method of the connection. Similarly, if the transaction fails, it will be rolled back by calling the rollback() method.

Hibernate transactions

​ If the persistence of the application is implemented through Hibernate, then you need to use HibernateTransactionManager. For Hibernate3, the following statement needs to be added to the Spring context definition:

<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory" />
</bean>

​ The sessionFactory attribute needs to assemble a Hibernate session factory. The implementation details of HibernateTransactionManager are that it delegates the responsibility of transaction management to the org.hibernate.Transaction object, which is obtained from the Hibernate Session. When the transaction is successfully completed, HibernateTransactionManager will call the commit() method of the Transaction object, otherwise, the rollback() method will be called.

Java Persistence API Transactions (JPA)

Hibernate has been the Java Persistence Standard for many years, but now the Java Persistence API has entered everyone's field of vision as the true Java Persistence Standard. If you plan to use JPA, then you need to use Spring's JpaTransactionManager to handle transactions. You need to configure JpaTransactionManager like this in Spring:

<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
	<property name="sessionFactory" ref="sessionFactory" />
</bean>

JpaTransactionManager only needs to be equipped with a JPA entity management factory (any implementation of the javax.persistence.EntityManagerFactory interface). The JpaTransactionManager will work with the JPA EntityManager generated by the factory to construct transactions.

Java Native API Transactions

​ If the application does not use the transaction management described above, or spans multiple transaction management sources (such as two or more different data sources), you need to use JtaTransactionManager:

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

​ JtaTransactionManager delegates the responsibility of transaction management to javax.transaction.UserTransaction and javax.transaction.TransactionManager objects, where the successful completion of the transaction is committed through the UserTransaction.commit() method, and the transaction failure is rolled back through the UserTransaction.rollback() method.

Spring transaction control configuration

​Through jdbc persistent transactions, there are two ways to implement transaction configuration: Xml configuration and annotation configuration.

XML configuration

import namespace dependencies
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
    <!--<scope>runtime</scope>-->
</dependency>
add namespace

Add transaction tx and aop namespaces in the spring.xml configuration file

affairs

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

The configuration is as follows

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">

    
    <context:property-placeholder location="db.properties"></context:property-placeholder>
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <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>

    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan>


    <!--配置声明式事务-->
    <!--配置事务管理器:封装事务固定套路代码的类-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源:原因:因为调用封装的方法,需要用Connection,需要靠数据源获取-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务通知  transaction-manager属性表示这个事务通知是哪个事务管理器管理的-->
    <tx:advice id="t" transaction-manager="transactionManager">
        <!--具体给哪个方法加入通知呢?把方法写在这
			updateAccount是一个事务单元
			注意:该方法出现异常就会触发异常通知实现事务回滚,所以在该方法内部决不能加入try-catch对异常处理
		-->
        <tx:attributes>
            <tx:method name="updateAccount"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务切面-->
    <aop:config>
        <!--设置哪个方法需要被声明式事务管理,通过切点定义需要声明式事务管理的方法,但是这里表达式的范围写大一些,不能定位到具体方法,需要配合上面事务通知一起才会生效-->
        <aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/>
         <!--advice-ref指向上面的通知id-->
        <aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor>
    </aop:config>
</beans>

Note: The default spring transaction is rolled back only when an uncaught runtimeexcetpion occurs.

The principle of spring aop exception capture:

The intercepted method needs to explicitly throw an exception without any processing, so that the aop agent can catch the exception of the method and roll back. By default, aop only catches the exception of runtime exception, but it can be configured to catch specific Exception and rollback In other words, do not use try catch in the service method or add throw new RunTimeexcetpion() at the end of the catch, so that when the program is abnormal, it can be caught by aop and rolled back.

Introduction to declarative transaction attributes

<tx:method>The tag has the configuration of the following properties

name attribute

​ Configure which methods require transaction control, support * wildcards

<tx:advice id="t" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 表示所有以update开头的方法需要进行事务管理 -->
        <tx:method name="update*"/>
        <!-- 表示所有方法需要进行事务管理-->
        <!-- <tx:method name="*"/>-->
    </tx:attributes>
</tx:advice>
readonly attribute (true|false)

Whether it is a read-only transaction. If it is true, it tells the database that this transaction is a read-only transaction. The bottom layer supports the code logic of the query, and the performance will be improved by not using the code of committing and rolling back the transaction, so as long as it is a query method , It is recommended to set readonly="true". If it is false (default value), the transaction needs to be submitted. It is recommended to add, delete, modify without setting the readonly attribute or set readonly="false".

<tx:advice id="t" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 所有select开头的方法执行查询逻辑 -->
        <tx:method name="select*" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>
timeout

Transaction failed to commit after timeout

<tx:advice id="t" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="select*" timeout="5"></tx:method>
    </tx:attributes>
</tx:advice>
rollback-for attribute

​ Exception type fully qualified path, indicating what type of exception occurs for data rollback.

<tx:advice id="t" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 定义只要方法出现了ArithmeticException类型异常及子类型异常都需要进行回滚 -->
        <tx:method name="select*" rollback-for="java.lang.ArithmeticException"></tx:method>
    </tx:attributes>
</tx:advice>

​ Corresponding attribute: no-rollback-for What type of exception does not roll back, just configure one with the rollback-for attribute

propagation attribute

​ Transaction propagation behavior, including:
​ REQUIRED, SUPPORTS, MANDATORY, NEVER
​ REQUIRES_NEW, NOT_SUPPORTED, NESTED

​ We generally use: **REQUIRED:** default value. Use it if there is a transaction, and add a new transaction if there is no transaction.

<tx:advice id="t" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="select*" propagation="REQUIRED"></tx:method>
    </tx:attributes>
</tx:advice>
isolation property

This attribute controls the isolation level of the transaction. The default value is DEFAULT, indicating the isolation level of the database (MySQL8 default transaction isolation level REPEATABLE_READ)

Annotation configuration

When using xml to configure declarative transactions before, one thing was actually done: add transactions to methods, so it is unnecessary to use such complicated xml for such a simple operation, so the configuration can be simplified through annotations.

Configure Transaction Manager

Comment out the configuration of configuration transaction notification and configuration transaction aspect in applicationContext.xml

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">


    <context:property-placeholder location="db.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <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>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan>


    <!--配置声明式事务-->
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务通知-->
    <!--<tx:advice id="t" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="update*" timeout="5" no-rollback-for="java.lang.ArithmeticException" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>-->

    <!--配置事务切面-->
    <!--<aop:config>
        <aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/>
        <aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor>
    </aop:config>-->
</beans>
Add configuration annotation scanning
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">


    <context:property-placeholder location="db.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <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>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan>


    <!--配置声明式事务-->
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务通知-->
    <!--<tx:advice id="t" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="update*" timeout="5" no-rollback-for="java.lang.ArithmeticException" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>-->

    <!--配置事务切面-->
    <!--<aop:config>
        <aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/>
        <aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor>
    </aop:config>-->
    
    <!--加入配置注解扫描-->
    <tx:annotation-driven />
</beans>

Add transaction annotations to methods

On the Service method, add a transaction annotation to the method that needs to add a transaction. If you need to configure the attribute, you can set it in the parameter position

    @Transactional(propagation=Propagation.REQUIRED)
    public int updateAccount(Integer outid,Integer inid,Double money){
    
    
        // 定义状态码:成功1,失败0,初始化状态下 code是0
        int code = 0;
        // 账户A(id为6)给账户B(id为10)转100 :
        // 账户A支出100:
        int outRow = ad.outAccount(outid, money);
        // 制造异常:
        //int a = 1 / 0;
        // 账户B收入100:
        int inRow = ad.inAccount(inid, money);
        // 如果收入和支出两个操作都执行成功的话,表示转账成功:
        if (outRow == 1 && inRow == 1){
    
    
            code = 1;// 转账成功
        }


        // 状态码作为方法的返回值:
        return code;
    }

PS: If many methods require transaction processing, you can directly put this annotation on the class, which means that all methods of this class are controlled by transactions.

y>


<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>


<context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan>


<!--配置声明式事务-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务通知-->
<!--<tx:advice id="t" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="update*" timeout="5" no-rollback-for="java.lang.ArithmeticException" propagation="REQUIRED" isolation="DEFAULT"/>
    </tx:attributes>
</tx:advice>-->

<!--配置事务切面-->
<!--<aop:config>
    <aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/>
    <aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor>
</aop:config>-->

<!--加入配置注解扫描-->
<tx:annotation-driven />

##### 方法上加入事务注解

Service 方法上在需要添加事务的方法上加入事务注解,如需配置属性在参数位置设置即可

```java
    @Transactional(propagation=Propagation.REQUIRED)
    public int updateAccount(Integer outid,Integer inid,Double money){
        // 定义状态码:成功1,失败0,初始化状态下 code是0
        int code = 0;
        // 账户A(id为6)给账户B(id为10)转100 :
        // 账户A支出100:
        int outRow = ad.outAccount(outid, money);
        // 制造异常:
        //int a = 1 / 0;
        // 账户B收入100:
        int inRow = ad.inAccount(inid, money);
        // 如果收入和支出两个操作都执行成功的话,表示转账成功:
        if (outRow == 1 && inRow == 1){
            code = 1;// 转账成功
        }


        // 状态码作为方法的返回值:
        return code;
    }

PS: If many methods require transaction processing, you can directly put this annotation on the class, which means that all methods of this class are controlled by transactions.

Guess you like

Origin blog.csdn.net/qq_27566167/article/details/130405001