Spring 데이터베이스 읽 기와 쓰기 분 리 를 위 한 예제

현재 대형 전자상거래 시스템 은 데이터베이스 차원 에서 대부분 읽 기와 쓰기 분리 기술 을 사용 하 는데 그것 이 바로 Master 데이터베이스 이 고 여러 개의 Slave 데이터베이스 이다.Master 라 이브 러 리 는 데이터 업데이트 와 실시 간 데이터 조 회 를 담당 하고 Slave 라 이브 러 리 는 당연히 비 실시 간 데이터 조 회 를 책임 집 니 다.실제 응용 에서 데이터 베 이 스 는 다 중 쓰기(데 이 터 를 읽 는 빈도 가 높 고 데 이 터 를 업데이트 하 는 빈도 가 상대 적 으로 적다)이 고 데 이 터 를 읽 는 데 시간 이 오래 걸 리 며 데이터베이스 서버 를 차지 하 는 CPU 가 많아 사용자 체험 에 영향 을 주기 때문이다.우리 의 일반적인 방법 은 조 회 를 메 인 라 이브 러 리 에서 추출 하고 여러 개의 라 이브 러 리 를 사용 하 며 부하 균형 을 사용 하여 모든 라 이브 러 리 에서 의 조회 압력 을 줄 이 는 것 이다.
읽 기와 쓰기 분리 기술 의 목표:Master 라 이브 러 리 의 압력 을 효과적으로 줄 이 고 사용자 가 데 이 터 를 조회 하 는 요 구 를 서로 다른 Slave 라 이브 러 리 에 나 누 어 시스템 의 건장 성 을 확보 할 수 있 습 니 다.읽 기와 쓰기 가 분 리 된 배경 을 살 펴 보 자.
사이트 의 업무 가 계속 확대 되 고 데이터 가 계속 증가 하 며 사용자 가 점점 많아 지면 서 데이터 뱅 크 의 부담 도 점점 커지 고 전통 적 인 방식 을 사용한다.예 를 들 어 데이터 뱅 크 나 SQL 의 최적화 는 대체적으로 요구 에 이 르 지 못 한다.이 럴 때 읽 기와 쓰기 분리 전략 으로 현황 을 바 꿀 수 있다.
구체 적 으로 개발 과정 에서 어떻게 읽 기와 쓰기 의 분 리 를 편리 하 게 실현 할 수 있 습 니까?현재 자주 사용 하 는 방법 은 두 가지 가 있다.
1.첫 번 째 방식 은 우리 가 가장 자주 사용 하 는 방식 이다.바로 2 개의 데이터 베이스 연결 을 정의 하 는 것 이다.하 나 는 MasterDataSource 이 고 다른 하 나 는 SlaveDataSource 이다.데 이 터 를 업데이트 할 때 MasterDataSource 를 읽 고 데 이 터 를 조회 할 때 SlaveDataSource 를 읽 습 니 다.이런 방식 은 매우 간단 해서 나 는 군말 하지 않 겠 다.
2.두 번 째 방식 은 동적 데이터 원본 전환 입 니 다.프로그램 이 실 행 될 때 데이터 원본 을 프로그램 에 동적 으로 짜 서 메 인 라 이브 러 리 를 읽 을 지,라 이브 러 리 에서 읽 을 지 선택 하 는 것 입 니 다.주로 사용 되 는 기술 은 annotation,Spring AOP,반사 입 니 다.실현 방식 을 상세히 소개 하 겠 습 니 다.
실현 방식 을 소개 하기 전에 필요 한 지식,spring 의 AbstractRouting DataSource 류 를 준비 합 니 다.
AbstractRouting DataSource 라 는 종 류 는 spring 2.0 이후 에 추 가 된 것 입 니 다.먼저 AbstractRouting DataSource 의 정 의 를 살 펴 보 겠 습 니 다.

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {}
AbstractRouting DataSource 는 AbstractDataSource 를 계승 하고 AbstractDataSource 는 DataSource 의 하위 클래스 입 니 다.DataSource 는 javax.sql 의 데이터 원본 인터페이스 로 다음 과 같이 정의 합 니 다.

public interface DataSource extends CommonDataSource,Wrapper {

 /**
  * <p>Attempts to establish a connection with the data source that
  * this <code>DataSource</code> object represents.
  *
  * @return a connection to the data source
  * @exception SQLException if a database access error occurs
  */
 Connection getConnection() throws SQLException;

 /**
  * <p>Attempts to establish a connection with the data source that
  * this <code>DataSource</code> object represents.
  *
  * @param username the database user on whose behalf the connection is
  * being made
  * @param password the user's password
  * @return a connection to the data source
  * @exception SQLException if a database access error occurs
  * @since 1.4
  */
 Connection getConnection(String username, String password)
  throws SQLException;

}

DataSource 인 터 페 이 스 는 데이터베이스 연결 을 가 져 오 는 두 가지 방법 을 정의 합 니 다.AbstractRouting DataSource 가 DataSource 인 터 페 이 스 를 어떻게 실현 하 는 지 살 펴 보 겠 습 니 다.

public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
  }

  public Connection getConnection(String username, String password) throws SQLException {
    return determineTargetDataSource().getConnection(username, password);
  }

자신의 determineTargetDataSource()방법 으로 connection 을 얻 은 것 이 분명 하 다.determineTargetDataSource 방법 은 다음 과 같이 정의 합 니 다.

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
      dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
      throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
  }
우리 가 가장 관심 을 가 지 는 것 은 역시 다음 두 마디 이다.

Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
determineCurrentLookupkey 방법 은 lookupKey 를 되 돌려 줍 니 다.resolvedDataSources 방법 은 lookupKey 에 따라 맵 에서 데이터 원본 을 얻 는 것 입 니 다.resolvedDataSources 와 determineCurrentLookupkey 는 다음 과 같이 정의 합 니 다.

  private Map<Object, DataSource> resolvedDataSources;

  protected abstract Object determineCurrentLookupKey()
상기 정 의 를 보면 우 리 는 생각 이 좀 있 지 않 습 니까?resolvedDataSources 는 Map 유형 입 니 다.우 리 는 MasterDataSource 와 SlaveDataSource 를 Map 에 저장 할 수 있 습 니 다.다음 과 같 습 니 다.
key 
value
master
MasterDataSource
slave
SlaveDataSource
저 희 는 DynamicDataSource 가 AbstractRouting DataSource 를 계승 하여 determineCurrentLookupkey()방법 을 실현 하고 있 습 니 다.이 방법 은 Map 의 key,master 또는 slave 를 되 돌려 줍 니 다.
자,이렇게 많은 말 을 했 으 니 좀 짜증 이 난다.다음은 어떻게 실현 하 는 지 보 자.
위 에서 이미 우리 가 사용 해 야 할 기술 을 언급 하 였 으 니,우 리 는 먼저 annotation 의 정 의 를 살 펴 보 자.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
  String value();
}
우 리 는 spring 의 추상 적 인 추상 류 AbstractRouting DataSource 를 실현 해 야 합 니 다.바로 determineCurrentLookupkey 를 실현 하 는 방법 입 니 다.

public class DynamicDataSource extends AbstractRoutingDataSource {

  @Override
  protected Object determineCurrentLookupKey() {
    // TODO Auto-generated method stub
    return DynamicDataSourceHolder.getDataSouce();
  }

}


public class DynamicDataSourceHolder {
  public static final ThreadLocal<String> holder = new ThreadLocal<String>();

  public static void putDataSource(String name) {
    holder.set(name);
  }

  public static String getDataSouce() {
    return holder.get();
  }
}

DynamicDataSource 의 정 의 를 보면 그 가 돌아 온 것 은 DynamicDataSourceHolder.getDataSouce()값 입 니 다.프로그램 이 실 행 될 때 DynamicDataSourceHolder.putDataSource()방법 을 호출 하여 값 을 부여 해 야 합 니 다.다음은 우리 가 실현 하 는 핵심 부분,즉 AOP 부분 입 니 다.DataSourceAspect 의 정 의 는 다음 과 같 습 니 다.

public class DataSourceAspect {

  public void before(JoinPoint point)
  {
    Object target = point.getTarget();
    String method = point.getSignature().getName();

    Class<?>[] classz = target.getClass().getInterfaces();

    Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
        .getMethod().getParameterTypes();
    try {
      Method m = classz[0].getMethod(method, parameterTypes);
      if (m != null && m.isAnnotationPresent(DataSource.class)) {
        DataSource data = m
            .getAnnotation(DataSource.class);
        DynamicDataSourceHolder.putDataSource(data.value());
        System.out.println(data.value());
      }
      
    } catch (Exception e) {
      // TODO: handle exception
    }
  }
}

테스트 를 편리 하 게 하기 위해 저 는 2 개의 데이터 베 이 스 를 정 의 했 습 니 다.shop 시 뮬 레이 션 Master 라 이브 러 리,test 시 뮬 레이 션 Slave 라 이브 러 리,shop 과 test 의 표 구조 가 일치 하지만 데이터 가 다 릅 니 다.데이터 베 이 스 는 다음 과 같 습 니 다.

<bean id="masterdataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/shop" />
    <property name="username" value="root" />
    <property name="password" value="yangyanping0615" />
  </bean>

  <bean id="slavedataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/test" />
    <property name="username" value="root" />
    <property name="password" value="yangyanping0615" />
  </bean>
  
    <beans:bean id="dataSource" class="com.air.shop.common.db.DynamicDataSource">
    <property name="targetDataSources"> 
       <map key-type="java.lang.String"> 
         <!-- write -->
         <entry key="master" value-ref="masterdataSource"/> 
         <!-- read -->
         <entry key="slave" value-ref="slavedataSource"/> 
       </map> 
       
    </property> 
    <property name="defaultTargetDataSource" ref="masterdataSource"/> 
  </beans:bean>

  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>


  <!--   SqlSessionFactoryBean -->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:config/mybatis-config.xml" />
  </bean>

spring 설정 에 op 설정 추가

<!--        aop -->
  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  <beans:bean id="manyDataSourceAspect" class="com.air.shop.proxy.DataSourceAspect" />
  <aop:config>
    <aop:aspect id="c" ref="manyDataSourceAspect">
      <aop:pointcut id="tx" expression="execution(* com.air.shop.mapper.*.*(..))"/>
      <aop:before pointcut-ref="tx" method="before"/>
    </aop:aspect>
  </aop:config>
  <!--        aop -->
다음은 MyBatis 의 UserMapper 의 정의 입 니 다.테스트 에 편리 하도록 로그 인하 여 Master 라 이브 러 리 를 읽 었 습 니 다.사용자 목록 에서 Slave 라 이브 러 리 를 읽 었 습 니 다.

public interface UserMapper {
  @DataSource("master")
  public void add(User user);

  @DataSource("master")
  public void update(User user);

  @DataSource("master")
  public void delete(int id);

  @DataSource("slave")
  public User loadbyid(int id);

  @DataSource("master")
  public User loadbyname(String name);
  
  @DataSource("slave")
  public List<User> list();
} 

자,이 클립 스 를 실행 하여 효 과 를 보고 사용자 이름 admin 을 입력 하여 로그 인 해 보 세 요.


이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기