5 минут на изучение причин сбоя транзакции Spring

Spring
5 минут на изучение причин сбоя транзакции Spring

предисловие

Управление транзакциями Spring почти используется в проектах, но правильно ли мы его используем? Вы точно знаете принцип? Эта статья быстро объяснит принцип сбоя транзакции Spring в сочетании с бизнес-сценариями.

1 деловая сцена

Если есть такое дело, метод сохранения в классе А должен вызывать метод сохранения2 этого класса.Выполняется ли метод в сохранении2 успешно или нет, это не может повлиять на выполнение метода сохранения.Поэтому мы будем думать о устанавливая поведение распространения транзакции save2. в REQUIRES_NEW, код выглядит следующим образом:

@Service
@Slf4j
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private UserService2 userService2;

    @Transactional
    public void save() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(5, 'Jack5')");
        try {
            save2();
        } catch (Exception e) {
            System.err.println("出错啦");
        }

    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save2() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(6, 'Jack6')");
        int i = 1 / 0;
    }
}

Поскольку метод save2 не может повлиять на выполнение метода сохранения, метод save2 должен быть перехвачен. Ожидаемый результат должен быть Метод save вставляет данные нормально, но метод save2 не вставляет данные

Результаты:

结果1
结果2
Да, вы правильно прочитали, метод save2 был успешно вставлен! Если вы знаете причину, вы можете перестать читать ниже ~

2 Исследуйте

2.1 Поведение распространения Spring

Давайте опубликуем поведение распространения Spring общественное перечисление Распространение {
ТРЕБУЕТСЯ(0), ПОДДЕРЖКИ(1), ОБЯЗАТЕЛЬНО(2), REQUIRES_NEW(3), НЕ ПОДДЕРЖИВАЕТСЯ(4), НИКОГДА(5), ВЛОЖЕННЫЙ (6); } ТРЕБУЕТСЯ: если транзакция в настоящее время существует, присоединиться к транзакции; если текущей транзакции нет, создать новую транзакцию. ПОДДЕРЖИВАЕТ: если транзакция уже существует, присоединяйтесь к ней; если текущей транзакции нет, продолжайте работу в нетранзакционном режиме. ОБЯЗАТЕЛЬНО: если в данный момент есть транзакция, присоединиться к транзакции; если текущей транзакции нет, создать исключение. REQUIRES_NEW: создать новую транзакцию, если есть текущая транзакция, приостановить текущую транзакцию. NOT_SUPPORTED: работает в нетранзакционном режиме, приостанавливает текущую транзакцию, если есть текущая транзакция. НИКОГДА: выполняется без транзакций, выдает исключение, если транзакция уже существует. NESTED : если транзакция в настоящее время существует, создайте транзакцию для запуска как вложенную транзакцию текущей транзакции; если текущей транзакции нет, значение эквивалентно REQUIRED .

Чтобы быть уверенным, бизнес действительно использует REQUIRES_NEW. Но почему это не удается?

2.2 Динамический прокси

Прежде чем двигаться дальше, давайте кратко рассмотрим динамические прокси. Основная функция шаблона прокси — это шаблон проектирования, созданный для улучшения методов в классе. Режим прокси делится на динамический прокси и статический прокси. Класс прокси динамического прокси генерируется во время выполнения, а статический прокси генерируется во время компиляции. Динамические прокси можно разделить на динамические прокси JDK на основе интерфейса и динамические прокси Cglib на основе классов.

Ниже объясняется динамический прокси на основе JDK: Класс Proxy и интерфейс InvocationHandler предоставляются в пакете java.lang.reflect java. С помощью этого класса и этого интерфейса можно создавать динамические прокси-классы JDK и динамические прокси-объекты.

public interface Person {
    void work();
}

public class Student implements Person {
    @Override
    public void work() {
        System.out.println("读书");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    //增强的目标类
    private Person person;

    public MyInvocationHandler(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("先吃饭-----再看书");
        method.invoke(person, args);
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Student();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(person);
        System.out.println(Arrays.toString(Student.class.getInterfaces()));
        Person proPerson = (Person) Proxy.newProxyInstance(Student.class.getClassLoader(), Student.class.getInterfaces(), myInvocationHandler);
        proPerson.work();
    }
}

Результат: Сначала ешь-----читай потом читать

Подробный код можно скачать с github,GitHub.com/229319258/ это…

2.3 Яма динамических прокси

На данный момент мы знаем, что транзакции Spring реализованы на основе динамических прокси. Итак, какое отношение реальная причина сбоя транзакции Spring имеет к динамическим прокси?

Чтобы смоделировать проблему сбоя транзакции Spring, немного измените приведенный выше код:

public class Student implements Person {
    @Override
    public void work() {
        System.out.println("读书");
        try {
            this.work2();
        } catch (Exception e) {

        }
    }
    public void work2() {
        System.out.println("不想读啊");
        int i = 1 / 0;
    }
}

Все, вы можете сосредоточиться на блоке кода try.Мы можем обнаружить, что экземпляр Student, который на самом деле вызывает метод work2, не является так называемым расширенным классом work2.Точно так же метод save2 сбоя транзакции Spring выше, вызываемый экземпляр не является прокси-классом, а нерасширенным обычным объектом UserService.

Следовательно, без использования метода, сгенерированного Proxy, транзакции Spring, конечно же, не пройдут~

Ну вот опять проблема. Если я действительно хочу, чтобы транзакция save2 вступила в силу, что мне делать? Есть два способа

  • положить save2 обратно на другой класс
  • Используйте метод AopContext.currentProxy() для получения текущего прокси-объекта.
    @Transactional
    public void save() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(5, 'Jack5')");
        try {
            UserService proxy = (UserService) AopContext.currentProxy();
            proxy.save2();
        } catch (Exception e) {
            System.err.println("出错啦");
        }

    }

3 Заключение

1. Когда мы используем транзакции Spring, мы не можем напрямую вызывать @Transactional(propagation = Propagation.REQUIRES_NEW) того же класса в определении @Transactional

2. Помимо отказа в данном случае, мы не можем напрямую вызвать метод @Transactional в этом же классе в методе без набора @Transactional, т.к. фактически вызывается не метод класса прокси, а метод самого прокси-класса.как:


    //    @Transactional
    public void save() {
        save2();
    }

    @Transactional()
    public void save2() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(7, 'Jack7')");
        int i = 1 / 0;
    }

Глядя на данные в базе, те же самые данные save2 не откатятся, потому что вместо вызова прокси-класса вызывается обычный метод this (UserService). Следовательно, сделка также недействительна.

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

кодовый адрес

кодовый адрес

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

Подробный анализ реализации и принципа динамического прокси Java. Что касается сбоя транзакции Spring

Фан Цин

Команда разработчиков Java компании Guangzhou Reed Technology

Reed Technology-Guangzhou Professional Internet Software Service Company

Ухватитесь за каждую деталь и создайте каждую красоту

Подпишитесь на наш официальный аккаунт, чтобы узнать больше

Хотите сразиться с нами? лагу поиск"Рид Технология» или отправить свое резюме наserver@talkmoney.cnПрисоединяйтесь к нам

Следите за нами, ваши комментарии и лайки - наша самая большая поддержка