Принцип использования и реализации транзакции 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);
}
}
С приведенным выше кодом я провел два теста:
-
В конфигурационном файле транзакция не открывается.то есть
<tx:annotation-driven/>
Эта строка закомментирована, хотя метод, который мы выполняем, выбрасываетRuntimeExcepton
, но данные все еще вставляются в базу данных. -
В файле конфигурации запустите транзакцию.Удалите приведенный выше комментарий, удалите записи в базе данных, повторно выполните код запуска и обнаружите, что данные не были вставлены.Когда программа выдает исключение,
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
Основные понятия:
- PointcutОпределите pointcut, который можно использовать для выполнения логики аспекта до и после перехваченного метода.
-
AdviceИспользуется для определения поведения перехвата, где реализована расширенная логика, это интерфейс-предок.
org.aopalliance.aop.Advice
. Существуют и другие унаследованные интерфейсы, такие какMethodBeforeAdvice
, что конкретно относится к улучшениям перед выполнением метода. -
AdvisorОн используется для инкапсуляции всей информации аспекта, в основном двух вышеупомянутых, которые используются в качестве
Advice
а такжеPointcut
адаптер.
После просмотра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
, на следующем рисунке показана схема его системы наследования:
Как видите, достигается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
Это было подробно описано в , вот что делает этот метод:
- выяснить
bean
Соответствующий бустер - Создать агента на основе найденного бустера
и создать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
система наследования:
он достигает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);
}
В методе создания транзакции в основном выполняются следующие три вещи:
- использовать
DelegatingTransactionAttribute
УпаковкаtxAttr
пример - Получить транзакцию:
tm.getTransaction(txAttr)
- Информация о транзакции сборки:
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
резюме
В декларативной обработке транзакций в основном есть следующие этапы обработки:
-
Получить свойства транзакции:
tas.getTransactionAttribute(method, targetClass)
-
Загрузить конфигурацию, настроенную в
TransactionManager
:determineTransactionManager(txAttr);
- Различные методы обработки транзакций используют разную логику: Что касается декларативных транзакций и программных транзакций, вы можете проверить эту статью —Объяснение примеров программирования Spring и декларативных транзакций
-
Получите транзакцию и соберите информацию о транзакции до выполнения целевого метода.интерес:
createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
-
Выполнить целевой метод:
invocation.proceed()
-
Произошло исключение, попробуйте обработать исключение:
completeTransactionAfterThrowing(txInfo, ex);
-
Удаление информации о транзакции перед совершением транзакции:
cleanupTransactionInfo(txInfo)
-
совершить транзакцию:
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
использованная литература
- Тщательное понимание использования @transactional в Spring
- Spring — аннотация @Transactional
- Два метода конфигурации транзакций, обычно используемые в spring, и уровень распространения и изоляции транзакций.
- Подробное объяснение точечного анализа транзакций Spring
- Советник, Совет, Pointcut весной
- Объяснение примеров программирования Spring и декларативных транзакций
- spring-transaction
- принцип точки сохранения
- Углубленный анализ исходного кода Spring / под редакцией Хао Цзя -- Пекин: издательство People's Posts and Telecommunications Publishing House
Портал:
-
Изучение исходного кода Spring (1) инфраструктуры контейнера
-
Изучение исходного кода Spring (2) анализ тегов по умолчанию
-
Функция расширения изучения исходного кода Spring (6), часть 1
-
Функция расширения изучения исходного кода Spring (семь), часть 2
-
Изучение исходного кода Spring (8) Принципы использования и реализации АОП
-
Изучение исходного кода Spring (9) Транзакционная транзакция