Рассказываем о ямах Spring-Mybatis в настройке нескольких источников данных

задняя часть база данных Spring MyBatis
Рассказываем о ямах Spring-Mybatis в настройке нескольких источников данных

团队博客: https://juejin.cn/post/6844903721927720967

Обзор

Давайте сначала поговорим о бизнес-предыстории. С непрерывным развитием системных сервисов наша система будет наводнена различными предприятиями. В это время мы должны начать думать о том, как улучшить детализацию системы. Например, общий пример : Система электронной коммерции может быть разделена на товарные модули, модули заказов, адресные модули и т. д. Эти модули могут быть извлечены независимо, чтобы сформировать отдельную услугу Это будет включать связь между различными модулями, некоторые простые услуги, мы можемrpcИнтерфейс общается напрямую, но некоторые сервисы не применяют этот режим.В этой статье в основном рассказывается о多数据源Некоторые ямы встречались на дороге.

Несколько источников данных

Структура проекта

Адрес источника:GitHub.com/JayMeasurementon/SP…

目录结构

файл конфигурации:DataSourceConfig

    @Bean(name = "masterDataSource")
    @Qualifier("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "slaveDataSource")
    @Qualifier("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

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

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

        return 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();
    }

Процесс создания проекта может относиться к:«Спринг-мибати, прочитал и писать разделение»

база данных

test_1:

CREATE TABLE `school` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `school_name` varchar(255) DEFAULT NULL,
  `province` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

test_2:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

1. Ссылка на базу данных ненормальная

Эта ссылка на базу данных является ненормальной, ссылаясь на切换数据源Когда ссылка на базу данных ненормальна

Запустите наш сервис:

Это означает, что с конфигурацией нашего сервиса проблем нет, так что же такое так называемое исключение связи с базой данных?

Test:

    @Autowired
    private SchoolService schoolService;

    @Autowired
    private UserService userService;

    @Test
    public void addUser() {
        userService.inserUser("root2","root2");
    }
    
    @Test
    public void addSchool() {
        schoolService.addSchool("ceshi1", "ceshi1");
    }

Установите источник данных по аннотации:

@Service
@DataSource("db2")
public class UserService

@Service
@DataSource("db1")
public class SchoolService

Мы создали тестовый класс для обнаружения обработки двух источников данных.

Из результатов:

1,schoolServiceудалось (дб:test_1)

2,UserServiceне удалось (дб:test_2)

errorMessage:

org.springframework.jdbc.BadSqlGrammarException: 
### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: Table 'test_1.user' doesn't exist
### The error may involve com.jaycekon.mybatis.multi.mapper.UserMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO `user`(`username`, `password`)          VALUES ( ?, ?);
### Cause: java.sql.SQLSyntaxErrorException: Table 'test_1.user' doesn't exist
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Table 'test_1.user' doesn't exist

Приведенное выше исключение, первая яма, с которой мы можем столкнуться:UserServiceИсключение ссылки на источник данных в

Анализ аномалий

1. Источник данных связан сtest_1Указывает, что источник данных не был успешно переключен

2. Соблюдайте метод аспекта, мониторdataSource

 @Before("@annotation(com.jaycekon.mybatis.multi.config.DataSource)")

3.@DataSource

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface DataSource 

Из приведенных выше аннотаций можно узнать, что наш объект аннотации — это TYPE (класс), а вAspectJМониторинг аннотаций в поддерживает только мониторинг аннотаций методов, а не аннотаций классов, поэтому описанным выше способом мы не можем добиться динамического переключения источников данных, аннотируя весь класс:

@Service
@DataSource("db2")
public class UserService

@Service
@DataSource("db1")
public class SchoolService

Решение

1. ИзменитьDataSourceДля аннотации метода отслеживайте каждый метод, которому необходимо переключить источник данных.тупой.

2. Пройти@Pointcut("execution(* com.jaycekon.demo.mapper.*.*(..))")В форме Pointcut вы можете отслеживать все классы и методы в пакете.Этот метод в порядке, но каждый раз, когда вы создаете новый класс, вам может потребоваться изменить конфигурацию.

3. Режим, используемый в настоящее время в текущем использовании различных источников данныхmapper,type-aliases,configОтдельный Для настройки см.:портал

Измененный каталог (файл конфигурации должен содержать только два элемента):

2. Исключение сопоставления картографа

После изменения нового файла конфигурации мы можем обратиться к следующему коду (похожему на db2):

@Configuration
@MapperScan(value = "com.jaycekon.mybatis.multi.mapper.db1")
@EnableTransactionManagement
public class DataSourceConfig {

    private static final String MAPPER_LOCATION = "mybatis.mapper-locations.db1";

    @Autowired
    private Environment env;


    @Bean(name = "masterDataSource")
    @Qualifier("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }



    @Bean(name = "db1SqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource myTestDbDataSource) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(myTestDbDataSource);
        fb.setTypeAliasesPackage(env.getProperty("mybatis.type-aliases-package"));
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty(MAPPER_LOCATION)));
        return fb.getObject();
    }


    @Bean
    public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource myTestDbDataSource) {
        return new DataSourceTransactionManager(myTestDbDataSource);
    }
}

На самом деле конфигурационный файл здесь скрывает яму, когда мы начнем компиляцию, проблем не будет, но когда мы зайдем(db2)Когда возникает проблема:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.jaycekon.mybatis.multi.mapper.db2.UserMapper.insert

Мы видим, что,db1(school)Модульные тесты хороши, ноdb2(user)Но что-то пошло не так.

Анализ аномалий

1,MapperСканирование не нашло соответствующегоXMLдокумент

2. Существует несколько источников данныхSqlSessionFactory, необходимоMapperфайл привязан к соответствующемуSqlSessionFactory

3. Решение — сканироватьMapper, привяжите его к соответствующемуSqlSessionFactory :

@MapperScan(value = "com.jaycekon.mybatis.multi.mapper.db2", sqlSessionFactoryRef = "db2SqlSessionFactory")

существует@MapperScanСоответствующее объяснение можно найти в:

   * Specifies which {@code SqlSessionFactory} to use in the case that there is
   * more than one in the spring context. Usually this is only needed when you
   * have more than one datasource.

Начать тестовый урок --pass,запуск программы--pass

Если вы думаете, что эта яма здесь, вы меня недооцениваете~

2.1 Сопоставление псевдонимов типов

Как правило, с нашими модульными тестами и сервисами проблем не возникает, и разумно выполнить следующую разработку в обычном режиме.Spring-BootДля разработки нам нужно сделать операцию перед публикацией.Jar包, а затем запустите службу с помощью командной строки:

java -jar target/spring-boot-mybatis-multi.jar

И тогда возникнут следующие проблемы:

Failed to parse mapping resource: 'class path resource [mybatis-mappers/db2/UserMapper.xml]';
nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML.
Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. 
Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'User'. 
Cause: java.lang.ClassNotFoundException: Cannot find class: User

в конфигурацииSqlSessionFactoryмы установилиTypeAliasesPackageпуть сканирования:

    @Bean(name = "db1SqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource myTestDbDataSource) throws Exception {
        ...
        fb.setTypeAliasesPackage(env.getProperty("mybatis.type-aliases-package"));
        ...
    }

Но он ничего не сделал, почему так?

Анализ аномалий

1. Не работает сканирование псевдонимов

2. Перейдите на Github, чтобы найти соответствующий контент, и вы найдете тот же опыт:портал

Решение

1. Не используйте псевдонимы (不是个好办法)

2. Вmybatis/spring-boot-starterВ этом проекте официальныйDemo

Мы перехватываем часть более критического кода посередине:

    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);

Мы используем方法2Попробуйте и посмотрите, решит ли это проблему:

оVFSНекоторые пояснения:

虚拟文件系统(VFS),用来读取服务器里的资源

Лично понимаю, что вновь созданныйSqlSessionFactoryНе удалось загрузить файл конфигурации, в результате@PrimaryВсе кромеSqlSessionFactoryНевозможно загрузить соответствующие файлы конфигурации.

3. Исключение конфигурации

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

Почему возвращаются пустые данные?

Анализ аномалий

1. Данные возвращаются, с сервисом проблем нет

2,schoolNameсоответствующая база данныхschool_name, при промежуточных преобразованиях необходимо использовать преобразование имени в регистре верблюдов.

Преобразование имени CamelCasemybatis.configuration.map-underscore-to-camel-caseсуществует проблема.

Решение

1. Добавить конфигурациюmybatis.configuration.map-underscore-to-camel-case=true

2. СоздатьMybatisConfigкласс конфигурации (db2похожий):

    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    @Scope("prototype")
    public org.apache.ibatis.session.Configuration globalConfiguration() {
        return new org.apache.ibatis.session.Configuration();
    }
    
    @Bean(name = "db1SqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource myTestDbDataSource,
                                               org.apache.ibatis.session.Configuration config) throws Exception {
        ...
        fb.setConfiguration(config);
        ...
    }

3.@Scope("prototype")Класс конфигурации здесь использует область с несколькими экземплярами, в основном для решения проблемы, связанной с тем, что одноэлементный режим повлияет на ссылку на источник данных.

Время ожидания подключения к базе данных истекло

Когда вы публикуете проект на сервер, проблем с отладкой интерфейса нет, через одну ночь вы вдруг обнаруживаете, что сервис не работает, что происходит?

{
    "msg": "\n### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.\n### SQL: ******\n###
    Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.\n; SQL [];
    No operations allowed after connection closed.; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: 
    No operations allowed after connection closed.",
    "code": 500
}

MySQL5.0В дальнейшем выполняется процесс долгосрочного подключения к БД.DB连接После 8 часов бездействия (по умолчанию "wait_timeout" сервера Mysql составляет 8 часов), Mysql автоматически закроет соединение. Это проблема, в пуле соединенийconnectionsЕсли он бездействует более 8 часов, mysql отключает его, а сам пул соединений не знаетconnectionистек, если в это время естьClientпроситьconnection, пул соединений сделает недействительнымConnectionпредоставлятьClient, вызовет указанное выше исключение. Поэтому при настройке источника данных необходимо настроить соответствующие параметры пула соединений, регулярно проверять правильность соединения и регулярно очищать недействительные соединения.Цитировать

Решение — улучшите соответствующую конфигурацию:

spring.datasource.jdbcUrl=jdbc:mysql://localhost:3306/test_1
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.default-auto-commit = false
spring.datasource.default-read-only = true
spring.datasource.max-idle = 10
spring.datasource.max-wait = 10000
spring.datasource.min-idle = 5
spring.datasource.initial-size = 5
spring.datasource.validation-query = SELECT 1
spring.datasource.test-on-borrow = false
spring.datasource.test-while-idle = true
spring.datasource.time-between-eviction-runs-millis = 18800


spring.datasource.db2.jdbcUrl=jdbc:mysql://localhost:3306/test_2
spring.datasource.db2.username=root
spring.datasource.db2.password=123456
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db2.default-auto-commit = false
spring.datasource.db2.default-read-only = true
spring.datasource.db2.max-idle = 10
spring.datasource.db2.max-wait = 10000
spring.datasource.db2.min-idle = 5
spring.datasource.db2.initial-size = 5
spring.datasource.db2.validation-query = SELECT 1
spring.datasource.db2.test-on-borrow = false
spring.datasource.db2.test-while-idle = true
spring.datasource.db2.time-between-eviction-runs-millis = 18800

4. Исключение транзакции

Поскольку мы находимся в нескольких источниках данных, мы используем несколькоsqlSessionFactoryПоэтому в разделе управления транзакциями будут вопросы, связанные с исключениями управления транзакциями.Заинтересованная детская обувь может относиться к:woohoo.atom IK OS.com/main/Web дом…, рекомендует интегрированныйDemo

Суммировать

Конфигурация Mybatis с несколькими источниками данных в основном делится на два типа: один — источник данных динамической конфигурации, а другой — с несколькими источниками данных.sqlsessionFactory, некоторые ямы в этой статье в основном основаны на многихsqlSessionFactory. Все вышеперечисленные проблемы возникают в процессе разработки, и вы, возможно, сталкивались с ними более или менее, и надеемся оказать вам соответствующую помощь.

Если вы не согласны с вашим личным мнением, поправьте меня.

Демонстрационный адрес:GitHub.com/JayMeasurementon/SP…