Изучение исходного кода Spring (9) Транзакционная транзакция

Spring

Принцип использования и реализации транзакции Spring Transaction


предисловие

Данные бизнес-системы обычно попадают в базу данных, напримерMySQL,OracleВ других основных базах данных при обновлении данных неизбежно могут возникать ошибки.В настоящее время необходимо отменить предыдущую операцию обновления данных, чтобы избежать ошибочных данных.

SpringДекларативная транзакция может помочь нам справиться с операциями отката, чтобы нам не нужно было обращать внимание на базовые транзакционные операции базы данных, и нам не нужно было писать операции отката в try/catch/finaly в случае возникновения ошибки. исключение.

Springимеет более высокий уровень гарантии транзакций, чем другие технологии в отрасли (например,TCC / 2PC / 3PCи т. д.) немного слабее, но используютSpringТранзакции уже удовлетворяют большинству сценариев, поэтому стоит изучить правила их использования и настройки.

Давайте учиться вместеSpringКак используются транзакции и как они реализуются.


Пример использования

1. Создайте таблицу базы данных

create table test.user(
id int auto_increment	
primary key,
name varchar(20) null, age int(3) null)
engine=InnoDB charset=utf8;

2. Создайте заказ на покупку, соответствующий таблице базы данных.

public class JdbcUser {

	private Integer id;

	private String name;

	private Integer age;
	
	...(使用 ctrl + N 进行代码补全 setter 和 getter)
}

3. Создайте сопоставление между таблицами и сущностями

В использованииJdbcTemplateочень запутанный, вJavaВ классе написано много хардкодингаSQL,а такжеMyBatisСпособ использования разный, для простоты примера используемJdbcTemplate, но все равно рекомендую друзьям пользоватьсяMyBatis, чтобы стиль кода оставался чистым.

public class UserRowMapper implements RowMapper {


	@Override
	public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
		JdbcUser user = new JdbcUser();
		user.setId(rs.getInt("id"));
		user.setName(rs.getString("name"));
		user.setAge(rs.getInt("age"));
		return user;
	}
}

4. Создайте интерфейс для обработки данных

public interface UserDao {

	/**
	 * 插入
	 * @param user	用户信息
	 */
	void insertUser(JdbcUser user);

	/**
	 * 根据 id 进行删除
	 * @param id	主键
	 */
	void deleteById(Integer id);

	/**
	 * 查询
	 * @return	全部
	 */
	List<JdbcUser> selectAll();
}

5. Создайте класс реализации интерфейса операций с данными

В отличие от примеров в книге, аннотации транзакций не добавляются в интерфейс, а добавляются в общедоступные методы.Вы можете настроить событие распространения и уровень изоляции для каждого метода.

public class UserJdbcTemplate implements UserDao {

	private DataSource dataSource;

	private JdbcTemplate jdbcTemplate;


	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void insertUser(JdbcUser user) {
		String sql = "insert into user (id, name, age) values (?, ?, ?)";
		jdbcTemplate.update(sql, user.getId(), user.getName(), user.getAge());
		System.out.println("Create record : " + user.toString());
	}

	@Override
	@Transactional
	public void deleteById(Integer id) {
		String sql = "delete from user where id = ?";
		jdbcTemplate.update(sql, id);
		System.out.println("Delete record, id = " + id);
		// 事务测试,抛出异常,让上面的插入操作回滚
		throw new RuntimeException("aa");
	}


	@Override
	public List<JdbcUser> selectAll() {
		String sql = "select * from user";
		List<JdbcUser> users = jdbcTemplate.query(sql, new UserRowMapper());
		return users;
	}



	public void setDataSource(DataSource dataSource) {
		// 使用 setter 注入参数时,同时初始化 jdbcTemplate
		this.dataSource = dataSource;
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

}

6. Создайте файл конфигурации

<?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:tx="http://www.springframework.org/schema/tx"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/tx
	   http://www.springframework.org/schema/tx/spring-tx.xsd">

	<!-- 数据源 MySQL -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8"/>
		<property name="username" value="root"/>
		<property name="password" value="12345678"/>
	</bean>

	<bean id="userJdbcTemplate" class="transaction.UserJdbcTemplate">
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<!-- 开启事务,如果将这行去掉,将不会创建事务  -->
	<tx:annotation-driven/>
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
</beans>

7. Добавьте зависимости

Не забудьте добавить соединение с базой данных иjdbc,txэти двоеspringзависимости модуля

optional(project(":spring-jdbc"))  // for Quartz support
optional(project(":spring-tx"))  // for Quartz support
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.6'

8. Код запуска

public class TransactionBootstrap {

	public static void main(String[] args) {
			ApplicationContext context = new ClassPathXmlApplicationContext("transaction/transaction.xml");
			UserJdbcTemplate jdbcTemplate = (UserJdbcTemplate) context.getBean("userJdbcTemplate");
			System.out.println("--- Records Creation start ----");
			JdbcUser user = new JdbcUser(4, "test", 21);
			jdbcTemplate.insertUser(user);
	}
}

С приведенным выше кодом я провел два теста:

  1. В конфигурационном файле транзакция не открывается.то есть<tx:annotation-driven/>Эта строка закомментирована, хотя метод, который мы выполняем, выбрасываетRuntimeExcepton, но данные все еще вставляются в базу данных.
  2. В файле конфигурации запустите транзакцию.Удалите приведенный выше комментарий, удалите записи в базе данных, повторно выполните код запуска и обнаружите, что данные не были вставлены.Когда программа выдает исключение,SpringТранзакция выполнена успешно, операция вставки отменена.

Аннотировать свойство @Transactional

Конкретное место:org.springframework.transaction.annotation.Transactional

Атрибуты Типы эффект
value String Необязательный уточняющий дескриптор, указывающий используемый менеджер транзакций.
propagation Перечисление: Распространение Необязательное поведение распространения транзакции
isolation Перечисление: Изоляция Необязательный параметр уровня изоляции транзакций
readOnly boolean Установите транзакцию для чтения-записи или только для чтения, по умолчанию только для чтения
rollbackFor Массив классов, который должен наследоваться от Throwable Массив классов исключений, вызвавших откат транзакции
rollbackForClassName Массив имен классов, должен наследоваться от Throwable
noRollbackFor Массив классов, который должен наследоваться от Throwable Массив классов исключений, которые не приведут к откату транзакции
noRollbackForClassName Массив имен классов, должен наследоваться от Throwable

Распространение транзакций

  • REQUIRED

Это свойство распространения по умолчанию.Если у внешнего вызывающего объекта есть транзакция, он присоединится к транзакции, если нет, создаст новую.

  • PROPAGATION_SUPPORTS

Если в данный момент есть транзакция, присоединитесь к ней; если текущей транзакции нет, продолжите работу в нетранзакционном режиме.

  • PROPAGATION_NOT_SUPPORTED

Запустите в нетранзакционном режиме, приостановите текущую транзакцию, если текущая транзакция есть.

  • PROPAGATION_NEVER

Работает без транзакций и выдает исключение, если транзакция уже существует.


Изоляция транзакций

  • READ_UNCOMMITTED

Самый низкий уровень, только гарантированно не читать Физически поврежденные данные, допускающие грязное чтение

  • READ_COMMITTED

Только читать данные, которые были отправлены

  • REPEATABLE_READ

повторяемое чтение

  • SERIALIZABLE

Сериализованное чтение, чтение и запись блокируют друг друга

Вот лишь краткое описание этих двух основных атрибутов, потому что нижний слой связан с базой данных, вы можете видеть то, что я разобрал до этого Механизм блокировки MySQL


Реализация логики в Spring

После ознакомления с тем, как его использовать и установки ключевых атрибутов, давайте разберемся, как реализуется код в духе знания того, что это такое и что это такое.


Разобрать

Как упоминалось ранее при анализе пользовательских тегов,AOPа такжеTXоба используют пользовательские теги,Следите за нашей предыдущей статьейAOPДля обучения давайте рассмотрим процедуру анализа пользовательских тегов: пользовательские теги транзакций.

ТаргетингTxNamespaceHandlerМетод инициализации класса:

@Override
public void init() {
	registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
	// 使用 AnnotationDrivenBeanDefinitionParser 解析器,解析 annotation-driven 标签
	registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
	registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}

Согласно вышеуказанному методу,SpringВо время инициализации, если возникают такие ситуации, как<tx:annotation-driven>После настройки в начале будет использоватьсяAnnotationDrivenBeanDefinitionParserпарсерparseметод разбора.

public BeanDefinition parse(Element element, ParserContext parserContext) {
	registerTransactionalEventListenerFactory(parserContext);
	String mode = element.getAttribute("mode");
	// AspectJ 另外处理
	if ("aspectj".equals(mode)) {
		// mode="aspectj"
		registerTransactionAspect(element, parserContext);
		if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) {
			registerJtaTransactionAspect(element, parserContext);
		}
	}
	else {
		// mode="proxy"
		AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
	}
	return null;
}

SpringТранзакции по умолчаниюAOPв качестве основы, при необходимости используйтеAspectJспособ выполнить ввод транзакции, вам необходимоmodeКонфигурация в свойствах:

<tx:annotation-driven mode="aspectj"/>

Это примечание в основном вращается вокруг реализации по умолчанию, динамическойAOPучиться, если дляAspectJЕсли вы заинтересованы, пожалуйста, прочитайте больше информации~


Регистрация InfrastructureAdvisorAutoProxyCreator

а такжеAOPАналогично при парсинге будет создаваться автоматически созданный прокси, а в транзакцииTXмодуль, используяInfrastructureAdvisorAutoProxyCreator.

Прежде всего, в конфигурации по умолчанию,AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext)Что сделал:

private static class AopAutoProxyConfigurer {
	public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
		// 注册 InfrastructureAdvisorAutoProxyCreator 自动创建代理器
		AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
		// txAdvisorBeanName = org.springframework.transaction.config.internalTransactionAdvisor
		String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
		if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
			Object eleSource = parserContext.extractSource(element);
			// Create the TransactionAttributeSource definition.
			// 创建 TransactionAttributeSource 的 bean
			RootBeanDefinition sourceDef = new RootBeanDefinition(
					"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
			// 注册 bean,并使用 Spring 中的定义规则生成 beanName
			String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
			// 创建 TransactionInterceptor 的 bean
			RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
			interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
			String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
			// 创建 TransactionAttributeSourceAdvisor 的 bean
			RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
			// 将 sourceName 的 bean 注入 advisor 的 transactionAttributeSource 属性中
			advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
			// 将 interceptorName 的 bean 注入到 advisor 的 adviceBeanName 属性中
			advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
			if (element.hasAttribute("order")) {
				// 如果配置了 order 属性,则加入到 bean 中
				advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
			}
			// 以 txAdvisorBeanName 名字注册 advisorDef
			parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
			// 创建 CompositeComponentDefinition
			CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
			compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
			compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
			compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
			parserContext.registerComponent(compositeDef);
		}
	}
}

Класс прокси и три прописаны здесьbean, три ключаbeanОн поддерживает всю функцию транзакций. Чтобы лучше понять взаимосвязь между тремя позже, давайте сначала рассмотрим ее.AOPОсновные понятия:

  1. PointcutОпределите pointcut, который можно использовать для выполнения логики аспекта до и после перехваченного метода.
  2. AdviceИспользуется для определения поведения перехвата, где реализована расширенная логика, это интерфейс-предок.org.aopalliance.aop.Advice. Существуют и другие унаследованные интерфейсы, такие какMethodBeforeAdvice, что конкретно относится к улучшениям перед выполнением метода.
  3. AdvisorОн используется для инкапсуляции всей информации аспекта, в основном двух вышеупомянутых, которые используются в качествеAdviceа такжеPointcutадаптер.

advisor_consist

После просмотраAOPПосле концепции продолжайте смотреть на эти три ключевыхbean:

  • TransactionInterceptor: ДостигнутоAdviceИнтерфейс, в котором определяется поведение перехвата.
  • AnnotationTransactionAttributeSource: инкапсулирует логику перехвата целевого метода, хотя он и не реализованPointcutинтерфейса, но когда целевой метод оценивается позже, он фактически делегируетсяAnnotationTransactionAttributeSource.getTransactionAttributeSource, через режим адаптера возвращаетPointcutИнформация о точке среза.
  • TransactionAttributeSourceAdvisor: ДостигнутоAdvisorИнтерфейс упаковывает две вышеуказанные информации.

Эти триbeanсостоит из конструкции сAOPСтруктура реализации фасетного объемного звучания такая же, поэтому сначала изучитеAOPвнедрение, понимание транзакций было бы полезно


Давайте посмотрим, как создается наш автоматически созданный прокси:

AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element)

public static void registerAutoProxyCreatorIfNecessary(
		ParserContext parserContext, Element sourceElement) {
	BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
			parserContext.getRegistry(), parserContext.extractSource(sourceElement));
	useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
	registerComponentIfNecessary(beanDefinition, parserContext);
}

private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
	if (beanDefinition != null) {
	    // 注册的 beanName 是 org.springframework.aop.config.internalAutoProxyCreator
		parserContext.registerComponent(
				new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
	}
}

На этом шаге зарегистрируйтеbeanNameдаorg.springframework.aop.config.internalAutoProxyCreatorизbean:InfrastructureAdsivorAutoProxyCreator, на следующем рисунке показана схема его системы наследования:

infrastructrue_advisor_auto_proxy_creator_diagram

Как видите, достигаетсяInstantiationAwareBeanPostProcessorЭтот интерфейс, т. е. вSpringконтейнер, всеbeanПри создании экземпляраSpringгарантирует вызовpostProcessAfterInitializationметод.

с предыдущим введениемAOPКак и агент, при создании экземпляраbeanКогда вызывается прокси-родительский классAbstractAutoProxyCreatorизpostProcessAfterInitializationметод:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		// 组装 key
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			// 如果适合被代理,则需要封装指定的 bean
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

из них оwrapIfNecessoryспособ, в предыдущей статьеAOPЭто было подробно описано в , вот что делает этот метод:

  1. выяснитьbeanСоответствующий бустер
  2. Создать агента на основе найденного бустера

и создатьAOPПодобный процесс агента повторяться не будет, а отличия будут описаны ниже:


Определите, подходит ли целевой метод для canApply

AopUtils#canApply(Advisor, Class<?>, boolean)

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
	if (advisor instanceof IntroductionAdvisor) {
		return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
	}
	else if (advisor instanceof PointcutAdvisor) {
		PointcutAdvisor pca = (PointcutAdvisor) advisor;
		return canApply(pca.getPointcut(), targetClass, hasIntroductions);
	}
	else {
		// It doesn't have a pointcut so we assume it applies.
		return true;
	}
}

Мы видели раньше,TransactionAttributeSourceAdvisorРодительский классPointcutAdvisor, поэтому при оценке целевого метода информация о точечном разрезе будет удаленаpca.getPointcut().

Тип аспекта, который мы ввели ранееbeanдаAnnotationTransactionAttributeSource, обернутый следующим методом, окончательный тип возвращаемого объекта:TransactionAttributeSourcePointcutинформация о точечном сокращении

private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
	@Override
	@Nullable
	protected TransactionAttributeSource getTransactionAttributeSource() {
		// 实现父类的方法,在子类中进行了扩展,返回之前在标签注册时的AnnotationTransactionAttributeSource
		return transactionAttributeSource;
	}
};

тег совпадения

в соответствииmatchВ эксплуатации разницаAOPидентифицировано@Before,@After, а наши делаTXидентифицировано@TransactionalЭтикетка.

Метод входа, чтобы определить, является ли это методом транзакции, находится здесь:

org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut#matches

@Override
public boolean matches(Method method, Class<?> targetClass) {
	// 事务切点匹配的方法
	TransactionAttributeSource tas = getTransactionAttributeSource();
	return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

Итак, на каком этапе он анализирует аннотацию транзакции, продолжает отслеживать код, ответ таков:

AnnotationTransactionAttributeSource#determineTransactionAttribute

protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
	for (TransactionAnnotationParser parser : this.annotationParsers) {
		TransactionAttribute attr = parser.parseTransactionAnnotation(element);
		if (attr != null) {
			return attr;
		}
	}
	return null;
}

На этом шаге пройдите зарегистрированный синтаксический анализатор аннотаций для синтаксического анализа.Поскольку нас интересует синтаксический анализ транзакции, мы непосредственно находим синтаксический анализатор аннотации транзакции:

SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotatedElement)

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
	// 解析事务注解的属性
	AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
			element, Transactional.class, false, false);
	if (attributes != null) {
		return parseTransactionAnnotation(attributes);
	}
	else {
		return null;
	}
}

Сначала определите, содержит ли он@TransactionalАннотация, если есть, продолжайте звонитьparseМетод разбора:

protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
	RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
	// 注释 9.4 解析事务注解的每一个属性
	Propagation propagation = attributes.getEnum("propagation");
	rbta.setPropagationBehavior(propagation.value());
	Isolation isolation = attributes.getEnum("isolation");
	rbta.setIsolationLevel(isolation.value());
	rbta.setTimeout(attributes.getNumber("timeout").intValue());
	rbta.setReadOnly(attributes.getBoolean("readOnly"));
	rbta.setQualifier(attributes.getString("value"));
	List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
	for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
		rollbackRules.add(new RollbackRuleAttribute(rbRule));
	}
	for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
		rollbackRules.add(new RollbackRuleAttribute(rbRule));
	}
	for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
		rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
	}
	for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
		rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
	}
	rbta.setRollbackRules(rollbackRules);
	return rbta;
}

резюме

С помощью вышеуказанных шагов завершается анализ атрибутов транзакции соответствующего класса или метода.

Основные шаги заключаются в том, чтобы найти энхансеры и определить, соответствуют ли эти энхансеры методам или классам.

еслиbeanКогда его можно усилить транзакциями, то есть он подходит для энхансеровBeanFactoryTransactionAttributeSourceAdvisorулучшить.

Раньше мы вводилиTransactionInterceptorприбытьBeanFactoryTransactionAttributeSourceAdvisor, поэтому при вызове прокси-класса, усиленного усилителем транзакций, он будет выполнятьсяTransactionInterceptorулучшить. В то же время, то естьTransactionInterceptorв классеinvokeЛогика завершения всей транзакции в методе.


бегать


Усилитель транзакций TransactionInterceptor

TransactionInterceptorАрхитектура, которая лежит в основе всей функциональности транзакций. доAOPизJDKТо же, что и динамический прокси-анализ,TransactionInterceptorПерехватчик наследуется отMethodInterceptor, поэтому мы начнем с его ключевого методаinvoke()Это выглядит как:

public Object invoke(MethodInvocation invocation) throws Throwable {
	// 注释 9.5 执行事务拦截器,完成整个事务的逻辑
	Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
	// Adapt to TransactionAspectSupport's invokeWithinTransaction...
	return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

На самом деле вызывается метод родительского класса:TransactionAspectSupport#invokeWithinTransaction

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
		final InvocationCallback invocation) throws Throwable {
	// 如果transaction属性为null,则该方法是非事务性的
	TransactionAttributeSource tas = getTransactionAttributeSource();
	// 获取对应事务属性
	final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
	// 获取事务管理器
	final PlatformTransactionManager tm = determineTransactionManager(txAttr);
	// 构造方法唯一标识(类.方法)
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
		// 声明式事务处理
		// 标准事务划分 : 使用 getTransaction 和 commit / rollback 调用
		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
		Object retVal;
		try {
			//传入的是回调函数对象: invocation.proceed。 执行被增强的方法
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// 异常回滚
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
			// 清除信息
			cleanupTransactionInfo(txInfo);
		}
		// 提交事务
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}
	else {
		// 编程式事务处理
		final ThrowableHolder throwableHolder = new ThrowableHolder();
		// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
		try {
			Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
				TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
			...
			return result;
		}
	}
}

Размещенный код был удален, а исключение ошибок было упрощено.try / catchи программная логика обработки транзакций.Поскольку мы используем более декларативную обработку транзакций, т.XMLконфигурация файла или@TransactionalКодировка аннотации, фактически переданнаяAOPреализуется, а программные транзакцииTransaction TemplateРеализация используется редко, поэтому эта часть кода обработки опущена.


менеджер транзакций

Этот метод определяет конкретного диспетчера транзакций, который будет использоваться для данной транзакции.

TransactionAspectSupport#determineTransactionManager

protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
	// Do not attempt to lookup tx manager if no tx attributes are set
	// 注释 9.6 寻找事务管理器
	if (txAttr == null || this.beanFactory == null) {
		// 如果没有事务属性或者 BeanFactory 为空时,从缓存里面寻找
		return asPlatformTransactionManager(getTransactionManager());
	}

	String qualifier = txAttr.getQualifier();
	// 如果注解配置中指定了事务管理器,直接取出使用
	if (StringUtils.hasText(qualifier)) {
		return determineQualifiedTransactionManager(this.beanFactory, qualifier);
	}
	else if (StringUtils.hasText(this.transactionManagerBeanName)) {
		return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
	}
	else {
		// 上面步骤都没找到,最后才去容器中,根据 className 来寻找
		PlatformTransactionManager defaultTransactionManager = asPlatformTransactionManager(getTransactionManager());
		...
		return defaultTransactionManager;
	}
}

Поскольку мы изначальноXMLнастроено в файлеtransactionManagerсвойство, поэтому метод в нашем случае будет возвращать типDataSourceTransactionManagerдиспетчер транзакций, следующееDataSourceTransactionManagerсистема наследования:

datasource_transaction_manager

он достигаетInitializingBeanинтерфейс, но только вafterPropertiesSet()метод, простая проверкаdataSourceЯвляется ли он пустым, не уточняйте этот класс.


транзакция открыта

TransactionAspectSupport#createTransactionIfNecessary

protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
	// 如果没有名称指定则使用方法唯一标识,并使用 DelegatingTransactionAttribute 包装 txAttr
	if (txAttr != null && txAttr.getName() == null) {
		txAttr = new DelegatingTransactionAttribute(txAttr) {
			@Override
			public String getName() {
				return joinpointIdentification;
			}
		};
	}

	TransactionStatus status = null;
	if (txAttr != null) {
		if (tm != null) {
			// 获取 TransactionStatus
			status = tm.getTransaction(txAttr);
		}
	}
	// 根据指定的属性与 status 准备一个 TransactionInfo
	return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

В методе создания транзакции в основном выполняются следующие три вещи:

  1. использоватьDelegatingTransactionAttributeУпаковкаtxAttrпример
  2. Получить транзакцию:tm.getTransaction(txAttr)
  3. Информация о транзакции сборки:prepareTransactionInfo(tm, txAttr, joinpointIdentification, status)

Метод ядра находится во втором и третьем пунктах соответственно извлечения ядра для ознакомления.


Получить статус транзакции

status = tm.getTransaction(txAttr);

Поскольку код длинный, давайте сразу суммируем некоторые ключевые моменты.

получить транзакцию

Создайте соответствующий экземпляр транзакции, который мы используемDataSourceTransactionManagerсерединаdoGetTransactionметод создания на основеJDBCэкземпляр транзакции.

protected Object doGetTransaction() {
	DataSourceTransactionObject txObject = new DataSourceTransactionObject();
	txObject.setSavepointAllowed(isNestedTransactionAllowed());
	// 如果当前线程已经记录数据库链接则使用原有链接
	ConnectionHolder conHolder =
			(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
	// false 表示非新创建连接
	txObject.setConnectionHolder(conHolder, false);
	return txObject;
}

Среди них, в том же потоке, чтобы определить, есть ли повторяющиеся транзакции, есть вTransactionSynchronizationManager.getResource(obtainDataSource())Логика ключевого суждения выглядит следующим образом:

private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");
			
private static Object doGetResource(Object actualKey) {
	Map<Object, Object> map = resources.get();
	if (map == null) {
		return null;
	}
	Object value = map.get(actualKey);
	// Transparently remove ResourceHolder that was marked as void...
	if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
		map.remove(actualKey);
		// Remove entire ThreadLocal if empty...
		if (map.isEmpty()) {
			resources.remove();
		}
		value = null;
	}
	return value;
}

В заключение:resourcesЯвляетсяThreadLocalЧастные объекты потока хранятся независимо для каждого потока, поэтому наличие транзакции оценивается на основе наличия активной транзакции в текущем потоке и текущего источника данных (DataSource).map.get(actualKey).


Обработать существующую транзакцию

В соответствии с вышеизложенным, чтобы судить о том, есть ли транзакция в текущем потоке, суждение основывается на том факте, что соединение, записанное текущим потоком, не является пустым, и соединение в connectionHolder не является пустым.transactionActiveСвойство не пусто. Если в текущем потоке есть транзакция, она будет обработана в соответствии с другими характеристиками распространения транзакций. Конкретная логика кода выглядит следующим образом:

if (isExistingTransaction(transaction)) {
	// Existing transaction found -> check propagation behavior to find out how to behave.
	// 当前线程存在事务,分情况进行处理
	return handleExistingTransaction(def, transaction, debugEnabled);
}

PROPAGATION_NEVER

В конфиге выставлена ​​конфигурацияPROPAGATION_NEVER, указывающее, что метод должен выполняться в нетранзакционной среде, но в транзакционном состоянии (возможно, внешний транзакционный метод вызывает нетранзакционный метод), будет выдано исключение:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
		throw new IllegalTransactionStateException(
				"Existing transaction found for transaction marked with propagation 'never'");
	}

PROPAGATION_NOT_SUPPORTED

Если транзакция существует, приостановите транзакцию вместо создания исключения:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
	Object suspendedResources = suspend(transaction);
	boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
	return prepareTransactionStatus(
			definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}

ожидающая транзакция

Для приостановленной операции основной целью является запись состояния исходной транзакции, чтобы облегчить восстановление транзакции последующими операциями:

По факту,suspend()Метод вызывает диспетчер транзакцийDataSourceTransactionManagerсерединаdoSuspend()метод

protected Object doSuspend(Object transaction) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	//  将数据库连接设置为 null
	txObject.setConnectionHolder(null);
	return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}

Ключевой метод, который, наконец, вызывается,TransactionSynchronizationManager#doUnbindResource

private static Object doUnbindResource(Object actualKey) {
	Map<Object, Object> map = resources.get();
	if (map == null) {
		return null;
	}
	Object value = map.remove(actualKey);
	if (map.isEmpty()) {
		resources.remove();
	}
	if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
		value = null;
	}
	if (value != null && logger.isTraceEnabled()) {
        Thread.currentThread().getName() + "]");
	}
	return value;
}

Прочитав статью в седьмой ссылке, я разобрался с кодомОперация ожидания транзакции: процесс, удаляющий текущий поток, объект активной транзакции источника данных.

Так как же реализовать приостановку транзакций?doSuspend()в методеtxObject.setConnectionHolder(null),БудуconnectionHolderУстановить какnull.

ОдинconnectionHolderПредставляет объект подключения к базе данных, если онnull, указывающее, что соединение должно быть получено из кэш-пула при его следующем использовании, а автоматическая отправка нового соединенияtrue.


PROPAGATION_REQUIRES_NEW

Указывает, что текущий метод должен выполняться в собственной транзакции, будет запущена новая транзакция, и если транзакция уже выполняется, метод будет приостановлен во время его выполнения.

SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
	boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
	DefaultTransactionStatus status = newTransactionStatus(
			definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
	// 新事务的建立
	doBegin(transaction, definition);
	prepareSynchronization(status, definition);
	return status;
}
catch (RuntimeException | Error beginEx) {
	resumeAfterBeginException(transaction, suspendedResources, beginEx);
	throw beginEx;
}

Так же, как и предыдущий метод, вPROPAGATION_REQUIRES_NEWВ рамках функции вещания он также будет использоватьсяsuspendметод приостанавливает исходную транзакцию.методdoBegin(), что является ядром открытия сделки.


PROPAGATION_NESTED

Указывает, что если транзакция выполняется в данный момент, метод должен выполняться во вложенной транзакции. Вложенная транзакция может быть зафиксирована или отменена независимо от инкапсулирующей транзакции. Если инкапсулирующая транзакция не существует, поведение аналогичноPROPAGATION_REQUIRES_NEW.

В обработке прокси есть две ветви, сPROPAGATION_REQUIRES_NEWПодобные не постите, расскажите о пользеsavepointОбработка транзакций в виде точек сохранения:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
	// 嵌入式事务的处理
	if (useSavepointForNestedTransaction()) {
		DefaultTransactionStatus status =
				prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
		// 创建 savepoint
		status.createAndHoldSavepoint();
		return status;
	}
}

Друзья, изучавшие базу данных, должны знатьsavepoint, вы можете использовать точки сохранения для отката части транзакции, делая обработку транзакций более гибкой и детализированной.. Проследил код и обнаружил, что метод создания точки сохраненияorg.hsqldb.jdbc.JDBCConnection#setSavepoint(java.lang.String), если вам интересно, вы можете продолжить углубленное изучение ~


создание транзакции

На самом деле этот метод появился в предыдущих методахdoBegin(), создать транзакцию в этом методе, и установить уровень изоляции базы данных, кстати,timeoutСвойства и настройкиconnectionHolder:

DataSourceTransactionManager#doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	Connection con = null;
	if (!txObject.hasConnectionHolder() ||
			txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
		Connection newCon = obtainDataSource().getConnection();

		txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
	}

	txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
	con = txObject.getConnectionHolder().getConnection();
	// 设置隔离级别
	Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
	txObject.setPreviousIsolationLevel(previousIsolationLevel);

	// configured the connection pool to set it already).
	// 更改自动提交设置,由 spring 进行控制
	if (con.getAutoCommit()) {
		txObject.setMustRestoreAutoCommit(true);
		con.setAutoCommit(false);
	}
	// 准备事务连接
	prepareTransactionalConnection(con, definition);
	// 设置判断当前线程是否存在事务的依据
	txObject.getConnectionHolder().setTransactionActive(true);

	int timeout = determineTimeout(definition);
	if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
		txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
	}

	// Bind the connection holder to the thread.
	if (txObject.isNewConnectionHolder()) {
		// 将当前获取到的连接绑定到当前线程
		TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
		}
	}
}

Вывод: открытие транзакций Spring должно установить для свойства автоматической фиксации базы данных значение false


резюме

В декларативной обработке транзакций в основном есть следующие этапы обработки:

  1. Получить свойства транзакции:tas.getTransactionAttribute(method, targetClass)
  2. Загрузить конфигурацию, настроенную вTransactionManager:determineTransactionManager(txAttr);
  3. Различные методы обработки транзакций используют разную логику: Что касается декларативных транзакций и программных транзакций, вы можете проверить эту статью —Объяснение примеров программирования Spring и декларативных транзакций
  4. Получите транзакцию и соберите информацию о транзакции до выполнения целевого метода.интерес:createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
  5. Выполнить целевой метод:invocation.proceed()
  6. Произошло исключение, попробуйте обработать исключение:completeTransactionAfterThrowing(txInfo, ex);
  7. Удаление информации о транзакции перед совершением транзакции:cleanupTransactionInfo(txInfo)
  8. совершить транзакцию:commitTransactionAfterReturning(txInfo)

Откат и фиксация транзакции

Эти два шага в основном вызывают базовое соединение с базой данных.API, так что нет никакой проработки.


Суммировать

В этой статье кратко описано, как использоватьSpringтранзакции и как реализовать их в коде.

В предыдущих сценариях использования использовались только декларативные транзакции с конфигурацией по умолчанию.@Transactional, я не знаю значения других настроек свойств, и я не знаю, что в конфигурации по умолчанию, если метод в том же классе вызывается сам по себе, транзакция не поддерживается.

Таким образом, после этого изучения и подведения итогов в следующий раз, когда вы будете его использовать, вы сможете узнать, какие проблемы можно решить с помощью различных настроек атрибутов, таких как изменение характеристик вещания.PROPAGATION, разрешить автовызов метода поддержки транзакции и установить время ожидания транзакцииtimeout, уровень изоляции и другие атрибуты.


Из-за ограниченных личных технологий, если есть какие-либо недоразумения или ошибки, оставьте комментарий, и я внесу исправления на основе предложений друзей.

Адрес Gitee https://gitee.com/vip-augus/spring-analysis-note.git

Адрес Github https://github.com/Vip-Augus/spring-analysis-note


использованная литература

  1. Тщательное понимание использования @transactional в Spring
  2. Spring — аннотация @Transactional
  3. Два метода конфигурации транзакций, обычно используемые в spring, и уровень распространения и изоляции транзакций.
  4. Подробное объяснение точечного анализа транзакций Spring
  5. Советник, Совет, Pointcut весной
  6. Объяснение примеров программирования Spring и декларативных транзакций
  7. spring-transaction
  8. принцип точки сохранения
  9. Углубленный анализ исходного кода Spring / под редакцией Хао Цзя -- Пекин: издательство People's Posts and Telecommunications Publishing House

Портал: