Рукописная транзакция и аннотация транзакции на основе SpringAOP

Spring

Личный блог: zhenganwen.top

Обзор АОП

AOP,СейчасAspect-Oriented ProgramАспектно-ориентированное программирование по сравнению с вертикальным улучшением объектов, таких как наследование и шаблоны декораторов,AOPОн горизонтальный, неинвазивный, подключаемый и часто используется повторно. Поэтому какSpringОн широко используется в ведении журналов, управлении транзакциями, управлении разрешениями, обработке исключений и других сценариях.

прокси-режим

из-заAOPОн основан на динамическом прокси, поэтому в этом разделе кратко представлен режим прокси. Режим прокси делится на статический прокси и динамический прокси.Основная идея состоит в том, чтобы играть роль посредника (прокси-объект) между вызывающим и вызываемым (прокси-объект, также известный как целевой объект), который может достичь разделения , повторное использование, Эффект защиты:

  • Все запросы на вызов должны сначала пройти через прокси-объект, поэтому прокси-объект может сначала отфильтровать эти запросы, чтобы заблокировать незаконные запросы, что необходимо для защиты.
  • Если вам нужно записывать информацию о вызывающем абоненте перед каждым вызовом, вы можете передать эту задачу прокси-объекту, не добавляя этот код к другим вызываемым объектам, который предназначен для повторного использования.
  • Для вызывающей стороны прокси-объект и целевой объект имеют одинаковый внешний вид. Через прокси-объект в качестве посредника вызывающая сторона не знает, кто на самом деле обрабатывает запрос. Вызывающая сторона и целевой объект не имеют прямой зависимости.

Прокси-объект не выполняет фактическую бизнес-обработку запроса на вызов.

статический прокси

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

Пример кода статического прокси-сервера выглядит следующим образом:

public interface UserService {

    void add();
}

public class UserReadServiceImpl implements UserService {
    public void add() {
        System.out.println("illegal invoke");
    }

    public void query() {
        System.out.println("query user");
    }
}

public class UserWriteServiceImpl implements UserService {
    public void add() {
        System.out.println("insert user");
    }

    public void query() {
        System.out.println("illegal invoke");
    }
}

public class UserServiceProxy implements UserService {

    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    public void add() {
        userService.add();
    }

    public void query() {
        userService.query();
    }
}

Тест выглядит следующим образом:

public class UserServiceProxyTest {

    @Test
    public void add() {
        UserService userService = new UserWriteServiceImpl();
        UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
        userServiceProxy.add();	
    }

    @Test
    public void query() {
        UserService userService = new UserReadServiceImpl();
        UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
        userServiceProxy.query(); 
    }
}

insert user
query user

Недостатки статического прокси:

  • Дополнения к каждой сфере деятельности (как здесьUserServiceявляется доменом), вам нужно вручную написать класс прокси
  • избыточность кода
    • Для каждого прокси-класса логика прокси должна быть написана в каждом прокси-методе, даже если логика одинакова. Например, прокси-класс журнала делает запись в журнале информации о запросе перед каждым вызовом целевого метода.Если это будет добавлено до того, как каждый прокси-метод вызовет целевой метод, он будет казаться избыточным и избыточным.

Чтобы избежать этих недостатков статических прокси, можно использовать динамические прокси.

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

В настоящее время существует две схемы динамического прокси, а именноjdkпрокси иcglibиграет роль.

  • jdkагент означаетjdkвстроенный, черезjdk apiможет быть достигнут,Только методы интерфейса, реализованные целевым объектом, могут быть улучшены.
  • cglibПрокси относятся к использованию сторонних библиотекcglibреализовать динамический прокси,cglibтакже зависит отasmбиблиотека,asmявляется методом модификации байт-кода, аcglibПрокси-объект создается путем изменения символической ссылки в байт-коде. Структуру файла байт-кода и ссылки на символы см. в документе «Углубленное понимание виртуальной машины Java (второе издание)» (Чжоу Чжимин). из-заcglibДинамические прокси основаны на наследовании, поэтомуcglibДинамические прокси могутУлучшить все наследуемые методы целевого объекта

jdkПример кода для прокси выглядит следующим образом:

package cn.tuhu.springaop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class LogProxy {

    private Object target;

    private LogInvocationHandler logInvocationHandler = new LogInvocationHandler();

    public LogProxy(Object target) {
        this.target = target;
    }

    public Object getProxyObject() {
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(classLoader, interfaces, logInvocationHandler);
    }

    private class LogInvocationHandler implements InvocationHandler {

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("log for request");
            method.invoke(target, args);
            return proxy;
        }
    }

}

Тест выглядит следующим образом:

package cn.tuhu.springaop.proxy;

import cn.tuhu.springaop.service.UserService;
import cn.tuhu.springaop.service.impl.UserReadServiceImpl;
import org.junit.Test;

import static org.junit.Assert.*;

public class LogProxyTest {

    @Test
    public void getProxyObject() {
        UserService userService = new UserReadServiceImpl();
        LogProxy logProxy = new LogProxy(userService);
        UserService userServiceProxy = (UserService) logProxy.getProxyObject();
        userServiceProxy.query();
        userServiceProxy.add();
    }
}


log for request
query user
log for request
illegal invoke

Рукописная транзакция на основе SpringAOP

АОП-программирование

Выше описаны основные принципы динамического прокси,SpringизAOPНижний слой представляет собой динамический прокси, который состоит из следующих элементов:

  • Точка входа / проблемаpointcut/joinpoint
    • Методы, которые необходимо улучшить
  • Улучшения/Уведомленияadvice
    • Требуется расширенная логика поверх целевого метода
  • разделaspect
    • Агрегат, который применяет уведомления через pointcuts

представлятьSpringAOPСвязанные зависимости:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.0.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.0.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>3.0.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.1</version>
</dependency>
<dependency>
    <groupId>aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.5.3</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_2</version>
</dependency>

Добавить кspringКонфигурационный файл:

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

    <context:component-scan base-package="cn.tuhu.springaop"></context:component-scan>
	
    <!-- 开启AOP编程注解,开启后标识为@Aspect的bean的AOP才会生效 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 

</beans>

Бизнес-интерфейс:

package cn.tuhu.springaop.service;

public interface UserService {

    void add();

    void query();
}

Реализация бизнеса:

package cn.tuhu.springaop.service.impl;

import cn.tuhu.springaop.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("insert user");
    }

    public void query() {
        System.out.println("query user");
    }
}

Класс аспекта:

package cn.tuhu.springaop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAspect {

    //前置通知
    @Before(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") //切入点:service.impl包下的所有方法
    public void before() {
        System.out.println("Before:最先执行");
    }

    //后置通知
    @After(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
    public void after() {
        System.out.println("After:方法执行之后执行");
    }

    //环绕通知
    @Around(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("Around Begin:Before执行之后方法执行之前执行");
        proceedingJoinPoint.proceed();
        System.out.println("Around End:After执行之后执行");
    }

    //正常结束通知
    @AfterReturning(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
    public void afterReturning() {
        System.out.println("AfterReturning:最后执行");
    }

    //异常终止通知
    @AfterThrowing(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
    public void afterThrowing() {
        System.out.println("AfterThrowing:抛出异常后执行");
    }

}

Тестовый класс:

package cn.tuhu.springaop.service.impl;

import cn.tuhu.springaop.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceImplTest {

    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

    @Test
    public void add() {
        UserService userService = context.getBean(UserService.class);
        userService.add();
    }
}


Before:最先执行
Around Begin:Before执行之后方法执行之前执行
insert user
After:方法执行之后执行
Around End:After执行之后执行
AfterReturning:最后执行
======================
Before:最先执行
Around Begin:Before执行之后方法执行之前执行
query user
After:方法执行之后执行
Around End:After执行之后执行
AfterReturning:最后执行

видимый,AOPМеханизм аспектов является гибким и может быть расширен различными способами, а также гибкая настройка проблем (executionвыражение).

Программные транзакции

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

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

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>3.0.6.RELEASE</version>
</dependency>

Настройте источник данных и менеджер транзакций в файле конфигурации:

<!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="123456"></property>
</bean>

<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 3.配置事务 -->
<bean id="dataSourceTransactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

Dao

package cn.tuhu.springaop.dao;

import cn.tuhu.springaop.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public void add(User user) {
        String sql = "insert into user (id,name) values(" + user.getId() + ",'" + user.getName() + "');";
        jdbcTemplate.execute(sql);
    }
}

Service

package cn.tuhu.springaop.service.impl;

import cn.tuhu.springaop.dao.UserDao;
import cn.tuhu.springaop.entity.User;
import cn.tuhu.springaop.service.UserService;
import cn.tuhu.springaop.util.TransactionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserDao userDao;

    @Autowired
    TransactionUtils transactionUtils;

    public void add() {
        TransactionStatus transactionStatus = null;
        try {
            //begin
            transactionStatus = transactionUtils.begin();

            User user = new User(1L, "张三");
            userDao.add(user);
            user = new User(2L, "李四");
            int i = 1 / 0;
            userDao.add(user);

            if (transactionStatus != null) {
                transactionUtils.commit(transactionStatus);
            }
        } catch (Exception e) {
            if (transactionStatus != null) {
                transactionUtils.rollback(transactionStatus);
            }
        }
    }

}

тестовая транзакция

public class UserServiceImplTest {

    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

    @Test
    public void add() {
        UserService userService = context.getBean(UserService.class);
        userService.add();
    }
}

обновить базу данныхuserТаблица обнаружила, что данные не вставлены, и закомментировалаi=1/0Затем вставьте две части данных, указывающие на то, что транзакция вступит в силу.

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

package cn.tuhu.springaop.proxy;

import cn.tuhu.springaop.util.TransactionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;

@Component
@Aspect
public class TransactionAop {

    @Autowired
    TransactionUtils transactionUtils;

    @Around(value = "execution(* cn.tuhu.springaop.service.impl.*.add*(..))," +
            "execution(* cn.tuhu.springaop.service.impl.*.update*(..))," +
            "execution(* cn.tuhu.springaop.service.impl.*.delete*(..))")
    public void transactionHandler(ProceedingJoinPoint proceedingJoinPoint) {
        TransactionStatus transactionStatus = transactionUtils.begin();
        try {
            proceedingJoinPoint.proceed();
            transactionUtils.commit(transactionStatus);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            transactionUtils.rollback(transactionStatus);
        }
    }
}

Бизнес-классу нужно сосредоточиться только на бизнес-коде

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserDao userDao;

    public void add() {
        User user = new User(1L, "张三");
        userDao.add(user);
        user = new User(2L, "李四");
        int i = 1 / 0;
        userDao.add(user);
    }

}

декларативная сделка

SpringДекларативная транзакция выполняется через@TransactionalЧтобы добиться этого с помощью аннотаций, в первую очередь мы сначала экранируем то, что было написано в предыдущем разделе.TransactionAop:

//@Component
//@Aspect
public class TransactionAop {

существуетspring.xmlОткрытые декларативные аннотации транзакций в (обратите внимание на введениеtxпространство имен):

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">
    <context:component-scan base-package="cn.tuhu.springaop"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 -->

    <!-- 1. 数据源对象: C3P0连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!-- 2. JdbcTemplate工具类实例 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 3.配置事务 -->
    <bean id="dataSourceTransactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>

Просто добавьте методы и классы@TransactionalАннотация может добавить контроль транзакций (если она добавлена ​​в класс, это эквивалентно добавлению ее в каждый метод@Transactional):

package cn.tuhu.springaop.service.impl;

import cn.tuhu.springaop.dao.UserDao;
import cn.tuhu.springaop.entity.User;
import cn.tuhu.springaop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserDao userDao;

    @Transactional(rollbackFor = Exception.class)
    public void add() {
        User user = new User(1L, "张三");
        userDao.add(user);
        user = new User(2L, "李四");
        int i = 1 / 0;  //不注释测一次,注释起来再测一次
        userDao.add(user);
    }

}

Принцип реализации

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

Напишите примечание о транзакции от руки

  1. определить аннотации транзакций

    package cn.tuhu.springaop.annotation;
    
    public @interface MyTransactional {
        
    }
    
  2. Напишите классы аспектов, которые улучшают методы с помощью аннотаций транзакций.

    package cn.tuhu.springaop.proxy;
    
    import cn.tuhu.springaop.annotation.MyTransactional;
    import cn.tuhu.springaop.util.TransactionUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.interceptor.TransactionAspectSupport;
    
    import java.lang.reflect.Method;
    
    @Component
    @Aspect
    public class TransactionAop {
    
        @Autowired
        TransactionUtils transactionUtils;
    
        private ProceedingJoinPoint proceedingJoinPoint;
    
        @Around(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
        public void transactionHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            TransactionStatus transactionStatus = null;
    
            if (hasTransaction(proceedingJoinPoint)) {
                transactionStatus = transactionUtils.begin();
            }
    
            proceedingJoinPoint.proceed();
    
            // 若hasTransaction(proceedingJoinPoint)判断通过,则transactionStatus不为null
            if (transactionStatus != null) {
                transactionUtils.commit(transactionStatus);
            }
        }
    
        /**
         * 判断切入点是否标注了@MyTransactional注解
         *
         * @param proceedingJoinPoint
         * @return
         */
        private boolean hasTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException {
            this.proceedingJoinPoint = proceedingJoinPoint;
            //获取方法名
            String methodName = proceedingJoinPoint.getSignature().getName();
            //获取方法所在类的class对象
            Class clazz = proceedingJoinPoint.getSignature().getDeclaringType();
            //获取参数列表类型
            Class[] parameterTypes = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterTypes();
            //根据方法名和方法参列各参数类型可定位类中唯一方法
            Method method = clazz.getMethod(methodName, parameterTypes);
            //根据方法对象获取方法上的注解信息
            MyTransactional myTransactional = method.getAnnotation(MyTransactional.class);
            return myTransactional == null ? false : true;
        }
    
        @AfterThrowing(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
        public void handleTransactionRollback() throws NoSuchMethodException {
            if (hasTransaction(proceedingJoinPoint)) {
                //获取当前事务并回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
        }
    }
    

    Тест пройден!

Поведение распространения транзакции

Springаннотация транзакции@Transactionalимеет атрибутpropagationУказывает поведение распространения текущей транзакции.Необязательные значения:

Распространение транзакций происходит между несколькими транзакциями, значение по умолчанию равноREQUIRED

 Propagation propagation() default Propagation.REQUIRED;

Наиболее часто используется толькоREQUIREDа такжеREQUIRED_NEWДва варианта, нижеследующее только знакомит со смыслом двух, остальные можете попробовать сами.

Допустим есть такой сценарий, для стола заказовorderИмеется журнал заказовorder_logВ частности, используется для регистрации запросов для создания заказов. Требуется, чтобы каждый раз, когда запрашивается формирование заказа, запрос регистрировался независимо от того, успешно сформирован заказ или нет, то есть информация о запросе всегда будет вставленаorder_logскорее, чемOrderServiceВлияние контроля транзакций.

Таким образом, мы можем указать операцию вставки журналаREQUIRED_NEW, чтобы при вызовеaddOrderвызыватьaddLogпотому чтоaddOrderТранзакция уже открыта, и транзакция приостановлена ​​наaddLogСоздайте новую транзакцию, подобную этойaddLogНезависимо отaddOrderВне транзакции его откат не затронет.

@Service
public class OrderLogService{
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void addLog(){
        // recode request info
    }
}

@Service
public class OrderService{
    
    @Autowire
    OrderLogService orderLogService;
    
    @Transactional(rollbackFor = Exception.class)
    public void addOrder(){
        orderLogService.addLog();
        // generate order
        // ...
        int i = 1 / 0 ;
    }
}

иначе, если нетaddLogДобавьте транзакцию или установите поведение ее распространения по умолчанию.REQUIREDесли,addLogЛогика будет такой же, какaddOrderЛогика в логе та же транзакция.Как только в процессе формирования ордера возникнет исключение, лог вставки тоже будет откатываться вместе.