Простая реализация разделения чтения и записи Mybatis

Java
Простая реализация разделения чтения и записи Mybatis

Обзор:

   Что касается стека технологий, Spring Boot 2.0 будет использоваться в качестве базовой платформы, в основном для последующего доступа к Spring Cloud для обучения и расширения. И Spring Boot 2.0 основан на Spring5, вы также можете предварительно просмотреть некоторые новые функции Spring5. Последующие технологии будут предложены в соответствующем блоге.

  Адрес проекта на GitHub:Spring-Blog  

  Познакомьтесь со структурой каталогов:

  • Spring-Blog(Parentпроект)
  • Spring-Блог-общий (Utilмодуль)
  • Весна-Блог-бизнес (Repositoryмодуль)
  • Spring-Блог-API (Webмодуль)
  • Spring-Blog-webflux (на основеSpring Boot 2.0веб-модуль)

   Чтобы друзья могли лучше понять содержание этого модуля, демо-код будет храниться в проекте Spring Boot:  

    Адрес на гитхабе:образец кода     

1. Источник данных

     Прежде чем мы начнем объяснять, нам нужно создать среду выполнения.Spring BootпредставлятьMybatisУчебник может относиться кпортал. Мы не будем здесь вдаваться в подробности, давайте сначала посмотрим на нашу структуру каталогов:   

ИспользовалиSpring BootДетская обувь должна быть чистой, когда мы вapplication.propertiesПосле настройки информации о подключении к нашей базе данных Spring Boot автоматически загрузит ее для нас.DataSource. Но если нам нужно выполнять операции разделения чтения-записи, мы должны научиться настраивать собственные источники данных.

   Сначала давайте посмотрим на информацию в файле конфигурации:

spring.datasource.url=jdbc:mysql://localhost:3306/charles_blog2
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#别名扫描目录
mybatis.type-aliases-package=com.jaycekon.demo.model
#Mapper.xml扫描目录
mybatis.mapper-locations=classpath:mybatis-mappers/*.xml

#tkmapper 帮助工具
mapper.mappers=com.jaycekon.demo.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL

1.1 DataSourceBuilder

    Давайте сначала рассмотрим использование DataSourceBuilder для создания DataSource:

@Configuration
@MapperScan("com.jaycekon.demo.mapper")
@EnableTransactionManagement
public class SpringJDBCDataSource {

    /**
     * 通过Spring JDBC 快速创建 DataSource
     * 参数格式
     * spring.datasource.master.jdbcurl=jdbc:mysql://localhost:3306/charles_blog
     * spring.datasource.master.username=root
     * spring.datasource.master.password=root
     * spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
     *
     * @return DataSource
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
}

     Как видно из кода, использованиеDataSourceBuilderПостроитьDataSourceСпособ очень простой, но следует отметить, что:

  • DataSourceBuilder может автоматически распознавать только jdbcurl, имя пользователя, пароль, имя класса драйвера и другие имена в файле конфигурации, поэтому нам нужно добавить аннотацию @ ConfigurationProperties в тело метода.

  • Имя переменной адреса подключения к базе данных должно использовать jdbcurl

  • Пул соединений с базой данных использует com.zaxxer.hikari.HikariDataSource.     

    При выполнении модульного теста мы видим создание и закрытие DataSource.

1.2 DruidDataSource

     Помимо использования вышеуказанных методов строительства, мы можем использовать Али при условииDruidПул соединений с базой данных для создания DataSource

@Configuration
@EnableTransactionManagement
public class DruidDataSourceConfig {

    @Autowired
    private DataSourceProperties properties;

    @Bean
    public DataSource dataSoucre() throws Exception {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(properties.getUrl());
        dataSource.setDriverClassName(properties.getDriverClassName());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());
        dataSource.setInitialSize(5);
        dataSource.setMinIdle(5);
        dataSource.setMaxActive(100);
        dataSource.setMaxWait(60000);
        dataSource.setTimeBetweenEvictionRunsMillis(60000);
        dataSource.setMinEvictableIdleTimeMillis(300000);
        dataSource.setValidationQuery("SELECT 'x'");
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        dataSource.setPoolPreparedStatements(true);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
        dataSource.setFilters("stat,wall");
        return dataSource;
    }
}

использоватьDruidDataSourceЭто может показаться громоздким в качестве пула соединений с базой данных, но с другой точки зрения, это более контролируемо. мы можем пройтиDataSourcePropertiesполучитьapplication.propertiesфайл конфигурации в:

spring.datasource.url=jdbc:mysql://localhost:3306/charles_blog2
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Следует отметить, что префикс файла конфигурации, прочитанный DataSourceProperties, — spring.datasource , мы можем ввести исходный код DataSourceProperties, чтобы наблюдать:

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties
        implements BeanClassLoaderAware, EnvironmentAware, InitializingBean

Видно, что формат префикса отмечен в исходном коде по умолчанию.

  В дополнение к использованию DataSourceProperties для получения файлов конфигурации мы также можем использовать общие переменные среды для чтения классов:  

@Autowired
    private Environment env;
    
    env.getProperty("spring.datasource.write")

2. Конфигурация нескольких источников данных

 Настройка нескольких источников данных в основном требует следующих шагов:

2.1 Имя источника данных DatabaseType

 Здесь напрямую используйте тип перечисления, чтобы различать, читать источник данных и записывать источник данных.

public enum DatabaseType {
    master("write"), slave("read");


    DatabaseType(String name) {
        this.name = name;
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "DatabaseType{" +
                "name='" + name + '\'' +
                '}';
    }
}

2.2 DatabaseContextHolder

    Этот класс в основном используется для записи источника данных, используемого текущим потоком, и использования ThreadLocal для записи данных

public class DatabaseContextHolder {
    private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();

    public static void setDatabaseType(DatabaseType type) {
        contextHolder.set(type);
    }

    public static DatabaseType getDatabaseType() {
        return contextHolder.get();
    }
}

2.3 DynamicDataSource

   Этот класс наследует AbstractRoutingDataSource для управления нашим источником данных и в основном реализует метод defineCurrentLookupKey.  Ниже подробно описано, как этот класс управляет несколькими источниками данных.    

public class DynamicDataSource extends AbstractRoutingDataSource {


    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        DatabaseType type = DatabaseContextHolder.getDatabaseType();
        logger.info("====================dataSource ==========" + type);
        return type;
    }

}

2.4 DataSourceConfig

Последний шаг состоит в том, чтобы настроить наш источник данных и разместить источник данных в DynamicDataSource:      

@Configuration
@MapperScan("com.jaycekon.demo.mapper")
@EnableTransactionManagement
public class DataSourceConfig {

    @Autowired
    private DataSourceProperties properties;

    /**
     * 通过Spring JDBC 快速创建 DataSource
     * 参数格式
     * spring.datasource.master.jdbcurl=jdbc:mysql://localhost:3306/charles_blog
     * spring.datasource.master.username=root
     * spring.datasource.master.password=root
     * spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
     *
     * @return DataSource
     */
    @Bean(name = "masterDataSource")
    @Qualifier("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

 /**
     * 手动创建DruidDataSource,通过DataSourceProperties 读取配置
     * 参数格式
     * spring.datasource.url=jdbc:mysql://localhost:3306/charles_blog
     * spring.datasource.username=root
     * spring.datasource.password=root
     * spring.datasource.driver-class-name=com.mysql.jdbc.Driver
     *
     * @return DataSource
     * @throws SQLException
     */
    @Bean(name = "slaveDataSource")
    @Qualifier("slaveDataSource")
    public DataSource slaveDataSource() throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(properties.getUrl());
        dataSource.setDriverClassName(properties.getDriverClassName());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());
        dataSource.setInitialSize(5);
        dataSource.setMinIdle(5);
        dataSource.setMaxActive(100);
        dataSource.setMaxWait(60000);
        dataSource.setTimeBetweenEvictionRunsMillis(60000);
        dataSource.setMinEvictableIdleTimeMillis(300000);
        dataSource.setValidationQuery("SELECT 'x'");
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        dataSource.setPoolPreparedStatements(true);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
        dataSource.setFilters("stat,wall");
        return dataSource;
    }

    /**
     *  构造多数据源连接池
     *  Master 数据源连接池采用 HikariDataSource
     *  Slave  数据源连接池采用 DruidDataSource
     * @param master
     * @param slave
     * @return
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource master,
                                        @Qualifier("slaveDataSource") DataSource slave) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseType.master, master);
        targetDataSources.put(DatabaseType.slave, slave);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(slave);// 默认的datasource设置为myTestDbDataSourcereturn dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource myTestDbDataSource,
                                               @Qualifier("slaveDataSource") DataSource myTestDb2DataSource) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(this.dataSource(myTestDbDataSource, myTestDb2DataSource));
        fb.setTypeAliasesPackage(env.getProperty("mybatis.type-aliases-package"));
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapper-locations")));
        return fb.getObject();
    }
}

    Приведенный выше блок кода относительно длинный, давайте разберем его:

  • masterDataSourceа такжеslaveDataSourceВ основном используется для создания источников данных, здесь мы используем hikaridatasource и druidDataSource соответственно в качестве источников данных.
  • DynamicDataSourceВ теле метода мы в основном помещаем оба источника данных в DynamicDataSource для унифицированного управления.
  • SqlSessionFactoryМетод заключается в единообразном управлении всеми источниками данных (DynamicDataSource).

2.5 UserMapperTest

    Далее кратко рассмотрим процесс создания DataSource:

    Прежде всего, мы видим, что наши два источника данных построены с использованиемHikariDataSourceа такжеDruidDataSource, то мы поместим два источника данных вtargetDataSource, а здесь мы говорим о нашем слейве как об источнике данных по умолчаниюdefaultTargetDataSource   

Затем перейдите к источнику данных:

     в основном изAbstractRoutingDataSourceв этом классеопределитьTargetDataSource()Суждение производится в методе, который будет называться здесьDynamicDataSourceметод, чтобы определить, какой источник данных использовать. Если источник данных не установлен, будет использоваться источник данных по умолчанию, который мы только что установили.DruidDataSourceисточник данных.     

       в конечном результате выполнения кода:

       Мы видим, что установленный нами источник данных по умолчанию действительно используется.       

3. Разделение чтения и записи

  Пройдя через тысячи гор и рек, мы, наконец, пришли к нашему модулю разделения чтения-записи.Во-первых, нам нужно добавить некоторую информацию о нашей конфигурации:

spring.datasource.read = get,select,count,list,query
spring.datasource.write = add,create,update,delete,remove,insert

Эти две переменные в основном используются для оценки аспектов, чтобы различать, какие части нужно считывать из источника данных, а какие — записывать.

3.1 Модификация источника динамических данных

public class DynamicDataSource extends AbstractRoutingDataSource {

    static final Map<DatabaseType, List<String>> METHOD_TYPE_MAP = new HashMap<>();


    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        DatabaseType type = DatabaseContextHolder.getDatabaseType();
        logger.info("====================dataSource ==========" + type);
        return type;
    }

    void setMethodType(DatabaseType type, String content) {
        List<String> list = Arrays.asList(content.split(","));
        METHOD_TYPE_MAP.put(type, list);
    }
}

  Здесь нам нужно добавить карту для записи некоторой информации о префиксах чтения и записи.

3.2 Модификация DataSourceConfig

  В DataSourceConfig, когда мы устанавливаем DynamicDataSource, устанавливаем в него информацию о префиксе.

@Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource master,
                                        @Qualifier("slaveDataSource") DataSource slave) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseType.master, master);
        targetDataSources.put(DatabaseType.slave, slave);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(slave);// 默认的datasource设置为myTestDbDataSource

        String read = env.getProperty("spring.datasource.read");
        dataSource.setMethodType(DatabaseType.slave, read);

        String write = env.getProperty("spring.datasource.write");
        dataSource.setMethodType(DatabaseType.master, write);

        return dataSource;
    }

3.3 DataSourceAspect

  После настройки префикса метода чтения-записи нам нужно настроить фасет для мониторинга и установить источник данных перед входом в метод Mapper:

  Основная рабочая точка — это DatabaseContextHolder.setDatabaseType(type); В сочетании с нашим методом получения источников данных из нескольких источников данных, описанных выше, это ключ для нас, чтобы установить источники данных для чтения или записи.   

@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
    private static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    @Pointcut("execution(* com.jaycekon.demo.mapper.*.*(..))")
    public void aspect() {

    }


    @Before("aspect()")
    public void before(JoinPoint point) {
        String className = point.getTarget().getClass().getName();
        String method = point.getSignature().getName();
        String args = StringUtils.join(point.getArgs(), ",");
        logger.info("className:{}, method:{}, args:{} ", className, method, args);
        try {
            for (DatabaseType type : DatabaseType.values()) {
                List<String> values = DynamicDataSource.METHOD_TYPE_MAP.get(type);
                for (String key : values) {
                    if (method.startsWith(key)) {
                        logger.info(">>{} 方法使用的数据源为:{}<<", method, key);
                        DatabaseContextHolder.setDatabaseType(type);
                        DatabaseType types = DatabaseContextHolder.getDatabaseType();
                        logger.info(">>{}方法使用的数据源为:{}<<", method, types);
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}

3.4 UserMapperTest

 После запуска метода сначала введите аспект и установите тип источника данных в соответствии с именем метода.  

Затем введите метод defineTargetDataSource, чтобы получить источник данных:

результат операции:

4. Пишите в конце

  Я надеюсь, что друзья, которые сочтут это полезным после прочтения, помогут блогеру нажать Start или fork на github

  Spring-BlogАдрес проекта на GitHub:Spring-Blog

  образец кодаАдрес на гитхабе:образец кода        

Наконец, опубликуйте новый публичный аккаунт (Java 补习课), пожалуйста, обратите внимание.Я в основном поделюсь содержанием интервью (см. предыдущую статью блоггера), технологиями с открытым исходным кодом Али и тому подобным, связанным с жизнью Али. Если вы хотите обменяться опытом интервью, вы можете добавить мой личный WeChat (Jayce-K) в группу, чтобы узнать ~