当前位置: 首页 > news >正文

Spring Boot 配置JPA数据库主从读写分离失败及解决办法

因为是老项目, Spring Boot 是1.4, 使用 AbstractRoutingDataSource 来做主从切换, 配置切面类在进入事务时切换成主库, 但实际运行起来却失败, 写操作路由到了从库

查了很多文章, 试了很多方法都无效, 包括修改注解 @Transactional 的 propagation 属性, 清空主从标记等等

打断点跟踪代码发现, 进入事务时并没有触发获取数据库连接, 而是事务里第一个查询触发了数据库连接的建立, 选择了从库, 后面的所有操作都不会触发数据源选择

后来查到这篇文章

JPA hibernate AbstractRoutingDataSource 在同一方法内使用不同数据源失败_user springboo jpa controller abstractroutingdatas-CSDN博客

通过这篇文章又找到下面的文章
Spring Data JPA 原理与实战第十一天 Session相关、CompletableFuture、LazyInitializationException_org.hibernate.resource.jdbc.spi.physicalconnection-CSDN博客
 

了解到JPA是有会话保持的, 一旦数据库链接创建在会话期内不会改变, 要想在进入事务时就选择数据源为主库, 需要修改 hibernate.connection.handling_mode 调整处理物理连接的模式。

因为我 Spring Boot  是1.4, 匹配的 hibernate 版本是5.0, 所以是把 release_mode 设置为AFTER_TRANSACTION

@Configuration
public class JpaConfig {

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            @Qualifier("routingDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.my.domain");

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);

        // 设置 JPA 属性
        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        properties.setProperty("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        properties.setProperty("hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
        properties.setProperty("hibernate.connection.release_mode", "AFTER_TRANSACTION");
        em.setJpaProperties(properties);
        return em;
    }
}

部署后又出现了新问题, 进入事务之前如果有查询操作, 还是会路由到从库, 进入事务时, 不会再触发重新获取数据源, 原因出在这部分代码 

public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImplementor {
	private Connection acquireConnectionIfNeeded() {
		if ( physicalConnection == null ) {
			// todo : is this the right place for these observer calls?
			observer.jdbcConnectionAcquisitionStart();
			try {
				physicalConnection = jdbcConnectionAccess.obtainConnection();
			}
			catch (SQLException e) {
				throw sqlExceptionHelper.convert( e, "Unable to acquire JDBC Connection" );
			}
			finally {
				observer.jdbcConnectionAcquisitionEnd( physicalConnection );
			}
		}
		return physicalConnection;
	}
}

如果之前已经获取了链接, jdbcConnectionAccess.obtainConnection() 会直接返回之前创建好的链接进行复用, 解决办法是进入事务之前不要有数据库查询操作, 把查询放到事务里

http://www.dtcms.com/a/14431.html

相关文章:

  • 激光工控机在精密制造中的应用与优势
  • mybatis 是否支持延迟加载?延迟加载的原理是什么?
  • 【新品解读】AI 应用场景全覆盖!解码超高端 VU+ FPGA 开发平台 AXVU13F
  • [Spring] Spring常见面试题
  • 2025.2.11——一、[极客大挑战 2019]PHP wakeup绕过|备份文件|代码审计
  • 联合汽车电子嵌入式面试题及参考答案
  • wordpress主题制作
  • Java面试题——事务
  • 部署onlyoffice后,php版的callback及小魔改(logo和关于)
  • 算法刷题-数组系列-卡码网.区间和
  • Maven 中常用的 scope 类型及其解析
  • OPEN CODER : THE OPEN COOKBOOK FOR TOP -TIER CODE LARGE LANGUAGE MODELS
  • udp和tcp的区别
  • 6.深度学习在推荐系统中的应用
  • 学习数据结构(9)栈和队列上
  • RabbitMQ 如何设置限流?
  • 前沿科技改变生活新趋势
  • 掌握 systemd:Linux 服务管理的核心工具
  • C++病毒(^_^|)(2)
  • Android Handler的机制跟源码分析
  • Android 系统面试问题
  • MySQL主从同步+binlog
  • 同为科技智能PDU助力Deepseek人工智能和数据交互的快速发展
  • 【python】简单的flask做页面。一组字母组成的所有单词。这里的输入是一组字母,而输出是所有可能得字母组成的单词列表
  • 21道关于Vue3的面试题及其解析
  • Linux学习笔记18---串口格式化函数移植实验
  • langchain学习笔记之小样本提示词Few-shot Prompt Template
  • 19.3 连接数据库
  • stm32点灯 GPIO的输出模式
  • 开源的轻量级分布式文件系统FastDFS