1 Обзор
Я использовал SpringBoot@Transactional
Заниматься управлением делами, но редко задумываться о том, как реализован SpringBoot, а сегодня из исходников посмотреть@Transactional
Как реализовать транзакции, и, наконец, мы объединяем понимание исходного кода, пишем аналогичную аннотацию, чтобы самостоятельно управлять транзакциями, чтобы помочь нам углубить наше понимание.
Инструкции по прочтению: в этой статье предполагается, что вы знакомы с языком Java и имеете базовое представление о транзакциях и их использовании.
2. Знание дел
Прежде чем приступить к просмотру исходного кода, давайте рассмотрим соответствующие знания о транзакциях.
2.1 Уровень изоляции транзакций
Зачем транзакциям нужны уровни изоляции? Это связано с тем, что в случае параллельных транзакций отсутствие уровня изоляции вызовет следующие проблемы:
-
Грязное чтение: когда транзакция A изменяет данные, но модификация не была отправлена в базу данных, транзакция B одновременно обращается к данным. Поскольку нет изоляции, данные, полученные транзакцией B, могут быть отменены транзакцией A, что приводит к несогласованности данных Проблема.
-
Потеряно для изменения: Когда транзакция A получает доступ к данным 100 и изменяет их на 100-1=99, а транзакция B считывает данные 100 и изменяет данные 100-1=99, окончательный результат модификации двух транзакций равен 99, но на самом деле это 98. Данные, измененные транзакцией А, теряются.
-
Неповторимое чтение: Когда транзакция A считывает данные X = 100, транзакция B изменяет данные X = 100 на X = 200. В это время, когда транзакция A считывает данные X во второй раз, она обнаруживает, что X = 200, в результате чего в течение всего периода A транзакции два чтения данных X несовместимы, что является неповторяемым чтением.
-
Фантомное чтение: Фантомные чтения аналогичны неповторяемым чтениям. Таблица фантомного чтения заключается в том, что когда транзакция A читает данные таблицы, есть только записи данных 3. В это время транзакция B вставляет записи данных 2. Когда транзакция A снова читает ее, она обнаруживает, что записей 5, и есть еще 2 записи ни с того ни с сего записи, как галлюцинации.
Неповторяемое чтение против фантомного чтения
Смысл неповторяемого чтения заключается в изменении :Те же условия, данные, которые вы прочитали, прочтите их снова и обнаружите, что значение другое, основное внимание уделяется операции обновления.Смысл фантомного чтения в том, чтобы добавить или удалить: При одинаковых условиях количество считываемых записей в первый и во второй раз разное, а упор делается на операции добавления и удаления.
Поэтому, чтобы избежать вышеуказанных проблем, существует понятие уровня изоляции в транзакциях, и в Spring определены пять констант, представляющих уровни изоляции:
постоянный | инструкция |
---|---|
TransactionDefinition.ISOLATION_DEFAULT | Уровень изоляции базы данных по умолчанию, уровень изоляции REPEATABLE_READ, принятый MySQL по умолчанию. |
TransactionDefinition.ISOLATION_READ_UNCOMMITTED | Самый низкий уровень изоляции, позволяющий читать незафиксированные изменения данных,Может вызвать грязное чтение, фантомное чтение или неповторяющееся чтение.. |
TransactionDefinition.ISOLATION_READ_COMMITTED | Позволяет читать данные, которые были зафиксированы параллельными транзакциями,Грязные чтения можно предотвратить, но фантомные или неповторяющиеся чтения все же могут происходить.. |
TransactionDefinition.ISOLATION_REPEATABLE_READ | Результаты многократного чтения одного и того же поля согласуются, если только данные не изменены его собственной транзакцией, ** может предотвратить грязное чтение и неповторяющееся чтение, но фантомное чтение все же может происходить. **В MySQL устранена возможность фантомного чтения на этом уровне изоляции с помощью MVCC. |
TransactionDefinition.ISOLATION_SERIALIZABLE | уровень изоляции сериализации,Этот уровень предотвращает грязные чтения, неповторяемые чтения и фантомные чтения., но сериализация влияет на производительность. |
2.2 Механизм распространения транзакций в Spring
Почему в Spring существует набор механизмов распространения транзакций? Это инструмент улучшения транзакций, который Spring предоставляет нам, в основном для решения проблемы вызовов между методами и способов обработки транзакций. Например, есть метод A, метод B и метод C, а метод B и метод C вызываются в A. Псевдокод выглядит следующим образом:
MethodA{
MethodB;
MethodC;
}
MethodB{
}
MethodC{
}
Предполагая, что все три метода открыли свои дела, какая между ними связь?MethodA
Откат повлияетMethodB
иMethodC
? Механизм распространения транзакций в Spring призван решить эту проблему.
В Spring определено семь вариантов поведения распространения транзакций:
тип | инструкция |
---|---|
PROPAGATION_REQUIRED | Если нет текущей транзакции, создайте новую транзакцию, если уже есть транзакция, присоединяйтесь к транзакции. Это самый распространенный выбор |
PROPAGATION_SUPPORTS | Поддерживать текущую транзакцию, если текущей транзакции нет, она будет выполнена в нетранзакционном режиме. |
PROPAGATION_MANDATORY | Используйте текущую транзакцию или создайте исключение, если текущей транзакции нет. |
PROPAGATION_REQUIRES_NEW | Создать новую транзакцию. Если есть текущая транзакция, приостановить текущую транзакцию. |
PROPAGATION_NOT_SUPPORTED | Выполнить операцию нетранзакционным способом, приостановив текущую транзакцию, если текущая транзакция существует. |
PROPAGATION_NEVER | Выполняется без транзакций и выдает исключение, если транзакция уже существует. |
PROPAGATION_NESTED | Если транзакция уже существует, выполните ее во вложенной транзакции. Если текущих транзакций нет, сделайте что-то похожее на PROPAGATION_REQUIRED. |
Как эти семь коммуникационных механизмов влияют на дела, заинтересованные студенты могут прочитатьэта статья.
3. Как реализовать аварийный откат
Изучив соответствующие знания о транзакциях, давайте формально изучим, как передать транзакцию в Spring Boot.@Transactional
Чтобы управлять транзакциями, давайте сосредоточимся на том, как он реализует откат.
веснойTransactionInterceptor
иPlatformTransactionManager
Эти два класса являются ядром всего модуля транзакций.TransactionInterceptor
Отвечает за перехват выполнения метода и определение необходимости фиксации или отката транзакции.PlatformTransactionManager
Это интерфейс управления транзакциями в Spring, который действительно определяет, как транзакции откатываются и фиксируются. Мы сосредоточимся на исходном коде этих двух классов.
TransactionInterceptor
Кода в классе много, упрощу логику для простоты объяснения:
//以下代码省略部分内容
public Object invoke(MethodInvocation invocation) throws Throwable {
//获取事务调用的目标方法
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
//执行带事务调用
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
Упрощенная логика invokeWithinTransaction выглядит следующим образом:
//TransactionAspectSupport.class
//省略了部分代码
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
Object retVal;
try {
//调用真正的方法体
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 如果出现异常,执行事务异常处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//最后做一下清理工作,主要是缓存和状态等
cleanupTransactionInfo(txInfo);
}
//如果没有异常,直接提交事务。
commitTransactionAfterReturning(txInfo);
return retVal;
}
Логика аварийного отката транзакцийcompleteTransactionAfterThrowing
следующее:
//省略部分代码
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
//判断是否需要回滚,判断的逻辑就是看有没有声明事务属性,同时判断是不是在目前的这个异常中执行回滚。
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
//执行回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
else {
//否则不需要回滚,直接提交即可。
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
}
Приведенный выше код четко объясняет основные принципы транзакций Spring, как оценивать и выполнять транзакции и как выполнять откат. Теперь к коду, который фактически выполняет логику отката.PlatformTransactionManager
Подкласс интерфейса, в качестве примера возьмем транзакцию JDBC,DataSourceTransactionManager
Это класс управления транзакциями jdbc. Следуйте приведенному выше кодуrollback(txInfo.getTransactionStatus())
Можно обнаружить, что окончательный исполняемый код выглядит следующим образом:
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
//调用jdbc的 rollback进行回滚事务。
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
3.1 Резюме
Вот краткое изложение идей реализации транзакций в Spring, Spring в основном опирается наTransactionInterceptor
Чтобы перехватить тело метода выполнения, определите, следует ли открывать транзакцию, а затем выполните тело метода транзакции в теле метода.catch
Живи ненормально, потом суди, нужно ли откатывать, а если нужно откатывать, делегируй настоящуюTransactionManager
Например в JDBCDataSourceTransactionManager
для выполнения логики отката. То же самое верно и для совершения транзакции.
Вот блок-схема, иллюстрирующая идею:
4. Напишите аннотацию для реализации отката транзакции
Мы разобрались с процессом выполнения транзакций Spring, теперь мы можем имитировать себя, чтобы написать аннотацию для реализации функции отката при возникновении указанного исключения. Здесь слой сохраняемости берет в качестве примера простейший JDBC. Давайте сначала разберем требования. Во-первых, мы аннотируем, что можем реализовать его на основе АОП Spring. Затем, поскольку это JDBC, нам нужен класс, который поможет нам управлять соединением, чтобы определить, откатывается ли исключение или фиксируется. Дайте высохнуть, когда закончите.
4.1 Первое добавление зависимостей
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.2 Добавить примечание
/**
* @description:
* @author: luozhou
* @create: 2020-03-29 17:05
**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyTransaction {
//指定异常回滚
Class<? extends Throwable>[] rollbackFor() default {};
}
4.3 Добавлен диспетчер соединений
Этот класс помогает нам управлять соединениями.Основная функция этого класса — привязать извлеченный объект соединения к потоку, что удобно для извлечения, фиксации или отката во время обработки АОП.
/**
* @description:
* @author: luozhou
* @create: 2020-03-29 21:14
**/
@Component
public class DataSourceConnectHolder {
@Autowired
DataSource dataSource;
/**
* 线程绑定对象
*/
ThreadLocal<Connection> resources = new NamedThreadLocal<>("Transactional resources");
public Connection getConnection() {
Connection con = resources.get();
if (con != null) {
return con;
}
try {
con = dataSource.getConnection();
//为了体现事务,全部设置为手动提交事务
con.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
resources.set(con);
return con;
}
public void cleanHolder() {
Connection con = resources.get();
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
resources.remove();
}
}
4.4 Добавить новый аспект
Эта часть является ядром обработки транзакций.Сначала получите класс исключения в аннотации, затем перехватите исключение выполнения, определите, является ли исключение исключением в аннотации или его подклассом, и если да, выполните откат, в противном случае отправьте.
/**
* @description:
* @author: luozhou
* @create: 2020-03-29 17:08
**/
@Aspect
@Component
public class MyTransactionAopHandler {
@Autowired
DataSourceConnectHolder connectHolder;
Class<? extends Throwable>[] es;
//拦截所有MyTransaction注解的方法
@org.aspectj.lang.annotation.Pointcut("@annotation(luozhou.top.annotion.MyTransaction)")
public void Transaction() {
}
@Around("Transaction()")
public Object TransactionProceed(ProceedingJoinPoint proceed) throws Throwable {
Object result = null;
Signature signature = proceed.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method == null) {
return result;
}
MyTransaction transaction = method.getAnnotation(MyTransaction.class);
if (transaction != null) {
es = transaction.rollbackFor();
}
try {
result = proceed.proceed();
} catch (Throwable throwable) {
//异常处理
completeTransactionAfterThrowing(throwable);
throw throwable;
}
//直接提交
doCommit();
return result;
}
/**
* 执行回滚,最后关闭连接和清理线程绑定
*/
private void doRollBack() {
try {
connectHolder.getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
connectHolder.cleanHolder();
}
}
/**
*执行提交,最后关闭连接和清理线程绑定
*/
private void doCommit() {
try {
connectHolder.getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
} finally {
connectHolder.cleanHolder();
}
}
/**
*异常处理,捕获的异常是目标异常或者其子类,就进行回滚,否则就提交事务。
*/
private void completeTransactionAfterThrowing(Throwable throwable) {
if (es != null && es.length > 0) {
for (Class<? extends Throwable> e : es) {
if (e.isAssignableFrom(throwable.getClass())) {
doRollBack();
}
}
}
doCommit();
}
}
4.5 Тестовая проверка
Создайте таблицу tb_test со следующей структурой:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_test
-- ----------------------------
DROP TABLE IF EXISTS `tb_test`;
CREATE TABLE `tb_test` (
`id` int(11) NOT NULL,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
SET FOREIGN_KEY_CHECKS = 1;
4.5.1 Написать службу
saveTest
Метод вызывает 2 оператора вставки при объявлении@MyTransaction
аннотация транзакции, обнаруженнаяNullPointerException
Просто откатываемся, и, наконец, мы выполняем операцию деления на 0, которая выдаетArithmeticException
. Мы используем модульные тесты, чтобы увидеть, будут ли данные откатываться.
/**
* @description:
* @author: luozhou kinglaw1204@gmail.com
* @create: 2020-03-29 22:05
**/
@Service
public class MyTransactionTest implements TestService {
@Autowired
DataSourceConnectHolder holder;
//一个事务中执行两个sql插入
@MyTransaction(rollbackFor = NullPointerException.class)
@Override
public void saveTest(int id) {
saveWitharamters(id, "luozhou@gmail.com");
saveWitharamters(id + 10, "luozhou@gmail.com");
int aa = id / 0;
}
//执行sql
private void saveWitharamters(int id, String email) {
String sql = "insert into tb_test values(?,?)";
Connection connection = holder.getConnection();
PreparedStatement stmt = null;
try {
stmt = connection.prepareStatement(sql);
stmt.setInt(1, id);
stmt.setString(2, email);
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.5.2 Модульное тестирование
@SpringBootTest
@RunWith(SpringRunner.class)
class SpringTransactionApplicationTests {
@Autowired
private TestService service;
@Test
void contextLoads() throws SQLException {
service.saveTest(1);
}
}
Код цифры объявляет о делахNullPointerException
Исключение для отката, возникшее во время работыArithmeticException
Это ненормально, поэтому не будет откатываться.Мы обновили базу данных справа и обнаружили, что данные были успешно вставлены нормально, что указывает на то, что они не были отброшены.
Мы меняем класс исключений отката наArithmeticException
, чтобы очистить исходные данные и выполнить их снова, появляетсяArithmeticException
Ненормально, в настоящее время в базу данных не добавлена ни одна запись, что означает откат, что указывает на то, что наши аннотации работают.
5. Резюме
В начале этой статьи мы рассмотрели соответствующие знания о транзакциях, и параллельные транзакции приведут кгрязное чтение,потерянная модификация,неповторяемое чтение,галлюцинации, для решения этих задач в БД вводится уровень изоляции транзакции, а уровень изоляции включает в себя:читать незафиксированные,чтение фиксации,повторяемое чтениеисериализовать.
Концепция транзакции расширена в Spring.Чтобы решить транзакционную связь между методом A, методом B и методом C, введена концепция механизма распространения транзакций.
весной@Transactional
Аннотированные транзакции реализуются в основном черезTransactionInterceptor
Перехватчик реализуется, перехватывает целевой метод, а затем определяет, является ли исключение целевым исключением, если это целевое исключение, оно будет откатываться, иначе транзакция будет зафиксирована.
Наконец, мы написали свой собственный через JDBC в сочетании с AOP Spring.@MyTransactional
В аннотации реализована функция отката при возникновении указанного исключения.