Loading

Spring注解驱动开发之TX

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 com.sunxiaping.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 com.sunxiaping.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 com.sunxiaping.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sunxiaping.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 com.sunxiaping.spring5.config;

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

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

}
  • AccountDao.java
package com.sunxiaping.spring5.dao;

import com.sunxiaping.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 com.sunxiaping.spring5.dao.impl;

import com.sunxiaping.spring5.dao.AccountDao;
import com.sunxiaping.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 com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import com.sunxiaping.spring5.dao.AccountDao;
import com.sunxiaping.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 com.sunxiaping.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sunxiaping.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 com.sunxiaping.spring5.config;

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

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

}
  • 测试:
package com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import com.sunxiaping.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

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

2.1.3.3 方法说明

  • TransactionStatus:
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

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

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

}

2.2 入门案例

  • JdbcConfig.java
package com.sunxiaping.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sunxiaping.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 com.sunxiaping.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 = "com.sunxiaping.spring5")
@Import(value = JdbcConfig.class)
@EnableTransactionManagement
public class SpringConfig {

}
  • AccountDao.java
package com.sunxiaping.spring5.dao;

import com.sunxiaping.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 com.sunxiaping.spring5.dao.impl;

import com.sunxiaping.spring5.dao.AccountDao;
import com.sunxiaping.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 com.sunxiaping.spring5.service;

public interface AccountService {

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

}
  • AccountServiceImpl.java
package com.sunxiaping.spring5.service.impl;

import com.sunxiaping.spring5.dao.AccountDao;
import com.sunxiaping.spring5.domain.Account;
import com.sunxiaping.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 com.sunxiaping.spring5;

import com.sunxiaping.spring5.config.SpringConfig;
import com.sunxiaping.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);
    }
    
}
posted @ 2020-09-13 09:18  许大仙  阅读(368)  评论(0编辑  收藏  举报