봄 환경에서 트랜잭션 관리를 MyBatis로

MyBatis로 디자인 아이디어는 매우 간단 JDBC에 대한 패키지로 간주하고 강력한 동적 SQL 맵핑 기능을 제공 할 수있다. 그러나 자체가 일부 캐시, 트랜잭션 관리 및 기타 기능을 가지고 있으므로 실제 사용은 여전히 ​​몇 가지 문제가 발생하기 때문에 -, 또한 JFinal, 자신의 생각의 최근 노출과 유사한 최대 절전 모드,하지만 더 간결하고, 다른 디자인 아이디어를 MyBatis로, - :하지만 같은 일이 성능에 관해서는, 몇 시간 전에 두 가지 문제가 발생하여 모두가 컴팩트 한 디자인으로 개발을 단순화 할 성능 극대화하고 개선하기 위해

  1. 최고 방법의 레코드 (위 DAO 방식)를 삭제 한 후 같은 삽입 기본 키를 녹음 할 때, 그리고 기본 키 위반 오류를보고합니다.
  2. 일부 프로젝트의 DAO 방법은 평균 실행 시간은 다른 프로젝트의 수있을 것 두 번 .

첫 번째 문제는, 두 DAO 이전에 첫 번째보다는 두 개의 서로 다른 연결, 두 번째 문을 가지고있을 것으로 추정된다 때때로 MyBatis로 로직의 분석을 통해, 실험 환경을 재현 할 수있는 경우에 나타날 것입니다 제출, 그것은 추가 분석 및 검증중인 기본 키 위반했다. 두 번째 질문의 경우, 우리는 주로 다음과 같은 관련 소스 코드와 실험을 분석하여 근본 원인을 찾기 위해 노력할 것입니다 :

  1. 문제 설명 및 분석
  2. 봄 환경에서의 MyBatis로드 프로세스
  3. 봄에서 환경 문제의 MyBatis로 관리
  4. 실험 검증

환경 프로젝트

전체 시스템이 여기에 설명 마이크로 서비스 아키텍처이다 "프로젝트는"별도의 서비스를 의미합니다. 다음과 같이 하나의 프레임은 실질적으로 봄 + MyBatis로, 특정 버전의 프로젝트입니다 :

스프링 3.2.9 / 4.3.5 + MyBatis로 3.2.6 +는 MyBatis로 스프링 1.2.2 + MySQL의 커넥터 5.1.20 + 평민 - DBCP 1.4

다음과 같이 그리고의 MyBatis 구성과 관련된 거래는 다음과 같습니다

// 코드 1. 
<! - # 빈 (bean) 1 -.> 
 <빈 ID = "DataSource에"클래스 = "org.apache.commons.dbcp.BasicDataSource" 
        소멸-방법 = "닫기"> 
        <! - 일부 데이터베이스 구성 정보 -> 
        <! - 일부 DBCP 연결 풀 구성 -> 
         // 설정이 자동으로 제출되는 경우 
        <속성 이름 = "defaultAutoCommit"값 = "$ {dbcp.defaultAutoCommit}"/> 
  </ 빈을> 
<! - # 2 빈 -> 
  <콩 ID = "SqlSessionFactory는"클래스 = "org.mybatis.spring.SqlSessionFactoryBean"> 
        <속성 이름 = "DataSource에"REF = "DataSource에"/> 
        <속성 이름 = "를 mapperLocations"값 = "CLASSPATH * :. 경로 /로 / 매퍼 / ** / * XML "/>
  </ 콩> 
<! - 콩 # 3 -> 
  <콩 ID = "의 transactionManager"
        클래스 = "org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <속성 이름 = "는 dataSource"REF = "는 dataSource"/> 
  </ 빈> 
<! - 콩 # 4 -> 
  <bean 클래스 = "org.mybatis .spring.mapper.MapperScannerConfigurer "> 
        <속성 이름 ="basePackage "값 ="path.to.mapper "/>. 
        <속성 이름 ="sqlSessionFactoryBeanName "값 ="SqlSessionFactory는 "/> 
  </ 빈> 
 <! - bean5 -> 
  <TX : 주석 기반의 트랜잭션 매니저 = "의 transactionManager"/>

문제 설명 및 분석

매우 다를 것이다 일반적으로, 각 호출에 전체 성능의 결과로 인해 통화의 수 많은 거의 20 MS에 천천히 약 6-10 몇 밀리 초, 평균에 매우 심각한 사이의 시간 차이를 두 배로. 이러한 몇 가지 항목보다 더 조심 후, 우리는 defaultAutoCommit 구성에서 프로젝트의 데이터 소스 구성 DAO 느린 구현 (콩 # 1) 거짓 것을 발견했다. 그리고이 구성 후 실제 정상으로 복원합니다.

추론의 MyBatis는 "비 자동 제출"문 실행에 의해, 대기, 또는 데이터베이스 증가에 대한 API 호출의 실제 수로 이어지는 두 번 이상 제출합니다. 그러나이 추론은 전체 프로젝트가 봄 환경에서 실행되기 때문에 문제가,도, 또한 Spring의 트랜잭션 관리, 그들은 아직도 완전히 해결 될 수 있으며, DAO 방법과 관리 서비스를 어떻게 조립되어 결국 MyBatis로에서 자세히 볼 필요가 열립니다 신비.

문제를 재현하는 방법

첫째, 데이터베이스에 두 개의 레코드를 삽입합니다), 두 가지 방법이 두 번 같은 매퍼 클래스입니다 insertModelList을 (호출 서비스, delModels을 () 메서드는 다음과 같이 두 가지 기록을 삭제 쓰기 :

//代码2 
// @ 트랜잭션 
공공 무효 고환 () { 
    목록 <모델> 모델 = 새로운 ArrayList를 <> (); 
    //省略一些数据工作... 
    modelMapper.insertModelList (5만1리터, 모델); 
    modelMapper.delModels (50001); 
    경우 (CollectionUtils.isNotEmpty (모델)) 
        modelMapper.insertModelList (50001 모델); 
    modelMapper.delModels (50001); 
} 
공공 무효 testOther () { 
    에서 System.out.println ( "加载类:"); 
    에서 System.out.println (modelMapper.getClass () getClassLoader를 ().); 
    modelMapper.delModels (50001); 
}

실행 시간 통계를 사용하는 실제 프로젝트 고양이도 고양이, AOP의 계산 시간을 구현하는 별도의 클래스를 사용하여이 모델링 :

//代码3 
공용 클래스 DaoTimeAdvice { 

  개인 오랜 시간 = 0; 
  개인용 긴 NUM = 0; 

  공공 객체 calcTime (ProceedingJoinPoint joinpoint를가)의 Throwable {던졌습니다 
    긴 다음 = System.nanoTime을 (); 
    Object 객체 = joinPoint.proceed (); 
    긴 지금 System.nanoTime = (); 
    setTime (다음 getTime () + (지금 후)); 
    setNum (getNum () + 1); 
    객체를 반환; 
  } 
  //省略게터 및 세터 ... 
  공공 무효 printInfo () { 
    에서 System.out.println ( "总共次数:"+ NUM); 
    에서 System.out.println ( "总共时间:"+ 시간); 
    에서 System.out.println ( "平均时间:"+ 시간 / NUM); 
  } 
}

테스트 코드 :

//代码4 
공공 정적 무효 시험 () { 
    에서 System.out.println (SimpleDateFormat의 새로운 ( "[YYYY-MM-DD HH : MM : SS]"). 포맷 (새 날짜 ()) 
            ! "开始测试"+) ; 
    {위해 (; 나는 TEST_NUM <I는 I = 0 ++ INT) 
        ItemStrategyServiceTest IST = (ItemStrategyServiceTest) context.getBean ( "IST를 ')를; 
        ist.testIS (); 
        (I ~ 1000 == 0) {경우 
            에서 System.out.println ( "1000次"); 
        } 
    } 
    DaoTimeAdvice 광고 = (DaoTimeAdvice) context.getBean ( "daoTimeAdvice"); 
    ad.printInfo (); 
    ItemStrategyServiceTest IST = (ItemStrategyServiceTest) context.getBean ( "IST를"); 
    ist.testOther (); 
    System.exit와 (1);

테스트 결과 :

defaultAutoCommit 사이클 총 경과 시간 (ns) 평균 시간 (ns)
참된 40000 17831088316 445777
참된 40000 17881589992 447039
그릇된 40000 27280458229 682011
그릇된 40000 27237413893 680935

defaultAutoCommit는 거짓이 약 1.5 배의 시간 소비를 재생하지 2 배에 해당하는 실행 시간 들어, 고양이 또는 다른 통계 AOP를 수행 할 때, 따라서 거짓 참 사이 확장 소비하는 다른 방법이 있다는 것을 추정 차이.

봄 환경에서의 MyBatis로드 프로세스

제 프로필에 따라, 빈은 전체 어셈블리는 DAO되어야 MyBatis로 :

  1. BasicDataSource의 조립체는 제 1 데이터 소스 콩, 원본, 이름 (1 콩 #)을 사용.

    이 콩은 인스턴스화 봄의 컨텍스트에 등록되어있는, 매우 간단합니다.

  2. 콩이 생성되고 해결 될 때 데이터 소스 사용 SqlSessionFactory는을 만드는 (콩 # 2) 문의 MyBatis 매핑 파일을 검색합니다.

    MyBatis로에서 실제 데이터베이스는 읽기 및 쓰기 작업을 실시 예에 의해 SQLSESSION을 달성하고, SQLSESSION SqlSessionFactory는 관리 할 수 ​​있습니다. org.mybatis.spring.SqlSessionFactoryBean 여기의 FactoryBean 클래스를 실현, 소스 코드를 표시, 봄 것 정말 SqlSessionFactory는이 빈의 인스턴스를 얻는 실제 수익을 (이 클래스는, 여기를 반복하지 상관없이 주제, 특별) 객체 DefaultSqlSessionFactory의 인스턴스이다.

  3. 팩토리 클래스 SqlSessionFactory는 매퍼 스캐너를 만드는 데 사용 (콩 # 4), 및 DAO를 포함하는 인스턴스 메서드를 만듭니다.

    위 방법의 순서를 사용할 수 있습니다 DAO 방법은 통상의 방법에 의해 호출되는 경우 해당 빈에 등록 된 컨텍스트를 봄해야하지만 MyBatis로의 일반적인 사용 시나리오에서 매핑 주석 또는 XML에 의해 더 매퍼의 구현 클래스 (특정 SQL 문입니다 ) 달성 파일 만이 인터페이스는 MyBatis로 이러한 인터페이스는 동적 프록시에 의해 구현된다. 본 명세서에서 사용 된 바와 같이, 클래스는 org.mybatis.spring.mapper.MapperScannerConfigurer입니다 org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor 모든 등록이 완료의 그것은, 모든 스프링 빈 정의에서 "입니다 인터페이스를 구현하지만 아직 인스턴스화되지 "Spring 컨텍스트 등록 매퍼에있어서 구현 클래스 (동적 프록시 객체)를 호출하기 전에. 특정 코드는 다음과 같이 :

    //代码5 
     @Override 
     공공 무효의 postProcessBeanDefinitionRegistry (BeanDefinitionRegistry에 레지스트리) { 
       경우 (this.processPropertyPlaceHolders) { 
         processPropertyPlaceHolders (); 
       } 
    
       ClassPathMapperScanner 스캐너 = 새로운 ClassPathMapperScanner (레지스트리); 
       //设置一些属性
    
       scanner.scan (StringUtils.tokenizeToStringArray (this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); 
     } 
    
     / ** 
    * 지정된 기본 패키지 내에 검사를 수행합니다. 
    파라미터 : basePackages 패키지가 주석 클래스를 확인하기 
    * 콩 @return 등록 번호 
    * /  
     {(... basePackages를 문자열) 공공 INT 검사를
       INT beanCountAtScanStart this.registry.getBeanDefinitionCount = ();
    
       doScan (basePackages); 
    
       // 필요한 경우 주석 설정 프로세서를 등록합니다. 
       (this.includeAnnotationConfig) {경우 
         AnnotationConfigUtils.registerAnnotationConfigProcessors (this.registry); 
       } 
    
       리턴 (this.registry.getBeanDefinitionCount () - beanCountAtScanStart); 
     }

    소스 코드에서 볼 수있는, 실제 구현 클래스 매퍼에서 org.mybatis.spring.mapper.MapperFactoryBean <개체>, 프로세스 org.mybatis.spring.mapper.ClassPathMapperScanner.processBeanDefinitions에서 특정 로직 (설정 <BeanDefinitionHolder>)입니다 . 마지막으로, 각 방법의 구현, 결국 org.mybatis.spring.SqlSessionTemplate의 특정 방법으로 떨어지는, 그리고 인터셉터는 다음을 차단 :

    //代码6 
       / ** 
        * 프록시가 적절한 SQLSESSION에있어 호출 경로의 MyBatis 방법에 필요한 
        Spring의 트랜잭션 관리에서 * 
        * 또한 {@code 방법 #의 호출 (객체, 객체 ...)}에 의해 던져진 예외를 펼쳤다 
        * 패스 은 {@code의 PersistenceExceptionTranslator}에 {@code의 PersistenceException}. 
        * / 
     개인 클래스의 InvocationHandler SqlSessionInterceptor이 구현은 { 
     @Override 
     의 Throwable를 발생 공용 객체 호출 (개체 프록시 방법에있어서, [] args를 개체) { 
       SQLSESSION SQLSESSION = getSqlSession ( 
           SqlSessionTemplate.this.sqlSessionFactory, 
           SqlSessionTemplate.this.executorType, 
           SqlSessionTemplate.this.exceptionTranslator );
       {시도 
         개체 결과 = method.invoke (SQLSESSION, 인수를); 
         (! isSqlSessionTransactional (SQLSESSION, SqlSessionTemplate.this.sqlSessionFactory)) {경우 
           // 힘도 아닌 더러운 세션에 커밋 일부 데이터베이스가 필요하기 때문에 
           가까운 ()를 호출하기 전에 COMMIT / ROLLBACK을 // 
           (참) sqlSession.commit을; 
         } 
         반환 결과; 
       } 캐치 (의 Throwable t) { 
         //省略一些错误处理것은 
         풀어 던져; 
       {} 마지막 
         경우 (SQLSESSION = NULL!) { 
           closeSqlSession (SQLSESSION, SqlSessionTemplate.this.sqlSessionFactory); 
         } 
       } 
     } 
     }
  4. 봄에서 환경 문제의 MyBatis로 관리

    트랜잭션 관리가 org.mybatis.spring.transaction.SpringManagedTransactionFactory를 사용하여, 예 같은 시간을 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 사용하여 실시간 SqlSessionFactory는의 소스 코드를 알 수 있습니다. 그러나 구성 코드 1에서, 또한 서비스 방법에 @Transactional 주석 (또는 스캔 할 수있는 다른 방법)으로, 즉, Spring 트랜잭션 관리를 구성하기 위해 추가, 다음 Spring의 트랜잭션 관리는 자동으로 생성됩니다 거래는 다음 사이에 거래를 MyBatis로와 협력은 어떻게입니까?

    방법은 Spring 트랜잭션 상단 코드가있는 경우, 아래 실행되지 않습니다 여부를 반환 코드 6 isSqlSessionTransactional ()에서 볼 수 커밋 (). 내 프로젝트의 실제 상황은 그렇게 확실히 코드를 찾습니다) (커밋) (커밋 다음,이 방법은 결국 떨어졌다 SpringManagedTransactionFactory에 와서, Spring 트랜잭션없이 :

    //代码7 
     개인 공극 대해서 openConnection ()이 발생되는 SQLException { 
       this.connection에 DataSourceUtils.getConnection = (this.dataSource)를; 
       this.autoCommit this.connection.getAutoCommit = (); 
       this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional (this.connection, this.dataSource); 
    
     } 
     공개 무효가) (투입하면되는 SQLException {발생 
       하는 경우 (this.connection! = NULL &&! this.isConnectionTransactional &&! this.autoCommit) { 
         경우 (LOGGER.isDebugEnabled ()) { 
           LOGGER.debug는 ( "JDBC 연결 [커밋"+ 이것을 .connection + "]"); 
         } 
         this.connection.commit (); 
       } 
     }

    그것은 당신이 작업이 데이터 소스 자동 커밋이 false 인 경우, 결과는 확실히 사실, 세 가지 변수에 의해 결정된다) (커밋 실행하고자하는 경우 여기를 볼 수 있습니다, 콘솔 라인 로그 표시됩니다 JDBC 연결을 커밋을 [XXXXXX] 그냥 프로젝트에서 발생하는 경우처럼. 이 작업은 제출 및 데이터베이스 상호 작용에 더 많은 시간을 소비해야합니다.

실험 검증

마지막 분석, DAO 방식의 실행 시간의 원인은 위 방법을 호스팅하는 경우, 더 커밋 것 이상 Spring 트랜잭션 관리자 (또는 데이터 소스 defaultAutoCommit는이 조건의 시작 부분에 이미 사실이다 검증 재생 문제) MyBatis로의 동작은 DAO 방법 정상적으로 각각의 실행 시간은 짧아지고, 지르지. 방법 플러스 @Transactional 서비스 노트 그래서, 테스트 케이스은 true 및 false입니다. 결과 :

우리는 때문에 이러한 이유에 거의 확실하다, 실행 시간은 기본적으로 가까이되었습니다 볼 수 있습니다. 이 퍼즐의 수는 특히 당신이 다른 생각이있는 경우 문제가 다시 발생하면 두 배 소모 시간에, 여전히뿐만 아니라 논의하기 위해 환영합니다.

추천

출처www.linuxidc.com/Linux/2019-08/159829.htm