MyBatis 의 Spring 환경 에서 의 사무 관리

MyBatis 의 디자인 사상 은 매우 간단 하여 JDBC 에 대한 패키지 로 볼 수 있 고 강력 한 동적 SQL 맵 기능 을 제공 합 니 다.그러나 그 자체 에 도 캐 시,사무 관리 등 기능 이 있 기 때문에 실제 사용 할 때 문제 가 생 길 수 있 습 니 다.또한 최근 에 JFinal 을 접 했 는데 그 사상 은 Hibernate 와 유사 하지만 더욱 간결 해 야 합 니 다.MyBatis 의 디자인 사상 과 다 르 지만 똑 같은 점 이 있 습 니 다.모두 간결 한 디자인 을 통 해 개발 과 성능 을 최대한 간소화 하고 향상 시 키 려 고 합 니 다.얼마 전에 두 가지 문제 에 부 딪 혔 습 니 다.
1.상부 방법(DAO 방법의 상부)에서 기록 하 나 를 삭제 한 다음 같은 메 인 키 의 기록 을 삽입 할 때 메 인 키 가 충돌 하 는 오 류 를 보고 합 니 다.
2.일부 항목 의 DAO 방법 은 평균 실행 시간 이 다른 항목 의 2 배 에 달 합 니 다.
첫 번 째 문 제 는 실험 환경 에서 도저히 재현 되 지 않 는 다 는 것 이다.MyBatis 의 논 리 를 분석 한 결과 두 DAO 가 각각 두 개의 서로 다른 Connection 을 받 았 고 두 번 째 문 구 는 첫 번 째 보다 일찍 제출 되 어 메 인 키 가 충돌 하여 한 단계 더 분석 하고 검증 해 야 한다.두 번 째 문제 에 대해 본 고 는 소스 코드 와 실험 을 통 해 루트 cause 를 찾 으 려 고 시도 할 것 이다.주로 다음 과 같은 내용 과 관련된다.
1.문제 설명 과 분석
2.MyBatis 가 Spring 환경 에서 불 러 오 는 과정
3.MyBatis 의 Spring 환경 에서 의 사무 관리
4.실험 검증
프로젝트 환경
전체 시스템 은 마이크로 서비스 구조 로 여기 서 토론 하 는'프로젝트'는 단독 서 비 스 를 말한다.단일 항목 의 프레임 워 크 는 기본적으로 Spring+MyBatis 이 고 구체 적 인 버 전 은 다음 과 같 습 니 다.
Spring 3.2.9/4.3.5 + Mybatis 3.2.6 + mybatis-spring 1.2.2 + mysql connector 5.1.20 + commons-dbcp 1.4
MyBatis 와 트 랜 잭 션 에 대한 설정 은 다음 과 같 습 니 다.

//  1
<!-- bean#1-->
 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
  destroy-method="close">
  <!--          -->
  <!--   DBCP      -->
   //           
  <property name="defaultAutoCommit" value="${dbcp.defaultAutoCommit}" />
 </bean>
<!-- bean#2-->
 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mapperLocations" value="classpath*:path/to/mapper/**/*.xml" />
 </bean>
<!-- bean#3 -->
 <bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
 </bean>
<!-- bean#4-->
 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value=".path.to.mapper" />
  <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
 </bean>
 <!-- bean5 -->
 <tx:annotation-driven transaction-manager="transactionManager" />
문제 설명 과 분석
한 배의 시간 차 는 매우 심각 하 다.매번 호출 될 때 까지 평균 적 으로 6~10 몇 ms 이 고 느 린 것 은 20 ms 에 가깝다.호출 횟수 가 많 기 때문에 전체적인 성능 에 큰 차이 가 있 을 것 이다.이 몇 가지 항목 을 자세히 비교 한 결과 DAO 가 느 린 항목 의 데이터 원본 설정(bean\#1)에서 default AutoCommit 의 설정 이 모두 false 인 것 을 발견 하 였 습 니 다.그리고 이 설정 을 트 루 로 바 꾼 후 정상 으로 돌 아 왔 습 니 다.
이 를 통 해 MyBatis 가'비 자동 제출'문 구 를 실행 할 때 대기 하거나 한 번 더 제출 해 실제 데이터베이스 API 호출 횟수 가 늘 어 난 것 으로 추정 된다.그러나 이 추정 에 도 문제 가 있다.전체 프로젝트 가 Spring 환경 에서 운영 되 고 Spring 의 사무 관리 도 열 렸 기 때문에 MyBatis 가 DAO 방법 과 관리 업 무 를 어떻게 조립 하 는 지 자세히 살 펴 봐 야 수수 께 끼 를 풀 수 있다.
문제 가 재현 되다
먼저 Service 를 작성 합 니 다.그 중에서 같은 mapper 류 의 두 가지 방법 을 각각 2 번 호출 했 습 니 다.insert ModelList()는 데이터베이스 에 두 개의 기록 을 삽입 합 니 다.delModels()방법 은 이 두 개의 기록 을 삭제 합 니 다.코드 는 다음 과 같 습 니 다.

//  2
//@Transactional
public void testIS(){
 List<Model> models= new ArrayList<>();
 //        。。。
 modelMapper.insertModelList(50001l, models);
 modelMapper.delModels(50001);
 if (CollectionUtils.isNotEmpty(models))
  modelMapper.insertModelList(50001, models);
 modelMapper.delModels(50001);
}
public void testOther(){
 System.out.println("   :");
 System.out.println(modelMapper.getClass().getClassLoader());
 modelMapper.delModels(50001);
}
실제 프로젝트 에서 cat 를 사용 하여 실행 시간의 통 계 를 실시 합 니 다.여기 서도 cat 를 본 떠 서 하나의 단독 AOP 류 를 사용 하여 시간 을 계산 합 니 다.

//  2
//@Transactional
public void testIS(){
 List<Model> models= new ArrayList<>();
 //        。。。
 modelMapper.insertModelList(50001l, models);
 modelMapper.delModels(50001);
 if (CollectionUtils.isNotEmpty(models))
  modelMapper.insertModelList(50001, models);
 modelMapper.delModels(50001);
}
public void testOther(){
 System.out.println("   :");
 System.out.println(modelMapper.getClass().getClassLoader());
 modelMapper.delModels(50001);
}
테스트 코드:

//  4
public static void test(){
 System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date())
   + "     !");
 for (int i = 0; i < TEST_NUM; i++) {
  ItemStrategyServiceTest ist = (ItemStrategyServiceTest) context.getBean("isTS");
  ist.testIS();
  if (i % 1000 == 0) {
   System.out.println("1000 ");
  }
 }
 DaoTimeAdvice ad = (DaoTimeAdvice) context.getBean("daoTimeAdvice");
 ad.printInfo();
 ItemStrategyServiceTest ist = (ItemStrategyServiceTest) context.getBean("isTS");
 ist.testOther();
 System.exit(1);
}
테스트 결과:
defaultAutoCommit
순환 횟수
총 소모 시간(ns)
평균 시간(ns)
true
40000
17831088316
445777
true
40000
17881589992
447039
false
40000
27280458229
682011
false
40000
27237413893
680935
default AutoCommit 가 false 일 때 실행 시간 은 true 의 1.5 배 에 가 깝 고 2 배의 시간 소 모 를 재현 하지 않 았 으 며 cat 통계 나 다른 AOP 방법 을 실행 할 때 다른 소모 가 있 을 것 으로 추정 되 어 false 와 true 간 의 차 이 를 확대 했다.
MyBatis Spring 환경 에서 불 러 오 는 과정
1 절 에 있 는 설정 파일 에 따 르 면 전체 MyBatis 에서 DAO 의 bean 조립 은 다음 과 같 아야 합 니 다.
1.먼저 Basic DataSource 를 사용 하여 데이터 원본 의 bean(bean\#1)을 설치 하고 이름 은 dataSource 입 니 다.
이 bean 은 매우 간단 하 다.바로 실례 화 되 어 Spring 의 상하 문 에 등 록 된 것 이다.
2.dataSource 를 사용 하여 sqlSessionFactory(bean#2),이 bean 을 만 들 때 MyBatis 의 구문 맵 파일 을 검색 하고 해석 합 니 다.
MyBatis 에서 진정한 데이터베이스 읽 기와 쓰기 동작 은 SqlSession 의 실례 를 통 해 이 루어 지고 SqlSession 은 SQLSession Factory 를 통 해 관리 해 야 한다.여기org.mybatis.spring.SqlSessionFactoryBean는 Factory Bean 류(이 종 류 는 특수 합 니 다.주제 와 상 관 없 이 더 이상 군말 하지 않 습 니 다)를 실현 합 니 다.Spring 은 이 bean 에서 진정한SQLSessionFactory인 스 턴 스 를 얻 을 수 있 습 니 다.소스 코드 에 따 르 면 실제 되 돌아 오 는 대상 은 DefaultSqlSession Factory 의 인 스 턴 스 입 니 다.
3.sqlSessionFactory이 공장 클래스 를 사용 하여 mapper 스캐너(bean\#4)를 만 들 고 DAO 방법 이 포 함 된 인 스 턴 스 를 만 듭 니 다.
상부 방법 이 일반적인 방법 으로 DAO 방법 을 사용 할 수 있 도록 Spring 컨 텍스트 에 해당 하 는 bean 을 등록 해 야 합 니 다.MyBatis 의 일반 사용 장면 에 서 는 mapper 의 실현 류 가 없습니다(구체 적 인 SQL 구문 맵 은 주석 이나 XML 파일 을 통 해 이 루어 집 니 다).인터페이스 만 있 고 MyBatis 에서 이 인 터 페 이 스 는 동적 대 리 를 통 해 이 루어 집 니 다.여기 서 사용 하 는 클래스 는org.mybatis.spring.mapper.MapperScannerConfigurer입 니 다. org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor인 터 페 이 스 를 실 현 했 기 때문에 Spring 에서'모든 bean 정의 가 모두 등록 되 었 으 나 예화 되 지 않 았 습 니 다'전에 호출 방법 은 Spring 컨 텍스트 에 mapper 실현 클래스(동적 에이전트 대상)를 등록 합 니 다.구체 적 인 코드 는 다음 과 같다.

 //  5
 @Override
 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
 if (this.processPropertyPlaceHolders) {
  processPropertyPlaceHolders();
 }

 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
 //      

 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
 }

 /**
* Perform a scan within the specified base packages.
* @param basePackages the packages to check for annotated classes
* @return number of beans registered
*/
 public int scan(String... basePackages) {
 int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

 doScan(basePackages);

 // Register annotation config processors, if necessary.
 if (this.includeAnnotationConfig) {
  AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
 }

 return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
 }
소스 코드 에서 볼 수 있 듯 이 진정한 mapper 실현 류 는 org.mybatis.spring.mapper.MapperFactoryBean<Object>,구체 적 인 논 리 는 방법 org.mybatis.spring.mapper.ClassPathMapperScanner.processBeanDefinitions(Set<BeanDefinitionHolder>)에 있다.마지막 으로 모든 방법의 집행 은 최종 적 으로 org.mybatis.spring.SqlSessionTemplate의 특정한 방법 에 떨 어 졌 고 다음 과 같은 차단기 에 의 해 차단 되 었 다.

//  6
 /**
 * Proxy needed to route MyBatis method calls to the proper SqlSession got
 * from Spring's Transaction Manager
 * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
 * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
 */
 private class SqlSessionInterceptor implements InvocationHandler {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 SqlSession sqlSession = getSqlSession(
  SqlSessionTemplate.this.sqlSessionFactory,
  SqlSessionTemplate.this.executorType,
  SqlSessionTemplate.this.exceptionTranslator);
 try {
  Object result = method.invoke(sqlSession, args);
  if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  // force commit even on non-dirty sessions because some databases require
  // a commit/rollback before calling close()
  sqlSession.commit(true);
  }
  return result;
 } catch (Throwable t) {
  //        
  throw unwrapped;
 } finally {
  if (sqlSession != null) {
  closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  }
 }
 }
 }
4.MyBatis 의 Spring 환경 에서 의 사무 관리
원본 코드 에서 알 수 있 듯 이 진정한 SqlSession Factory 는org.apache.ibatis.session.defaults.DefaultSqlSessionFactory의 인 스 턴 스 를 사용 하 는 동시에 사무 관리 사용org.mybatis.spring.transaction.SpringManagedTransactionFactory을 사용 합 니 다.그러나 코드 1 설정 에 Spring 사무 관리 설정 도 추가 되 었 습 니 다.즉,특정한 Service 방법(또는 스 캔 할 수 있 는 다른 방법)에@Transactional 주 해 를 추가 하면 Spring 의 사무 관 리 는 자동 으로 사 무 를 만 듭 니 다.그러면 MyBatis 와 어떻게 협력 합 니까?
코드 6 에 있 는 방법 isSqlSession Transactional()을 볼 수 있 습 니 다.상위 코드 에 Spring 이 있 는 지 여 부 를 되 돌려 줍 니 다.있 으 면 아래 commt()를 실행 하지 않 습 니 다.제 프로젝트 의 실제 상황 은 Spring 업무 가 없 기 때문에 아래 의 commt()에 갔 을 것 입 니 다.이 방법 은 결국SpringManagedTransactionFactory commit(),코드 를 보 았 습 니 다.

//  7
 private void openConnection() throws SQLException {
 this.connection = DataSourceUtils.getConnection(this.dataSource);
 this.autoCommit = this.connection.getAutoCommit();
 this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

 }
 public void commit() throws SQLException {
 if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
  if (LOGGER.isDebugEnabled()) {
  LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
  }
  this.connection.commit();
 }
 }
이 곳 에서commit()작업 을 수행 할 지 여 부 는 3 개의 변수 에 의 해 결 정 됩 니 다.DataSource autoCommit가 false 라면 그 결 과 는 반드시 true 이 고 콘 솔 에서 도 로 그 를 볼 수 있 습 니 다.Committeng JDBC Connection[xxxxx]은 프로젝트 에서 만난 상황 과 똑 같 습 니 다.이 제출 동작 은 데이터베이스 와 상호작용 을 해 야 하기 때문에 시간 이 비교적 걸린다.
실험 검증
이전 절 에서 분석 한 바 와 같이 DAO 방법의 실행 시간 이 길 어 지 는 이 유 는 제출 을 한 번 더 실행 하기 때 문 입 니 다.만약 에 상부 방법 이 Spring 사무 관리자 에 의 해 위탁 관리(또는 데이터 원본 의 default AutoCommit 가 true 이 고 이 조건 은 이미 시 작 된 문제 에서 다시 검증 되 었 습 니 다)되면 MyBatis 의 제출 동작 을 실행 하지 않 습 니 다.DAO 방법 은 그 에 따 른 실행 시간 이 짧 아 질 것 입 니 다.그래서 Service 방법 에@transactional 주 해 를 추가 하여 각각 true 와 false 의 상황 을 테스트 합 니 다.결과:

집행 시간 이 거의 가 까 워 졌 음 을 알 수 있 으 며 이 원인 이 원인 임 을 확인 할 수 있다.여기 에는 여전히 몇 가지 의문점 이 있다.특히 문제 가 재현 되 었 을 때 2 배의 시간 소모 가 없 었 다.다른 생각 이 있다 면 토론 을 하 는 것 도 환영한다.
총결산
위 에서 말 한 것 은 편집장 님 께 서 소개 해 주신 MyBatis 의 Spring 환경 에서 의 사무 관리 입 니 다.여러분 께 도움 이 되 셨 으 면 좋 겠 습 니 다.궁금 한 점 이 있 으 시 면 메 시 지 를 남 겨 주세요.편집장 님 께 서 바로 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!
만약 당신 이 본문 이 당신 에 게 도움 이 된다 고 생각한다 면,전 재 를 환영 합 니 다.번 거 로 우 시 겠 지만 출처 를 밝 혀 주 십시오.감사합니다!

좋은 웹페이지 즐겨찾기