Разделение чтения и записи Spring-Mybatis

Spring Boot MyBatis gradle
Разделение чтения и записи Spring-Mybatis

Обзор:

2018, После tou и lan какое-то время я начал находить себе занятие. На этот раз я собираюсь развивать личный блог и совершенствовать свои технологии в процессе разработки. Эта серия блогов будет предлагать только некоторые ценные технические идеи и не будет записывать процесс разработки, как ведение журнала.

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

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

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

  • Spring-Blog(Parentпроект)
  • Spring-Блог-общий (Utilмодуль)
  • Весна-блог-бизнесRepositoryмодуль)
  • Spring-Blog-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

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