Обзор:
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
образец кодаАдрес на гитхабе:образец кода