MybatisPlus 다 세대 구조(Multi-tenancy)상세 설명 실현

다 세대 구조(Multi-tenancy)가 실현 되 기 전에 관련 정 의 를 알 아 보 자.
다 세입 자가 무엇 입 니까?
다 세대 기술 이나 다 중 임대 기술,약칭SaaS은 일종 의 소프트웨어 구조 기술 로 다 중 사용자 환경 에서(이곳 의 다 중 사용 자 는 일반적으로 기업 사용 자 를 대상 으로)똑 같은 시스템 이나 프로그램 구성 요 소 를 공유 하고 각 사용자 간 데이터 의 격 리 성 을 확보 할 수 있 습 니 다.
간단히 말 하면 한 서버 에서 하나의 응용 인 스 턴 스 를 실행 하여 여러 세입 자(고객)에 게 서 비 스 를 제공 합 니 다.정의 에서 우 리 는 다 세대 가 하나의 구조 로 다 중 사용자 환경 에서 같은 프로그램 을 사용 하고 사용자 간 의 데이터 격 리 를 확보 하 는 데 목적 을 둔다.그러면 중점 은 간단명료 하고 이해 하기 쉽다.다 세대 의 중점 은 같은 프로그램 에서 다 중 사용자 데이터 의 격 리 를 실현 하 는 것 이다.
데이터 격 리 방안
다 세대 가 데이터 저장 에 있어 세 가지 주요 방안 이 존재 하 는데 그것 이 바로 다음 과 같다.
독립 데이터베이스
즉,세입 자 하나의 데이터 베 이 스 는 이런 방안 의 사용자 데이터 격 리 등급 이 가장 높 고 안전성 이 가장 좋 지만 원가 가 비교적 높다.
  • 장점:서로 다른 세입 자 에 게 독립 된 데이터 베 이 스 를 제공 하면 데이터 모델 의 확장 디자인 을 간소화 하고 서로 다른 세입 자의 독특한 수 요 를 만족 시 키 는 데 도움 이 된다.고장 이 나 면 복구 데이터 가 간단 합 니 다.
  • 단점:데이터 뱅 크 의 설치 수량 이 증가 하면 서 유지 비용 과 구 매 비용 이 증가 했다.
  • 공유 데이터베이스,독립 스키 마
    여러 개 또는 모든 세입 자 는 Database 를 공유 하지만 세입 자 마다 하나의 Schema(user 라 고도 할 수 있 습 니 다).베이스 라 이브 러 리,예 를 들 어 DB2,ORACLE 등 은 하나의 데이터베이스 에 여러 개의 SCHEMA 가 있 을 수 있다.
  • 장점:안전성 에 대한 요구 가 높 은 세입 자 에 게 어느 정도 논리 적 데이터 격 리 를 제 공 했 고 완전히 격 리 된 것 이 아니다.모든 데이터 베 이 스 는 더 많은 세입 자 수 를 지원 할 수 있 습 니 다.
  • 단점:고장 이 나 면 데이터 복구 가 어렵 습 니 다.데이터 베 이 스 를 복구 하 는 것 은 다른 세입 자의 데이터 와 관련 되 기 때 문 입 니 다.
  • 데이터베이스 공유,Schema 공유,데이터 시트 공유
    세입 자 는 같은 Database,같은 Schema 를 공유 하지만 표 에 TenantID 다 세입 자의 데이터 필드 를 추가 하 는 것 이다.공유 수준 이 가장 높 고 격 리 수준 이 가장 낮은 패턴 이다.
    쉽게 말 하면 데 이 터 를 삽입 할 때마다 고객 의 표지 가 있어 야 한 다 는 것 이다.이렇게 해야만 같은 표 에서 서로 다른 고객 의 데 이 터 를 구분 할 수 있 습 니 다.이것 도 우리 시스템 이 현재 사용 하고 있 는(providerid)
  • 장점:세 가지 방안 을 비교 하면 세 번 째 방안 의 유지 와 구 매 원가 가 가장 낮 고 모든 데이터 베 이 스 를 지원 하 는 세입 자 수량 이 가장 많다.
  • 단점:격 리 등급 이 가장 낮 고 안전성 이 가장 낮 으 며 디자인 개발 할 때 안전 에 대한 개 발 량 을 늘 려 야 한다.데이터 백업 과 복구 가 가장 어렵 습 니 다.표 마다 백업 하고 복원 해 야 합 니 다.

  • MybatisPlus 를 이용 하여 실현
    여기 서 우 리 는 세 번 째 방안( , Schema, )을 선택 하여 실현 했다.즉,모든 데이터 시트 에 세입 자 표지(provider_id)가 필요 하 다 는 것 을 의미한다.
    현재 데이터베이스 시트(user)는 다음 과 같 습 니 다.
    필드 이름
    필드 형식
    묘사 하 다.
    id
    BIGINT(20)
    메 인 키
    provider_id
    BIGINT(20)
    서비스 업 체 ID
    name
    VARCHAR(30)
    성명.provider_id을 세입 자 ID 로 보고 세입 자 와 세입 자 간 의 데 이 터 를 격 리 하 는 데 사용 합 니 다.현재 서비스 업 체 의 사용 자 를 조회 하려 면 SQL 은 대체적으로 다음 과 같 습 니 다.
    
    SELECT * FROM user t WHERE t.name LIKE '%Tom%' AND t.provider_id = 1;
    일부 시스템 이 공용 하 는 시 계 를 제외 하고 다른 세입 자 와 관련 된 시 계 를 생각해 보 자.우 리 는 귀 찮 지 않 게AND t.provider_id = ?조회 조건 을 더 해 야 한다.조금 만 주의 하지 않 으 면 데이터 가 경 계 를 넘 어 데이터 안전 문제 가 우려 된다.
    다행히 Mybatis Plus 라 는 신기 가 있어 서 매우 편리 하 게 실현 할 수 있 습 니 다 SQL .공식 문 서 는 다음 과 같 습 니 다.
    http://mp.baomidou.com/guide/tenant.html
    여기 서 드디어 본론 으로 들 어가 아주 간단 한 개발 환경 을 만 들 기 시작 합 시다!
    새 SpringBoot 환경
    POM 파일 은 다음 과 같 습 니 다.주로 MybatisPlus 와 H2 데이터 베 이 스 를 통합 하 였 습 니 다(테스트 편리).
    
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>com.wuwenze</groupId>
      <artifactId>mybatis-plus-multi-tenancy</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>jar</packaging>
    
      <name>mybatis-plus-multi-tenancy</name>
      <description>Demo project for Spring Boot</description>
    
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
      </parent>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>19.0</version>
        </dependency>
    
        <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
          <version>3.0.5</version>
        </dependency>
        <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus</artifactId>
          <version>3.0.5</version>
        </dependency>
        <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-generator</artifactId>
          <version>3.0.5</version>
        </dependency>
    
        <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    데이터 원본 설정(application.yml)
    
    spring:
     datasource:
      driver-class-name: org.h2.Driver
      schema: classpath:db/schema.sql
      data: classpath:db/data.sql
      url: jdbc:h2:mem:test
      username: root
      password: test
    
    logging:
     level:
      com.wuwenze.mybatisplusmultitenancy: debug
    대응 하 는 H2 데이터베이스 schema 파일 초기 화
    
    #schema.sql
    DROP TABLE IF EXISTS user;
    CREATE TABLE user
    (
      id BIGINT(20) NOT NULL COMMENT '  ',
      provider_id BIGINT(20) NOT NULL COMMENT '   ID',
      name VARCHAR(30) NULL DEFAULT NULL COMMENT '  ',
      PRIMARY KEY (id)
    );
    
    
    #data.sql
    INSERT INTO user (id, provider_id, name) VALUES (1, 1, 'Tony  ');
    INSERT INTO user (id, provider_id, name) VALUES (2, 1, 'William  ');
    INSERT INTO user (id, provider_id, name) VALUES (3, 2, '   ');
    INSERT INTO user (id, provider_id, name) VALUES (4, 2, '   ');
    INSERT INTO user (id, provider_id, name) VALUES (5, 2, '   ');
    INSERT INTO user (id, provider_id, name) VALUES (6, 2, '   ');
    MybatisPlus Config
    기초 환경 구축 이 완료 되 었 습 니 다.지금부터 MybatisPlus 다 세대 와 관련 된 실현 을 설정 합 니 다.
    1)핵심 설정:TenantSqlParser
    
    @Configuration
    @MapperScan("com.wuwenze.mybatisplusmultitenancy.mapper")
    public class MybatisPlusConfig {
    
      private static final String SYSTEM_TENANT_ID = "provider_id";
      private static final List<String> IGNORE_TENANT_TABLES = Lists.newArrayList("provider");
    
      @Autowired
      private ApiContext apiContext;
    
      @Bean
      public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
    
        // SQL      :        。
        TenantSqlParser tenantSqlParser = new TenantSqlParser()
            .setTenantHandler(new TenantHandler() {
    
              @Override
              public Expression getTenantId() {
                //                    ID,        SQL 。
                Long currentProviderId = apiContext.getCurrentProviderId();
                if (null == currentProviderId) {
                  throw new RuntimeException("#1129 getCurrentProviderId error.");
                }
                return new LongValue(currentProviderId);
              }
    
              @Override
              public String getTenantIdColumn() {
                return SYSTEM_TENANT_ID;
              }
    
              @Override
              public boolean doTableFilter(String tableName) {
                //       :    (provider)            。
                return IGNORE_TENANT_TABLES.stream().anyMatch((e) -> e.equalsIgnoreCase(tableName));
              }
            });
        paginationInterceptor.setSqlParserList(Lists.newArrayList(tenantSqlParser));
        return paginationInterceptor;
      }
    
      @Bean(name = "performanceInterceptor")
      public PerformanceInterceptor performanceInterceptor() {
        return new PerformanceInterceptor();
      }
    }
    2) ApiContext
    
    @Component
    public class ApiContext {
      private static final String KEY_CURRENT_PROVIDER_ID = "KEY_CURRENT_PROVIDER_ID";
      private static final Map<String, Object> mContext = Maps.newConcurrentMap();
    
      public void setCurrentProviderId(Long providerId) {
        mContext.put(KEY_CURRENT_PROVIDER_ID, providerId);
      }
    
      public Long getCurrentProviderId() {
        return (Long) mContext.get(KEY_CURRENT_PROVIDER_ID);
      }
    }
    3) Entity、Mapper
    
    @Data
    @ToString
    @Accessors(chain = true)
    public class User {
      private Long id;
      private Long providerId;
      private String name;
    }
    
    public interface UserMapper extends BaseMapper<User> {
    
    }

    유닛 테스트
    com.wuwenze.mybatisplusmultitenancy.MybatisPlusMultiTenancyApplicationTests
    
    @Slf4j
    @RunWith(SpringRunner.class)
    @FixMethodOrder(MethodSorters.JVM)
    @SpringBootTest(classes = MybatisPlusMultiTenancyApplication.class)
    public class MybatisPlusMultiTenancyApplicationTests {
    
    
      @Autowired
      private ApiContext apiContext;
    
      @Autowired
      private UserMapper userMapper;
    
      @Before
      public void before() {
        //              ID
        apiContext.setCurrentProviderId(1L);
      }
    
      @Test
      public void insert() {
        User user = new User().setName("   Tom  ");
        Assert.assertTrue(userMapper.insert(user) > 0);
    
        user = userMapper.selectById(user.getId());
        log.info("#insert user={}", user);
    
        //                 ID
        Assert.assertEquals(apiContext.getCurrentProviderId(), user.getProviderId());
      }
    
      @Test
      public void selectList() {
        userMapper.selectList(null).forEach((e) -> {
          log.info("#selectList, e={}", e);
          //              
          Assert.assertEquals(apiContext.getCurrentProviderId(), e.getProviderId());
        });
      }
    }
    실행 결과
    
    2018-11-29 21:07:14.262 INFO 18688 --- [      main] .MybatisPlusMultiTenancyApplicationTests : Started MybatisPlusMultiTenancyApplicationTests in 2.629 seconds (JVM running for 3.904)
    2018-11-29 21:07:14.554 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.insert      : ==> Preparing: INSERT INTO user (id, name, provider_id) VALUES (?, ?, 1)
    2018-11-29 21:07:14.577 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.insert      : ==> Parameters: 1068129257418178562(Long),    Tom  (String)
    2018-11-29 21:07:14.577 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.insert      : <==  Updates: 1
     Time:0 ms - ID:com.wuwenze.mybatisplusmultitenancy.mapper.UserMapper.insert
    Execute SQL:INSERT INTO user (id, name, provider_id) VALUES (?, ?, 1) {1: 1068129257418178562, 2: STRINGDECODE('\u65b0\u6765\u7684Tom\u8001\u5e08')}
    
    2018-11-29 21:07:14.585 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.selectById    : ==> Preparing: SELECT id, provider_id, name FROM user WHERE user.provider_id = 1 AND id = ?
    2018-11-29 21:07:14.595 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.selectById    : ==> Parameters: 1068129257418178562(Long)
    2018-11-29 21:07:14.614 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.selectById    : <==   Total: 1
    2018-11-29 21:07:14.615 INFO 18688 --- [      main] .MybatisPlusMultiTenancyApplicationTests : #insert user=User(id=1068129257418178562, providerId=1, name=   Tom  )
     Time:19 ms - ID:com.wuwenze.mybatisplusmultitenancy.mapper.UserMapper.selectById
    Execute SQL:SELECT id, provider_id, name FROM user WHERE user.provider_id = 1 AND id = ? {1: 1068129257418178562}
    
    2018-11-29 21:07:14.626 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.selectList    : ==> Preparing: SELECT id, provider_id, name FROM user WHERE user.provider_id = 1
     Time:0 ms - ID:com.wuwenze.mybatisplusmultitenancy.mapper.UserMapper.selectList
    Execute SQL:SELECT id, provider_id, name FROM user WHERE user.provider_id = 1
    
    2018-11-29 21:07:14.629 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.selectList    : ==> Parameters:
    2018-11-29 21:07:14.630 DEBUG 18688 --- [      main] c.w.m.mapper.UserMapper.selectList    : <==   Total: 3
    2018-11-29 21:07:14.632 INFO 18688 --- [      main] .MybatisPlusMultiTenancyApplicationTests : #selectList, e=User(id=1, providerId=1, name=Tony  )
    2018-11-29 21:07:14.632 INFO 18688 --- [      main] .MybatisPlusMultiTenancyApplicationTests : #selectList, e=User(id=2, providerId=1, name=William  )
    2018-11-29 21:07:14.632 INFO 18688 --- [      main] .MybatisPlusMultiTenancyApplicationTests : #selectList, e=User(id=1068129257418178562, providerId=1, name=   Tom  )

    인쇄 된 로 그 를 통 해 알 수 있 듯 이 이 방안 은 완벽 합 니 다.간단 한 설정 만 있 으 면 개발 자 들 이 완전히 무시 할 수 있 습 니 다(providerid)필드 의 존재 와 동시에 데이터 의 안전성 을 최대한 보장 하여 일거양득 이 라 고 할 수 있 습 니 다!
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기