Путь к росту программиста
Интернет/Программист/Технологии/Обмен данными
Чтение этой статьи займет около 10 минут.
Откуда: juejin.cn/post/7261601725840179255
В последнее время при выполнении бизнес-требований мне нужно начинать с другой базы. Получить данные из данных и записать их в текущую базу данныхсередина,Поэтому возникает вопрос переключения источников данных.。Я думал использоватьMybatis-plusсерединапоставлятьиздинамический источник данныхSpringBootизstarter:dynamic-datasource-spring-boot-starter
осознать。
После представления результатов выяснилось, что его нельзя использовать из-за экологических проблем в предыдущем проекте. Затем я изучил код переключения источника данных,Решите принять это самостоятельноThreadLocal+AbstractRoutingDataSource
моделировать и реализовыватьdynamic-datasource-spring-boot-starter
серединанить Переключение источника данных。
упомянутый вышеThreadLocalиAbstractRoutingDataSource
,Давайте кратко представим его.
ThreadLocal
:Я уверен, что все с этим знакомы,полное имя:thread local variable
。В основном для решения многихнить Проблемы несогласованности данных возникают из-за параллелизма。ThreadLocalдля каждогонить Предоставьте копию переменной,Убедитесь, что каждый нить не обращается к одному и тому же объекту в определенное время.,Этим достигается изоляция,увеличенная память,Но это сильно снижает расход производительности при синхронизации.,Уменьшает сложность управления параллелизмом.
AbstractRoutingDataSource
:По определению пользователяиз Выбор правила актуаленизисточник данных,
Роль: перед выполнением запроса,Установите используемый источник данных,Источник данных для реализации динамической маршрутизации,каждый разбаза данных Выполните его перед операцией запросаизабстрактныйметодdetermineCurrentLookupKey()
,Решите, какой источник данных использовать.
Программная среда: SpringBoot2.4.8 Мибатис-плюс3.2.0 Друид1.2.6 Ломбок1.18.20 commons-lang3 3.10
Создайте класс для реализации ThreadLocal, главным образом для получения, установки и удаления источника данных, соответствующего текущему потоку, с помощью методов get, set и delete.
/**
* @author: jiangjs
* @description:
* @date: 2023/7/27 11:21
**/
public class DataSourceContextHolder {
//Этот класс не предоставляет никаких локальных переменных. Эти переменные отличаются от своего обычного соответствия тем, что каждая нить обращается к нити (через методы get и set) и имеет свою собственную независимую копию инициализированной переменной.
private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
/**
* Настроить источник данных
* @param dataSourceName Имя источника данных
*/
public static void setDataSource(String dataSourceName){
DATASOURCE_HOLDER.set(dataSourceName);
}
/**
* Получить текущий источник данных
* @return Имя источника данных
*/
public static String getDataSource(){
return DATASOURCE_HOLDER.get();
}
/**
* Удалить текущий источник данных
*/
public static void removeDataSource(){
DATASOURCE_HOLDER.remove();
}
}
Определить реализацию класса динамического источника данныхAbstractRoutingDataSource
,проходитьdetermineCurrentLookupKey
метод Реализовано с помощью вышеперечисленногоизThreadLocalдобрыйсерединаизgetметод связывания,Достичь динамического переключения источников данных.
/**
* @author: jiangjs
* @description: Реализация динамических источников данных и маршрутизация к различным источникам данных в соответствии с AbstractRoutingDataSource.
* @date: 2023/7/27 11:18
**/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDataSources);
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
В приведенном выше коде также реализован метод построения класса источника динамических данных, в основном для установки источника данных по умолчанию и различных целевых источников данных, сохраненных в Map. Ключом карты является заданное имя источника данных, а значением — соответствующий источник данных (DataSource).
Настройте информацию о базе данных в application.yml:
#Настроить источник данных
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 15
min-idle: 15
max-active: 200
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: ""
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: false
connection-properties: false
/**
* @author: jiangjs
* @description: Настроить источник данных
* @date: 2023/7/27 11:34
**/
@Configuration
public class DateSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource createDynamicDataSource(){
Map<Object,Object> dataSourceMap = new HashMap<>();
DataSource defaultDataSource = masterDataSource();
dataSourceMap.put("master",defaultDataSource);
dataSourceMap.put("slave",slaveDataSource());
return new DynamicDataSource(defaultDataSource,dataSourceMap);
}
}
Через класс конфигурации,Преобразование информации базы данных Конфигурации в файле Конфигурация в источник данных.,и добавлен вDynamicDataSource
середина,в то же времяпроходить@BeanВоляDynamicDataSource
инъекцияSpringсередина Управлять,Позже при добавлении динамических источников данных,Будет использоваться.
В двух тестовых библиотеках — главной и подчиненной,Добавить таблицу отдельноtest_user
,В нем только одно полеuser_name
。
create table test_user(
user_name varchar(255) not null comment 'имя пользователя'
)
Добавьте информацию в основную библиотеку:
insert into test_user (user_name) value ('master');
Добавляем информацию из библиотеки:
insert into test_user (user_name) value ('slave');
Мы создаем метод getData, и параметром является имя источника данных, из которого необходимо запросить данные.
@GetMapping("/getData.do/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName){
DataSourceContextHolder.setDataSource(datasourceName);
TestUser testUser = testUserMapper.selectOne(null);
DataSourceContextHolder.removeDataSource();
return testUser.getUserName();
}
Вы можете реализовать другие классы Mapper и сущностей самостоятельно.
Результат выполнения:
1. При прохождении мастера:
картина
2. При прохождении раба:
картина
По результатам выполнения мы видим, что при передаче разных имен источников данных соответствующие запрашиваемые базы данных различаются, а возвращаемые результаты также различаются.
В приведенном выше коде,мы видимDataSourceContextHolder.setDataSource(datasourceName);
Чтобы установить базу, которую текущий нит должен запросить данных,проходитьDataSourceContextHolder.removeDataSource();
чтобы удалить текущийнить Уже установленоизисточник данных。использовалMybatis-plusдинамический источник данныхиз Друзья,Еще следует помнить, что мы будем использовать его при переключении источников данных.DynamicDataSourceContextHolder.push(String ds);
иDynamicDataSourceContextHolder.poll();
Глядя на исходный код этих двух методов, мы обнаружим, что стек фактически используется при использовании ThreadLocal. Преимущество этого метода в том, что он может использовать несколько вложенных источников данных. Я не буду рассказывать вам о реализации. друзья могут глянуть Mybatis - Исходный код динамических источников данных в плюсе.
Примечание. При запуске программы не забудьте исключить SpringBoot из автоматического добавления источников данных, иначе будут сообщаться о проблемах с циклическими зависимостями.
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
Несмотря на то, что в приведенном выше примере было реализовано динамическое переключение источников данных, мы обнаружим, что если в переключении источников данных участвуют несколько компаний, нам необходимо добавить этот фрагмент кода в каждый класс реализации.
Говоря об этом, некоторые друзья, вероятно, подумают об использовании аннотаций для оптимизации. Давайте реализуем это дальше.
Мы используем аннотацию динамического переключения источника данных mybatis: DS, код следующий:
/**
* @author: jiangjs
* @description:
* @date: 2023/7/27 14:39
**/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DS {
String value() default "master";
}
@Aspect
@Component
@Slf4j
public class DSAspect {
@Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")
public void dynamicDataSource(){}
@Around("dynamicDataSource()")
public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
DS ds = method.getAnnotation(DS.class);
if (Objects.nonNull(ds)){
DataSourceContextHolder.setDataSource(ds.value());
}
try {
return point.proceed();
} finally {
DataSourceContextHolder.removeDataSource();
}
}
}
В коде используется @Around,проходитьProceedingJoinPoint
получатьаннотацияинформация,получить значение прохода аннотации,Затем установите текущий источник данных. Друзья, которые не знают об АОП, могут воспользоваться Google или Baidu.
Добавьте два метода тестирования:
@GetMapping("/getMasterData.do")
public String getMasterData(){
TestUser testUser = testUserMapper.selectOne(null);
return testUser.getUserName();
}
@GetMapping("/getSlaveData.do")
@DS("slave")
public String getSlaveData(){
TestUser testUser = testUserMapper.selectOne(null);
return testUser.getUserName();
}
Поскольку в @DS установлено значение по умолчанию: master, нет необходимости добавлять его при вызове основного источника данных.
Результат выполнения:
1. Вызовите метод getMasterData.do:
картина
2. Вызовите метод getSlaveData.do:
картина
По результатам выполнения мы также переключали источник данных через @DS, реализуя способ переключения источников данных через аннотации в Mybatis-plus с динамическим переключением источников данных.
бизнес-сценарий : Иногда наш бизнес требует, чтобы мы добавляли эти источники данных из таблиц базы данных, в которых хранятся другие источники данных, а затем переключали эти источники данных в зависимости от различных ситуаций.
Поэтому нам необходимо преобразовать DynamicDataSource для динамической загрузки источников данных.
/**
* @author: jiangjs
* @description: объект источника данных
* @date: 2023/7/27 15:55
**/
@Data
@Accessors(chain = true)
public class DataSourceEntity {
/**
* база данныхадрес */
private String url;
/**
* база данныхимя пользователя */
private String userName;
/**
* пароль
*/
private String passWord;
/**
* база данныхводить машину */
private String driverClassName;
/**
* база данныеkey, который сохраняет ключ на карте
*/
private String key;
}
Общая информация об источниках данных, определенных в сущностях,определитьkeyиспользуется какDynamicDataSource
серединаMapсерединаизkey。
/**
* @author: jiangjs
* @description: Реализация динамических источников данных и маршрутизация к различным источникам данных в соответствии с AbstractRoutingDataSource.
* @date: 2023/7/27 11:18
**/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
private final Map<Object,Object> targetDataSourceMap;
public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDataSources);
this.targetDataSourceMap = targetDataSources;
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
/**
* Добавить информацию об источнике данных
* @param dataSources объект источника данныхсобирать * @return Вернуться, чтобы добавить результаты
*/
public void createDataSource(List<DataSourceEntity> dataSources){
try {
if (CollectionUtils.isNotEmpty(dataSources)){
for (DataSourceEntity ds : dataSources) {
//проверятьбаза данных Является ли это возможнымсоединять Class.forName(ds.getDriverClassName());
DriverManager.getConnection(ds.getUrl(),ds.getUserName(),ds.getPassWord());
//Определяем источник данных
DruidDataSource dataSource = new DruidDataSource();
BeanUtils.copyProperties(ds,dataSource);
//При подаче заявки на соединение выполните validationQuery, чтобы проверить, действительно ли соединение. Рекомендуется, чтобы Конфигурация имела значение TRUE, чтобы полученное соединение не было недоступным.
dataSource.setTestOnBorrow(true);
//Рекомендуется указать значение «Конфигурация» true, что не влияет на производительность и обеспечивает безопасность.
//Обнаружение при подаче заявки на соединение. Если время простоя превышает timeBetweenEvictionRunsMillis, выполните validationQuery, чтобы проверить, действительно ли соединение.
dataSource.setTestWhileIdle(true);
//SQL используется для определения допустимости соединения, требованием является оператор запроса.
dataSource.setValidationQuery("select 1 ");
dataSource.init();
this.targetDataSourceMap.put(ds.getKey(),dataSource);
}
super.setTargetDataSources(this.targetDataSourceMap);
// Поместите объединенную информацию в TargetDataSources в управление разрешенными источниками данных.
super.afterPropertiesSet();
return Boolean.TRUE;
}
}catch (ClassNotFoundException | SQLException e) {
log.error("---Ошибка программы---:{}", e.getMessage());
}
return Boolean.FALSE;
}
/**
* Проверьте, существует ли источник данных
* @param key Ключ, сохраненный в источнике данных
* @return Возвращаемый результат: true: существует, false: не существует.
*/
public boolean existsDataSource(String key){
return Objects.nonNull(this.targetDataSourceMap.get(key));
}
}
после трансформацииизDynamicDataSource
середина,Мы можем добавить private final Map<Object,Object> targetDataSourceMap
,этотmap会在添加источник данныхиз Конфигурацияфайл ВолясоздаватьизMapисточник данныхинформацияпроходитьDynamicDataSource
Метод конструктора для начального присваивания,Прямо сейчас:DateSourceConfig
добрыйсерединаизcreateDynamicDataSource()
методсередина。
в то же время我们在该добрыйсерединадобавилcreateDataSource
метод,Создать источник данных,и добавил на карту,Сновапроходитьsuper.setTargetDataSources(this.targetDataSourceMap)
;Переназначьте целевой источник данных.
В приведенном выше коде реализован метод добавления источника данных, поэтому давайте смоделируем добавление источника данных из таблицы базы данных, а затем добавим источник данных в карту источника данных, вызвав метод загрузки источника данных.
Определите таблицу базы данных в основной базе данных, чтобы сохранить информацию о базе данных.
create table test_db_info(
id int auto_increment primary key not null comment 'Идентификатор первичного ключа',
url varchar(255) not null comment 'база данныхURL',
username varchar(255) not null comment 'имя пользователя',
password varchar(255) not null comment 'пароль',
driver_class_name varchar(255) not null comment 'база данныхводить машину' name varchar(255) not null comment 'база данныхимя'
)
Для удобства вносим в базу предыдущую подчиненную библиотеку и модифицируем имя базы.
insert into test_db_info(url, username, password,driver_class_name, name)
value ('jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false',
'root','123456','com.mysql.cj.jdbc.Driver','add_slave')
Ваши друзья могут добавлять сущности и преобразователи, соответствующие таблице базы данных.
Добавьте источник данных при запуске SpringBoot:
/**
* @author: jiangjs
* @description:
* @date: 2023/7/27 16:56
**/
@Component
public class LoadDataSourceRunner implements CommandLineRunner {
@Resource
private DynamicDataSource dynamicDataSource;
@Resource
private TestDbInfoMapper testDbInfoMapper;
@Override
public void run(String... args) throws Exception {
List<TestDbInfo> testDbInfos = testDbInfoMapper.selectList(null);
if (CollectionUtils.isNotEmpty(testDbInfos)) {
List<DataSourceEntity> ds = new ArrayList<>();
for (TestDbInfo testDbInfo : testDbInfos) {
DataSourceEntity sourceEntity = new DataSourceEntity();
BeanUtils.copyProperties(testDbInfo,sourceEntity);
sourceEntity.setKey(testDbInfo.getName());
ds.add(sourceEntity);
}
dynamicDataSource.createDataSource(ds);
}
}
}
После вышеуказанного запуска SpringBoot,Данные в таблице базы данных добавлены в динамический источник данных.,Вызываем предыдущий тестовый метод,Воля Имя источника данных Передайте его как параметр, чтобы увидеть результаты выполнения.。
картина
В ходе тестирования мы обнаружили, что данные базы данных в таблице баз данных динамически добавляются в источник данных. Друзья могут с радостью добавлять источники данных по своему желанию.
Ладно, на сегодня это всё. Надеюсь, что моё общение даст вам более глубокое понимание метода динамического переключения источников данных.
Работа лучше тяжелой работы, но игра – это пустая трата времени. Давайте действовать, друзья.
github:github.com/lovejiashn/… [1]
<END>
Содержимое включает основы Java.、JavaWeb、Оптимизация производительности MySQL、JVM、Замок、Миллионы одновременных、очередь сообщений、Высокопроизводительное кэширование、отражение、Принцип пружинного семейного ведра、микросервисы、Zookeeper... и другие технологические стеки!