目 录CONTENT

文章目录

Spring注解驱动开发之TX(3)

Eric
2022-02-15 / 0 评论 / 0 点赞 / 206 阅读 / 4,934 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-12-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1 Spring对JDBC的封装

1.1 JdbcTemplate

1.1.1 概述

1.1.1.1 基本介绍
  • Spring对数据库的操作在JDBC上面做了基本的封装,让开发者在操作数据库的时候只需要关注SQL语句和查询结果处理器,即可完成功能。

  • 在配合Spring的IOC功能,可以把DataSource注册到JdbcTemplate中,同时利用Spring基于AOP的事务即可完成简单的数据库的CRUD操作。

1.1.1.2 源码
  • JdbcTemplate:
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {

    private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";

    private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";


    /** If this variable is false, we will throw exceptions on SQL warnings. */
    private boolean ignoreWarnings = true;

    /**
     * If this variable is set to a non-negative value, it will be used for setting the
     * fetchSize property on statements used for query processing.
     */
    private int fetchSize = -1;

    /**
     * If this variable is set to a non-negative value, it will be used for setting the
     * maxRows property on statements used for query processing.
     */
    private int maxRows = -1;

    /**
     * If this variable is set to a non-negative value, it will be used for setting the
     * queryTimeout property on statements used for query processing.
     */
    private int queryTimeout = -1;

    /**
     * If this variable is set to true, then all results checking will be bypassed for any
     * callable statement processing. This can be used to avoid a bug in some older Oracle
     * JDBC drivers like 10.1.0.2.
     */
    private boolean skipResultsProcessing = false;

    /**
     * If this variable is set to true then all results from a stored procedure call
     * that don't have a corresponding SqlOutParameter declaration will be bypassed.
     * All other results processing will be take place unless the variable
     * {@code skipResultsProcessing} is set to {@code true}.
     */
    private boolean skipUndeclaredResults = false;

    /**
     * If this variable is set to true then execution of a CallableStatement will return
     * the results in a Map that uses case insensitive names for the parameters.
     */
    private boolean resultsMapCaseInsensitive = false;


    /**
     * Construct a new JdbcTemplate for bean usage.
     * <p>Note: The DataSource has to be set before using the instance.
     * @see #setDataSource
     */
    public JdbcTemplate() {
    }

    /**
     * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
     * <p>Note: This will not trigger initialization of the exception translator.
     * @param dataSource the JDBC DataSource to obtain connections from
     */
    public JdbcTemplate(DataSource dataSource) {
        setDataSource(dataSource);
        afterPropertiesSet();
    }

    /**
     * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
     * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator
     * will be triggered.
     * @param dataSource the JDBC DataSource to obtain connections from
     * @param lazyInit whether to lazily initialize the SQLExceptionTranslator
     */
    public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
        setDataSource(dataSource);
        setLazyInit(lazyInit);
        afterPropertiesSet();
    }
    
    //略
}
1.1.1.3 方法说明
  • JdbcTemplate主要提供以下的方法:

    • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句。
    • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句。
    • query方法及queryForXxx方法:用于执行查询相关语句。
    • call方法:用于执行存储过程、函数相关语句。

1.1.2 入门案例

  • 导入相关jar包的Maven坐标:
<properties>
    <!--    5.2.7.RELEASE    -->
    <spring.version>5.2.7.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.23</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <!--    导入YAML解析工厂坐标    -->
    <dependency>
        <groupId>org.yaml</groupId>
        <artifactId>snakeyaml</artifactId>
        <version>1.26</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>
</dependencies>
  • sql脚本:
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `money` double NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  • 日志文件log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>
  • jdbc.yml
jdbc:
    driver: com.mysql.cj.jdbc.Driver
    password: 123456
    url: jdbc:mysql://192.168.2.112:3306/spring5?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
    user: root
  • 自定义YAMLPropertySourceFactory:
package top.open1024.spring5.factory;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;
import java.util.Properties;

/**
 * 自定义YAMLPropertySourceFactory
 */
public class YAMLPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
        YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();
        bean.setResources(encodedResource.getResource());
        Properties properties = bean.getObject();

        return (name != null ? new PropertiesPropertySource(name, properties) : new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties));
    }
}
  • 编写实体类:
package top.open1024.spring5.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;

    private String name;

    private Double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

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

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  • JdbcConfig.java
package top.open1024.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import top.open1024.spring5.factory.YAMLPropertySourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
@PropertySource(value = "classpath:jdbc.yml", factory = YAMLPropertySourceFactory.class)
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.user}")
    private String user;

    @Value("${jdbc.password}")
    private String password;


    /**
     * 配置数据源
     *
     * @return 数据源
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate
     *
     * @param dataSource 数据源
     * @return JdbcTemplate
     */
    @Bean
    public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
  • SpringConfig.java
package top.open1024.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan(value = "top.open1024.spring5")
@Import(value = JdbcConfig.class)
public class SpringConfig {

}
  • AccountDao.java
package top.open1024.spring5.dao;

import top.open1024.spring5.domain.Account;

import java.util.List;

public interface AccountDao {

    /**
     * 保存账户信息
     *
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 更新账户信息
     *
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 删除账户信息
     *
     * @param id
     */
    void deleteAccount(Integer id);

    /**
     * 根据id查询账户信息
     *
     * @param id
     * @return
     */
    Account findById(Integer id);

    /**
     * 查询全部账户信息
     *
     * @return
     */
    List<Account> findAll();

    /**
     * 查询数量
     *
     * @return
     */
    Long findCount();

}
  • AccountDaoImpl.java
package top.open1024.spring5.dao.impl;

import top.open1024.spring5.dao.AccountDao;
import top.open1024.spring5.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void saveAccount(Account account) {
        jdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (?,?) ", account.getName(), account.getMoney());
    }

    @Override
    public void updateAccount(Account account) {
        jdbcTemplate.update(" UPDATE `account` SET `name` =?,`money` =? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());
    }

    @Override
    public void deleteAccount(Integer id) {
        jdbcTemplate.update(" DELETE FROM `account` WHERE id = ? ", id);
    }

    @Override
    public Account findById(Integer id) {
        return jdbcTemplate.queryForObject("SELECT * FROM `account` WHERE id= ?", new BeanPropertyRowMapper<>(Account.class), id);
    }

    @Override
    public List<Account> findAll() {
        return jdbcTemplate.query("SELECT * FROM `account`",new BeanPropertyRowMapper<>(Account.class));
    }

    @Override
    public Long findCount() {
        return jdbcTemplate.queryForObject(" SELECT count(*) FROM `account` ",new SingleColumnRowMapper<>());
    }
}
  • 测试:
package top.open1024.spring5;

import top.open1024.spring5.config.SpringConfig;
import top.open1024.spring5.dao.AccountDao;
import top.open1024.spring5.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Autowired
    private AccountDao accountDao;

    @Test
    public void testSaveAccount(){
        Account account = new Account();
        account.setName("张三");
        account.setMoney(1.00);

        accountDao.saveAccount(account);
    }

    @Test
    public void testUpdateAccount(){
        Account account = accountDao.findById(1);
        account.setMoney(2.00);

        accountDao.updateAccount(account);
    }

    @Test
    public void testDeleteAccount(){
        Account account = accountDao.findById(1);
        if(null != account){
            accountDao.deleteAccount(1);
        }
    }

    @Test
    public void testFindById(){
        Account account = accountDao.findById(1);
        System.out.println("account = " + account);
    }

    @Test
    public void testFindAll(){
        List<Account> accountList = accountDao.findAll();
        System.out.println("accountList = " + accountList);
    }

    @Test
    public void testFindCount(){
        Long count = accountDao.findCount();
        System.out.println("count = " + count);
    }
    
}

1.2 NamedParameterJdbcTemplate

1.2.1 概述

1.2.1.1 基本介绍
  • 在经典的JDBC用法中,SQL的参数是用占位符?表示的,并且受到位置的限制。定位参数的问题在于,一旦参数的位置发生改变,就必须改变参数绑定。在Spring JDBC框架中,绑定SQL参数的另一种选择是使用具名参数。

  • 具名参数:SQL按名称而不是按照位置进行指定。具名参数更易于维护,也提升了可读性,具名参数由框架类在运行时用占位符取代。

1.2.1.2 源码
  • NamedParameterJdbcTemplate:
public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {

    /** Default maximum number of entries for this template's SQL cache: 256. */
    public static final int DEFAULT_CACHE_LIMIT = 256;


    /** The JdbcTemplate we are wrapping. */
    private final JdbcOperations classicJdbcTemplate;

    private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;

    /** Cache of original SQL String to ParsedSql representation. */
    @SuppressWarnings("serial")
    private final Map<String, ParsedSql> parsedSqlCache =
            new LinkedHashMap<String, ParsedSql>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
                @Override
                protected boolean removeEldestEntry(Map.Entry<String, ParsedSql> eldest) {
                    return size() > getCacheLimit();
                }
            };


    /**
     * Create a new NamedParameterJdbcTemplate for the given {@link DataSource}.
     * <p>Creates a classic Spring {@link org.springframework.jdbc.core.JdbcTemplate} and wraps it.
     * @param dataSource the JDBC DataSource to access
     */
    public NamedParameterJdbcTemplate(DataSource dataSource) {
        Assert.notNull(dataSource, "DataSource must not be null");
        this.classicJdbcTemplate = new JdbcTemplate(dataSource);
    }
    //略   
}

1.2.2 入门案例

  • JdbcConfig.java
package top.open1024.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import top.open1024.spring5.factory.YAMLPropertySourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import javax.sql.DataSource;

@Configuration
@PropertySource(value = "classpath:jdbc.yml", factory = YAMLPropertySourceFactory.class)
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.user}")
    private String user;

    @Value("${jdbc.password}")
    private String password;


    /**
     * 配置数据源
     *
     * @return 数据源
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate
     *
     * @param dataSource 数据源
     * @return JdbcTemplate
     */
    @Bean
    public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }


    /**
     * 配置NamedParameterJdbcTemplate
     *
     * @param jdbcTemplate
     * @return
     */
    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
        return new NamedParameterJdbcTemplate(jdbcTemplate);
    }
}
  • SpringConfig.java
package top.open1024.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan(value = "top.open1024.spring5")
@Import(value = JdbcConfig.class)
public class SpringConfig {

}
  • 测试:
package top.open1024.spring5;

import top.open1024.spring5.config.SpringConfig;
import top.open1024.spring5.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.HashMap;
import java.util.Map;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    @Test
    public void testSaveAccount() {
        Account account = new Account();
        account.setName("李四");
        account.setMoney(1d);

        BeanMap beanMap = BeanMap.create(account);

        namedParameterJdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (:name,:money) ",beanMap);
    }

    @Test
    public void testFindById(){
        Map<String,Object> map = new HashMap<>();
        map.put("id",1);
        Account account = namedParameterJdbcTemplate.queryForObject(" SELECT * FROM `account` WHERE id = :id ", map, new BeanPropertyRowMapper<>(Account.class));
        System.out.println("account = " + account);
    }
}

2 Spring中的事务

2.1 API介绍

2.1.1 PlatformTransactionManager和其实现类

2.1.1.1 作用
  • 此接口是Spring的事务管理器的核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。在Spring框架中,也为我们内置了一些具体策略,例如:DatasourceTransactionManager、HibernateTransactionManager、jpaTransactionManager等。
2.1.1.2 类图

PlatformTransactionManager和其实现类.png

2.1.1.3 方法说明
  • PlatformTransactionManager:
public interface PlatformTransactionManager extends TransactionManager {

    /**
     * 获取事务状态信息
     */
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;

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

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

}

2.1.2 TransactionDefinition

2.1.2.1 作用
  • 此接口是Spring中事务可控属性的顶层接口,里面定义了事务的一些属性以及获取属性的方法。例如:事务的传播行为、事务的隔离级别、事务的只读、事务的超时等待。通常情况下,我们在开发洪都可以配置这些属性,以求达到最佳效果。
2.1.2.2 类图

TransactionDefinition.png

2.1.2.3 定义信息说明
  • TransactionDefinition:
public interface TransactionDefinition {

    /**
     * REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
     */
    int PROPAGATION_REQUIRED = 0;

    /**
     * SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
     */
    int PROPAGATION_SUPPORTS = 1;

    /**
     * MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
     */
    int PROPAGATION_MANDATORY = 2;

    /**
     * REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
     */
    int PROPAGATION_REQUIRES_NEW = 3;

    /**
     * NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
     */
    int PROPAGATION_NOT_SUPPORTED = 4;

    /**
     * NEVER:以非事务方式运行,如果当前存在事务,抛出异常
     */
    int PROPAGATION_NEVER = 5;

    /**
     * NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
     */
    int PROPAGATION_NESTED = 6;

    /**
     * 事务的隔离级别默认值,当取值-1时,会采用下面的4个值其中一个。
     * 事务的隔离级别默认值,当取值-1时,会采用下面的4个值其中一个。
     */
    int ISOLATION_DEFAULT = -1;

    /**
     * 事务隔离级别为:读未提交
     * 事务隔离级别为:读未提交
     */
    int ISOLATION_READ_UNCOMMITTED = 1;  

    /**
     * 事务隔离级别为:读已提交
     * 可以防止脏读的发生,但是无法防住不可重复读和幻读的发生
     */
    int ISOLATION_READ_COMMITTED = 2;  

    /**
     * 事务隔离级别为:可重复读
     * 可以防止脏读和不可重复读的发生,但是无法防住幻读的发生
     */
    int ISOLATION_REPEATABLE_READ = 4;  

    /**
     * 事务隔离级别为:串行化
     * 此时所有错误情况均可防住,但是由于事务变成了独占模式(排他模式),因此效率最低
     */
    int ISOLATION_SERIALIZABLE = 8;  


    /**
     * 超时限制。默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
     */
    int TIMEOUT_DEFAULT = -1;


    /**
     * 获取事务传播行为
     */
    default int getPropagationBehavior() {
        return PROPAGATION_REQUIRED;
    }

    /**
     * 获取事务隔离级别
     */
    default int getIsolationLevel() {
        return ISOLATION_DEFAULT;
    }

    /**
     * 获取事务超时时间
     */
    default int getTimeout() {
        return TIMEOUT_DEFAULT;
    }

    /**
     * 获取事务是否只读
     */
    default boolean isReadOnly() {
        return false;
    }

    /**
     * 获取事务名称
     */
    @Nullable
    default String getName() {
        return null;
    }


    // Static builder methods

    /**
     * Return an unmodifiable {@code TransactionDefinition} with defaults.
     * <p>For customization purposes, use the modifiable
     * {@link org.springframework.transaction.support.DefaultTransactionDefinition}
     * instead.
     * @since 5.2
     */
    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }

}

2.1.3 TransactionStatus

2.1.3.1 作用
  • 此接口是事务运行状态表示的顶层接口,里面定义着获取事务运行状态的一些方法。
2.1.3.2 类图

TransactionStatus.png

2.1.3.3 方法说明
  • TransactionStatus:
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

    /**
     * 是否包含存储点
     */
    boolean hasSavepoint();

    /**
     * 刷新事务
     */
    @Override
    void flush();

}

2.2 入门案例

  • JdbcConfig.java
package top.open1024.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import top.open1024.spring5.factory.YAMLPropertySourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;

import javax.sql.DataSource;

@Configuration
@PropertySource(value = "classpath:jdbc.yml", factory = YAMLPropertySourceFactory.class)
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.user}")
    private String user;

    @Value("${jdbc.password}")
    private String password;


    /**
     * 配置数据源
     *
     * @return 数据源
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 配置JdbcTemplate
     *
     * @param dataSource 数据源
     * @return JdbcTemplate
     */
    @Bean
    public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }


    /**
     * 配置NamedParameterJdbcTemplate
     *
     * @param jdbcTemplate
     * @return
     */
    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
        return new NamedParameterJdbcTemplate(jdbcTemplate);
    }


    /**
     * 配置事务管理器
     *
     * @param dataSource
     * @return
     */
    @Bean
    public TransactionManager transactionManager(@Autowired DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
  • SpringConfig.java
package top.open1024.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan(value = "top.open1024.spring5")
@Import(value = JdbcConfig.class)
@EnableTransactionManagement
public class SpringConfig {

}
  • AccountDao.java
package top.open1024.spring5.dao;

import top.open1024.spring5.domain.Account;

import java.util.List;

public interface AccountDao {

    /**
     * 保存账户信息
     *
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 更新账户信息
     *
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 删除账户信息
     *
     * @param id
     */
    void deleteAccount(Integer id);

    /**
     * 根据id查询账户信息
     *
     * @param id
     * @return
     */
    Account findById(Integer id);

    /**
     * 查询全部账户信息
     *
     * @return
     */
    List<Account> findAll();

    /**
     * 查询数量
     *
     * @return
     */
    Long findCount();

    /**
     * 根据名称查询账户信息
     *
     * @param name
     * @return
     */
    Account findByName(String name);


}
  • AccountDaoImpl.java
package top.open1024.spring5.dao.impl;

import top.open1024.spring5.dao.AccountDao;
import top.open1024.spring5.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void saveAccount(Account account) {
        jdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (?,?) ", account.getName(), account.getMoney());
    }

    @Override
    public void updateAccount(Account account) {
        jdbcTemplate.update(" UPDATE `account` SET `name` =?,`money` =? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());
    }

    @Override
    public void deleteAccount(Integer id) {
        jdbcTemplate.update(" DELETE FROM `account` WHERE id = ? ", id);
    }

    @Override
    public Account findById(Integer id) {
        return jdbcTemplate.queryForObject("SELECT * FROM `account` WHERE id= ?", new BeanPropertyRowMapper<>(Account.class), id);
    }

    @Override
    public List<Account> findAll() {
        return jdbcTemplate.query("SELECT * FROM `account`", new BeanPropertyRowMapper<>(Account.class));
    }

    @Override
    public Long findCount() {
        return jdbcTemplate.queryForObject(" SELECT count(*) FROM `account` ", new SingleColumnRowMapper<>());
    }

    @Override
    public Account findByName(String name) {
        if (StringUtils.isEmpty(name)) {
            return null;
        }

        List<Account> accountList = jdbcTemplate.query("SELECT * FROM `account` WHERE `name` =?", new BeanPropertyRowMapper<>(Account.class), name);

        if (CollectionUtils.isEmpty(accountList)) {
            return null;
        }

        if (accountList.size() > 1) {
            throw new RuntimeException(name + "在数据库中不止一个");
        }

        return accountList.get(0);
    }

}
  • AccountService.java
package top.open1024.spring5.service;

public interface AccountService {

    /**
     * 转账
     *
     * @param sourceName 转出人
     * @param targetName 转入人
     * @param money      金额
     */
    void transfer(String sourceName, String targetName, Double money);

}
  • AccountServiceImpl.java
package top.open1024.spring5.service.impl;

import top.open1024.spring5.dao.AccountDao;
import top.open1024.spring5.domain.Account;
import top.open1024.spring5.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

@Service
@Transactional
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String sourceName, String targetName, Double money) {
        Account source = accountDao.findByName(sourceName);

        Account target = accountDao.findByName(targetName);

        if (!ObjectUtils.isEmpty(source) && !ObjectUtils.isEmpty(target)) {
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);

            accountDao.updateAccount(source);

            //模拟异常
            int num = 10 / 0;

            accountDao.updateAccount(target);
        }
    }
}
  • 测试:
package top.open1024.spring5;

import top.open1024.spring5.config.SpringConfig;
import top.open1024.spring5.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Spring5Test {

    @Autowired
    private AccountService accountService;

    @Test
    public void test() {
        accountService.transfer("张三","李四",100d);
    }
    
}
0

评论区