트랜잭션 봄의 원리를 이해하여 깊이있는 텍스트

기본 원리 봄 업무

봄은 사실 트랜잭션에 대한 트랜잭션 데이터베이스 지원의 성격이며, 어떤 트랜잭션 데이터베이스 지원, 스프링 트랜잭션 기능을 제공 할 수 없습니다 않습니다. 순수 JDBC 데이터베이스 작업을 위해, 당신이 서비스를 사용하려면 다음 단계를 수행 할 수 있습니다 :

  1. 연결 연결 콘을 얻기 = DriverManager.getConnection를 ()

  2. 열기 거래 con.setAutoCommit (참 / 거짓의);

  3. CRUD 수행

  4. ) (/) (con.rollback를 트랜잭션 / 롤백 트랜잭션 con.commit 커밋;

  5. () 연결 conn.Close을 닫습니다;

Spring의 트랜잭션 관리 기능을 사용한 후, 우리는 2 단계 4에서 더 이상 쓰기 코드는하지만, Spirng에 의해 자동으로 수행되지 않습니다 수 있습니다. 우리가 쓰고 CRUD 세션 그것을 닫기 전에 어떻게 봄 거래 후 열? 이 문제를 해결하기 위해, 전체를 달성하기 위해 Spring의 트랜잭션 관리 원칙을 이해할 수있다.

간단한 설명, 예를 들어 주석 모드는 다음과 같은 :

  1. 식별 주석 @Transactional 관련 클래스와 메소드에, 구성 파일의 주석 드라이브를 엽니 다.

  2. 그것은 중요한 빈을 생성 파싱하기 시작 스프링 때, 이번에는 메모 관련된 클래스 및 메소드를 볼 수 있고, 이에 따라 분사 @Transaction 기반으로 이러한 클래스 및 메소드 및 구성 파라미터에 대한 프록시 에이전트를 생성 우리는 관련 문제를 처리하기 위해 (트랜잭션을 커밋 열린 정상의 트랜잭션이 비정상적으로 롤백됩니다).

  3. 실제 트랜잭션 커밋 및 롤백 데이터베이스 계층은 바이너리 로그 또는 리두 로그를 통해 얻을 수있다.

Spring의 트랜잭션 메커니즘

모든 데이터 액세스 기술은 트랜잭션이 데이터 조작, 데이터의 완료에 최선을 다하고 있습니다 또는 오류가 발생했을 때 롤백, 트랜잭션 처리, 이러한 기술 거래를 활성화하는 API를 제공합니다.

다른 데이터 액세스 기술을 처리하는 핸들 거래에 대한 통합 메커니즘 스프링의 트랜잭션 메커니즘. 표에 나타낸 바와 같이 스프링의 트랜잭션기구는 PlatformTransactionManager의 인터페이스, 다른 데이터 액세스 기술 인터페이스를 사용하여 다른 거래를 제공한다.

데이터 액세스 기술 및 구현

데이터 액세스 기술 실현
JDBC 의 DataSourceTransactionManager
JPA JapTransactionManager
최대 절전 모드 HibernateTransactionManager를
JDO JdoTransactionManager가
분산 트랜잭션 JtaTransactionManager를

코드는 다음과 같이 프로그램에서 트랜잭션 관리자가 정의합니다 :

@Bean
public PlatformTransactionManager transactionManager() {

    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setDataSource(dataSource());
    return transactionManager;
}

선언적인 트랜잭션

봄 선언적인 트랜잭션 지원, 사용자는 방법이 방법은 트랜잭션 지원을 요구하는 방법 쇼에 @Transactional 주석을 사용하여 주석을 사용하여 트랜잭션의 사용을 필요로 선택할 수 있습니다. 이것은 AOP 기반의 작업의 실현이다.

@Transactional
public void saveSomething(Long  id, String name) {
    //数据库操作
}

여기서 특히 주목할입니다 org.springframework.transaction.annotation 패키지 대신 javax.transaction의에서이 @Transactional 주석.

AOP 프록시 구현의 두 종류 :

  • JDK는,이 차단되지 에이전트 인터페이스는 전용 메소드는 인터페이스에 존재하지 않아야이고;

  • CGLIB는 서브 클래스, 개인 방법은 여전히 ​​서브 클래스에 표시되지 않습니다, 그것은 가로 챌 수 없습니다.

자바 동적 프록시.

특히 네 단계를 수행 :

  1. 자신의 통화 프로세서를 만들 수있는 인터페이스를 구현하여 InvocationHandler입니다;

  2. 프록시 클래스와 인터페이스 객체 및 클래스 로더의 세트를 지정하여 동적 프록시 클래스를 생성하는 단계;

  3. 동적 프록시 클래스 생성자가 반사함으로써 얻어지는 유일한 파라미터 타입은 호 프로세서 인터페이스 타입이고;

  4. 핸들러를 호출하도록 구성된 동적 프록시 클래스 인스턴스 생성자 객체를 생성하여 파라미터로서 전달된다.

GCLIB 에이전트

CGLIB (코드 생성 라이브러리) 강력한 고성능, 고품질의 코드 생성 라이브러리입니다. 그것은 런타임 Java 클래스에서 확장 및 Java 인터페이스를 구현 할 수 있습니다.

  • CGLIB 캡슐화 ASM은 동적으로 런타임에 새로운 클래스 (하위 클래스)를 생성합니다.

  • AOP에 대한 CGLIB는 프록시를 기반으로해야 인터페이스를 JDK, CGLIB는 이러한 제한이 없습니다.

구별의 원칙 :

반사를 이용하여 자바 동적 프록시 익명 프록시 클래스를 생성하는 프로세스를 구현하는 특정 방법을 호출하기 전에 인터페이스 호출의 InvokeHandler. 오픈 소스 패키지를 이용한 동적 프록시 CGLIB ASM을, 클래스, 서브 클래스 바이트 코드를 수정하여 가공에로드 프록시 객체 클래스 파일.

  1. 대상 객체 구현하는 경우 인터페이스, 그것은 JDK 동적 프록시 AOP 구현의 기본을 사용합니다

  2. 대상 객체가 인터페이스를 구현하는 경우, 당신은 AOP를 달성 CGLIB의 사용을 강제 할 수

  3. 인터페이스를 구현하지 않는 대상 객체가 CGLIB 라이브러리를 사용해야 할 경우, 스프링이 자동으로 JDK의 다이내믹 프록시와 변환 CGLIB 사이

클래스 내부 기관, 프록시의 자체 인스턴스를 유지하여이 시간을 갈 수있는 직접적인 방법이 아닌 경우.

@Service
public class PersonServiceImpl implements PersonService {
    @Autowired
    PersonRepository personRepository;

    // 注入自身代理对象,在本类内部方法调用事务的传递性才会生效
    @Autowired
    PersonService selfProxyPersonService;

    /**
     * 测试事务的传递性
     *
     * @param person
     * @return
     */
    @Transactional
    public Person save(Person person) {
        Person p = personRepository.save(person);
        try {
            // 新开事务 独立回滚
            selfProxyPersonService.delete();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            // 使用当前事务 全部回滚
            selfProxyPersonService.save2(person);
        } catch (Exception e) {
            e.printStackTrace();
        }
        personRepository.save(person);

        return p;
    }

    @Transactional
    public void save2(Person person) {
        personRepository.save(person);
        throw new RuntimeException();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void delete() {
        personRepository.delete(1L);
        throw new RuntimeException();
    }
}

Spring 트랜잭션 전파 특성

여러 개의 동시 트랜잭션이 때 소위 스프링 부동산 거래의 확산이 정의, 봄은 이러한 문제의 행동에 대처하는 방법이어야한다. 이러한 속성은 TransactionDefinition의 특정 상수는 아래 표에 설명 정의 :

상수 이름 상수 설명
PROPAGATION_REQUIRED 어떤 거래, 우리는 새로운 비즈니스를 작성하지 않는 경우, 현재 트랜잭션을 지원합니다. 가장 일반적인 선택뿐만 아니라 봄 기본 거래의 확산이다.
PROPAGATION_REQUIRES_NEW 현재의 트랜잭션 (transaction)가 존재하는 경우 새로운 트랜잭션, 현재의 트랜잭션 (transaction) 보류. 새로운 트랜잭션이 보류중인 트랜잭션이 결과는 내부 내부 트랜잭션 예외를 발생하지 트랜잭션 실행을 롤백 할 수 외부 트랜잭션이 실패한 후 롤백 두 개의 트랜잭션이다 외부 거래를 할 수 없다 캡처, 처리는 롤백하지 않을 수 있습니다
PROPAGATION_SUPPORTS 어떤 트랜잭션이 비 트랜잭션 방식으로 실행되지 않을 경우, 현재 트랜잭션을 지원합니다.
PROPAGATION_MANDATORY 현재의 트랜잭션을 지원하고, 트랜잭션 (transaction) 경우 예외를 throw합니다.
PROPAGATION_NOT_SUPPORTED 현재의 트랜잭션 (transaction)가 존재하는 경우, 보류 현재의 트랜잭션 (transaction)를 넣어, 비 트랜잭션 방식으로 작업을 수행합니다.
PROPAGATION_NEVER 현재의 트랜잭션 (transaction)가 존재하는 경우 비 트랜잭션 방식으로 수행, 예외가 발생합니다.
PROPAGATION_NESTED 액티브 한 트랜잭션 (transaction)가있는 경우, 중첩 된 트랜잭션에서 실행됩니다. 활성 트랜잭션이없는 경우를 눌러 필수 속성을 실행합니다. 이 점은 롤백 할 수 있습니다 저장 트랜잭션이 여러 가지고, 별도의 트랜잭션을 사용합니다. 롤백 내정 외부 업무에 영향을 미치지 않습니다. DataSourceTransactionManager를 트랜잭션 관리자 만 개시.

데이터베이스 격리 수준

격리 수준 분리 레벨 값 문제 발생
읽기 커밋 0 더러운 읽기에 리드
읽기 커밋 1 방지 더러운 읽기, 반복 할 수없는 수 읽기 및 팬텀 읽기
반복-읽기 방지 더러운 팬텀 읽기를 허용, 비 반복 읽기, 읽기
직렬화 읽기 직렬화 거래는 더티 읽기, 반복 불가능한 읽기, 팬텀 읽기를 방지하기 위해, 하나 하나를 실행할 수 있습니다. 효율성 천천히, 사용주의

트랜잭션 데이터가 추가 및 삭제했지만, 다른 트랜잭션이 커밋되지 않은 데이터를 읽을 수 제출하지 않은 : 더러운 읽기. 첫 번째 트랜잭션이 롤백되면이 때, 다음 두 번째 트랜잭션은 더티 데이터에 참석.

비 반복 판독하는 제 1 판독 동작 및 제 2 동작, 또한 수정 트랜잭션 데이터는 두 데이터가 일치하지 않는 판독이 시간 사이에 거래의 두 판독 동작을하고있다.

매직 읽기 : 첫 번째 트랜잭션이 새로운 데이터를 수정할 때 데이터의 범위에 대한 첫 번째 트랜잭션이 대량 편집이이 범위에 두 번째 트랜잭션 데이터를 추가 할 수 있도록, 그것은 손실됩니다.

요약 :

높은 격리 수준은, 더는 데이터의 무결성과 일관성을 보장 할 수 있지만, 동시 성능에 미치는 영향도 크다.

데이터베이스의 기본 격리 수준의 대부분은 SQLSERVER, Oracle과 같은 읽기 최선을 다하고있다

몇몇 데이터베이스의 기본 격리 수준 : 예를 들어 반복 읽기 : MySQL의 InnoDB의

봄 격리 수준

상수 설명
ISOLATION_DEFAULT 이는 PlatfromTransactionManager 기본 격리 수준 인 기본 데이터베이스 트랜잭션 격리 수준을 사용합니다. 각각 또한 JDBC 분리 레벨과 네.
ISOLATION_READ_UNCOMMITTED 이것은 다른 거래 비용을 약속하고이 데이터 커밋되지 않은 트랜잭션을 볼 수 있습니다, 가장 낮은 트랜잭션 격리 수준이다. 더러운 것이 격리 수준은 읽어 들여, 반복 불가의 읽어 들여 및 팬텀 읽습니다.
ISOLATION_READ_COMMITTED 거래 후 순서대로 제출 된 데이터를 다른 트랜잭션을 읽고 수정할 수 있도록합니다. 또 다른 트랜잭션이 커밋되지 않은 트랜잭션을 읽을 수 없습니다.
ISOLATION_REPEATABLE_READ 이 트랜잭션 격리 수준을 방지 더러운 비 반복 읽기, 읽습니다. 하지만 팬텀 읽기가 발생할 수 있습니다.
ISOLATION_SERIALIZABLE 이것은 가장 비싸지 만 가장 신뢰할 수있는 트랜잭션 격리 수준의 비용. 트랜잭션이 실행 순차적으로 처리된다.

중첩 된 트랜잭션

通过上面的理论知识的铺垫,我们大致知道了数据库事务和spring事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制。

假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()

PROPAGATION_REQUIRED(spring 默认)

如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。

假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW

比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。

那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。

他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。

PROPAGATION_SUPPORTS

假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED

现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

a、捕获异常,执行异常分支逻辑

void methodA() {

        try {

            ServiceB.methodB();

        } catch (SomeException) {

            // 执行其他业务, 如 ServiceC.methodC();

        }

    }

这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback

另外三种事务传播属性基本用不到,在此不做分析。

总结

对于项目中需要使用到事务的地方,我建议开发者还是使用spring的TransactionCallback接口来实现事务,不要盲目使用spring事务注解,如果一定要使用注解,那么一定要对spring事务的传播机制和隔离级别有个详细的了解,否则很可能发生意想不到的效果。

Spring Boot 对事务的支持

通过org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration类。我们可以看出Spring Boot自动开启了对注解事务的支持 Spring

只读事务(@Transactional(readOnly = true))的一些概念

  • 概念:从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。

@Transcational(readOnly=true) 这个注解一般会写在业务类上,或者其方法上,用来对其添加事务控制。当括号中添加readOnly=true, 则会告诉底层数据源,这个是一个只读事务,对于JDBC而言,只读事务会有一定的速度优化。

而这样写的话,事务控制的其他配置则采用默认值,事务的隔离级别(isolation) 为DEFAULT,也就是跟随底层数据源的隔离级别,事务的传播行为(propagation)则是REQUIRED,所以还是会有事务存在,一代在代码中抛出RuntimeException,依然会导致事务回滚。

  • 应用场合:

  1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;

  2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

【注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务】

References:

  1. http://www.codeceo.com/article/spring-transactions.html

  2. http://www.cnblogs.com/fenglie/articles/4097759.html

  3. https://www.zhihu.com/question/39074428/answer/88581202

  4. http://blog.csdn.net/andyzhaojianhui/article/details/51984157

发布了50 篇原创文章 · 获赞 1706 · 访问量 222万+

추천

출처blog.csdn.net/zl1zl2zl3/article/details/105372909