Обзор:
Что касается стека технологий, 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
) в группу, чтобы узнать ~