public class ThreadInfoHolder { // thread local, 获取、存储本线程处理的账号对应分片信息 private static final ThreadLocal<Shard> shardLocal = new ThreadLocal<Shard>(); /** * 获取当前线程处理的账号对应分片信息 * * @return */ public static Shard getCurrentThreadShard() { return ThreadInfoHolder.shardLocal.get(); } /** * 在当前线程存储账号对应分片信息 * * @param shard */ public static void addCurrentThreadShard(Shard shard) { ThreadInfoHolder.shardLocal.set(shard); } /** * 清空前线程存储分片信息 * * @param shard */ public static void cleanCurrentThreadShard() { ThreadInfoHolder.shardLocal.remove(); } }
public class Shard { //存放Account数据的DB_ID private Integer dbId; /** * @return the dbId */ public Integer getDbId() { return dbId; } /** * @param dbId the dbId to set */ public void setDbId(Integer dbId) { this.dbId = dbId; } }
import java.util.List; import java.util.Map; import com.common.dao.model.User; public interface DbInfoService { public List<Map<String,Object>> getUserInfo(User user); } com.service.impl.DbInfoServiceImpl实现类: package com.service.impl; import java.util.List; import java.util.Map; import com.common.bean.Shard; import com.common.bean.ThreadInfoHolder; import com.common.dao.BaseDao; import com.common.dao.model.User; import com.service.DbInfoService; public class DbInfoServiceImpl implements DbInfoService{ public BaseDao baseDao; public void setBaseDao(BaseDao baseDao) { this.baseDao = baseDao; } public List<Map<String,Object>> getUserInfo(User user) { baseDao.add("login.addUser", user); List<Map<String,Object>> result=baseDao.getList("login.getUserInfo",user.getName()); return result; } }
public class Test { public DbInfoService dbInfoService; public void setDbInfoService(DbInfoService dbInfoService) { this.dbInfoService = dbInfoService; } public List<Map<String,Object>> getInfo(User user) { List<Map<String,Object>> result=dbInfoService.getUserInfo(user); return result; } }
public class TransactionTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:spring/datasource-config.xml"); Test t=(Test)ctx.getBean("test"); User user=new User(); user.setName("xj"); user.setPassword("123"); Shard shard=new Shard(); shard.setDbId(1);//使用datasource1 ThreadInfoHolder.addCurrentThreadShard(shard); List<Map<String,Object>> result =t.getInfo(user); shard.setDbId(2);//使用datasource2 ThreadInfoHolder.addCurrentThreadShard(shard); List<Map<String,Object>> result1 =t.getInfo(user); System.out.println(result); } }
最近对动态路由进行了学习,现总结如下:
通过集成org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource类,自定义动态数据源。
配置如下:datasource-config.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- 数据源配置 --> <bean id="dataSourceFirst" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@10.20.151.4:1521:ptdev" /> <property name="username" value="pt" /> <property name="password" value="pt" /> <property name="maxActive" value="200" /> <property name="maxIdle" value="5" /> <property name="poolPreparedStatements" value="true" /> <property name="removeAbandoned" value="true" /> <property name="removeAbandonedTimeout" value="300" /> </bean> <bean id="dataSourceSecond" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@10.20.151.12:1521:pt10g" /> <property name="username" value="pt" /> <property name="password" value="pt" /> <property name="maxActive" value="200" /> <property name="maxIdle" value="5" /> <property name="poolPreparedStatements" value="true" /> <property name="removeAbandoned" value="true" /> <property name="removeAbandonedTimeout" value="300" /> </bean> <bean id="dataSource" class="com.common.bean.RoutingDataSource"> <property name="targetDataSources"> <map> <entry key="1" value-ref="dataSourceFirst" /> <entry key="2" value-ref="dataSourceSecond" /> </map> </property> <property name="defaultTargetDataSource"> <ref local="dataSourceFirst" /> </property> </bean> <!--配置事物--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource" /> </property> </bean> <bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" lazy-init="true" /> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="lobHandler" ref="lobHandler" /> <property name="configLocations" value="classpath*:/ibatis/config/sql-map.xml" /> </bean> <bean id="txAttributeSource" class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource"> <property name="properties"> <props> <prop key="add*">PROPAGATION_REQUIRED,-PtServiceException</prop> <prop key="update*">PROPAGATION_REQUIRED,-PtServiceException</prop> <prop key="delete*">PROPAGATION_REQUIRED,-PtServiceException</prop> <prop key="batch*">PROPAGATION_REQUIRED,-PtServiceException</prop> <prop key="get*">PROPAGATION_REQUIRED,-PtServiceException</prop> </props> </property> </bean> <bean id="transactionManagerProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyTargetClass"> <value>true</value> </property> <property name="target"> <ref bean="transactionManager" /> </property> </bean> <bean id="transactionDefinition" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <property name="transactionManager"> <ref bean="transactionManagerProxy" /> </property> <property name="transactionAttributeSource"> <ref bean="txAttributeSource" /> </property> </bean> <bean id="baseDao" class="com.common.dao.impl.BaseDaoImpl"> <property name="sqlMapClient"> <ref bean="sqlMapClient" /> </property> <property name="dataSource"> <ref bean="dataSource" /> </property> </bean> <bean id="dbInfoService" parent="transactionDefinition"> <property name="target"> <bean class="com.service.impl.DbInfoServiceImpl"> <property name="baseDao" ref="baseDao" /> </bean> </property> </bean> <bean id="test" class="com.common.bean.Test"> <property name="dbInfoService" ref="dbInfoService"></property> </bean> </beans>
package com.common.bean; import java.sql.Connection; import java.sql.SQLException; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class RoutingDataSource extends AbstractRoutingDataSource { protected Object determineCurrentLookupKey() { //获取当前线程处理的账号对应分片信息 Shard shard = ThreadInfoHolder.getCurrentThreadShard(); //动态选定DataSource String dbId = shard == null ? null : String.valueOf(shard.getDbId()); return dbId; } @Override public String toString() { //获取当前线程处理的账号对应分片信息 Shard shard = ThreadInfoHolder.getCurrentThreadShard(); //动态选定DataSource String dbId = shard == null ? null : String.valueOf(shard.getDbId()); return "DB ID " + dbId + ":" + super.toString(); } public String getTargetDbId() throws SQLException { Connection conn = null; try { //jdbc:oracle:thin:@10.20.151.4:1521:ptdev, UserName=xx, Oracle JDBC driver conn = determineTargetDataSource().getConnection(); if (conn != null) { String connectionDesc = conn.getMetaData().getURL(); int beginIdx = connectionDesc.indexOf("@") + 1; int endIdx = connectionDesc.indexOf(":", beginIdx); return connectionDesc.substring(beginIdx, endIdx); } } finally { if (conn != null) { conn.close(); } } return null; } }
运行结果是分别向数据库1和数据库2中插入了1调记录。
注意:
由于对DbInfoService配置了事物,如果将切换数据源的代码ThreadInfoHolder.addCurrentThreadShard(shard);放在在DbInfoServiceImpl类的getUserInfo方法中,如下:
public List<Map<String,Object>> getUserInfo(User user)
{
Shard shard=new Shard();
shard.setDbId(1);
ThreadInfoHolder.addCurrentThreadShard(shard);
baseDao.add("login.addUser", user);
shard.setDbId(2);
ThreadInfoHolder.addCurrentThreadShard(shard);
baseDao.add("login.addUser", user);
List<Map<String,Object>> result=baseDao.getList("login.getUserInfo",user.getName());
return result;
}
main方法改为:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:spring/datasource-config.xml");
Test t=(Test)ctx.getBean("test");
User user=new User();
user.setName("xj");
user.setPassword("123");
List<Map<String,Object>> result =t.getInfo(user);
System.out.println(result);
}
则运行结果将是向数据库1中插入2条相同的记录,而不是分别想数据库1,2各插一条记录,产生该结果的原因是应为由于 DbInfoServiceImpl配置了事物,所以在getUserInfo方法中的第一次连数据库会新建一个连接,而后将该连接绑定在线程的本地变量 即ThreadLoad中,当以后在需要访问数据库时不在新建连接而是使用这个绑定了老连接,在本例子中,即第一次的数据库连接是连数据库1,当第二次访 问数据库时,使用的还是这个数据库1的连接,即切换数据源设置代码shard.setDbId(2);ThreadInfoHolder.addCurrentThreadShard(shard);失效。同理我们可以退出,如果将切换数据源代码放在Test类的getInfo方法中,即:
public List<Map<String,Object>> getInfo(User user)
{
Shard shard=new Shard();
shard.setDbId(1);
ThreadInfoHolder.addCurrentThreadShard(shard);
List<Map<String,Object>> result=dbInfoService.getUserInfo(user);
shard.setDbId(2);
ThreadInfoHolder.addCurrentThreadShard(shard);
List<Map<String,Object>> result1=dbInfoService.getUserInfo(user);
return result;
}
这样是能正确运行的,应为在调用事物前我们已经切换了数据源。
相关推荐
将Nacos与Spring Cloud Gateway结合,可以实现动态路由,即在运行时动态调整路由规则,提高系统的灵活性和可扩展性。 首先,我们要理解Spring Cloud Gateway的核心概念。它是一个基于Spring Framework 5、Project ...
《Spring Cloud最佳实践项目详解》 在现代微服务架构中,Spring Cloud以其强大的服务治理能力,成为了Java开发者构建分布式系统的重要工具。本项目实例深入探讨了Spring Cloud的最佳实践,涵盖了Spring Cloud全家桶...
Spring Cloud微服务实践的知识点可以从文档标题及内容中提炼出以下几点: ### 微服务架构 微服务架构是一套方法论,它提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的进程中,并围绕业务能力构建。...
1. **Spring的AbstractRoutingDataSource**:这是Spring提供的核心类,用于实现动态数据源切换。这个抽象类维护了一个数据源路由决策表,可以根据特定的规则(如事务上下文、线程局部变量等)来决定使用哪个数据源。...
在这个Spring Boot进阶实践中,我们将探讨如何高效地使用开发工具、构建RESTful API,并深入理解配置文件的处理。 首先,我们来谈谈开发工具。Spring Tool Suite (STS) 是一个专为Spring开发者设计的集成开发环境...
Spring Cloud(二十):Gateway 动态路由(金丝雀发布/灰度发布) 01-17 Spring Cloud(十九):Spring Cloud Gateway(读取、修改 Request Body) 2018 11-10 Spring Cloud(十八):Alibaba 之 Nacos 06-20 ...
在Spring Boot应用中,多数据源自动路由是一项重要的功能,尤其在处理多个数据库或需要根据特定条件切换数据源的场景下...这个项目是一个很好的学习资源,对于理解和实践Spring Boot的多数据源管理具有很高的参考价值。
2. **配置管理**:Spring Cloud Config允许集中化管理所有服务的配置,方便动态更新。 3. **断路器模式**:通过集成Hystrix,可以防止服务雪崩,确保系统的稳定性和容错性。 4. **消息队列**:Spring Cloud Stream和...
1. **配置Eureka**:在服务提供者和服务消费者中都引入Eureka的相关依赖,配置Eureka Server的地址,并在启动时向Eureka注册服务。 2. **定义服务接口**:在服务消费者项目中,创建一个接口,该接口定义了要调用的...
总之,Spring的数据库动态切换功能通过抽象化DataSource路由,实现了在运行时根据业务逻辑或环境条件灵活切换数据库的能力。这一特性大大增强了应用程序的灵活性和可扩展性。通过深入学习和实践,你可以将其应用于...
在本项目中,Spring Cloud Gateway与Zookeeper的整合意味着网关可以从Zookeeper中动态获取服务实例的信息,实现动态路由。当服务实例在Zookeeper上注册或下线时,Gateway能够自动感知这些变化,更新其路由规则,确保...
它提供了动态路由,服务熔断,负载均衡,以及针对微服务的细粒度控制等功能。通过自定义过滤器,我们可以实现复杂的业务逻辑,比如身份验证和权限控制。 接下来,我们关注Spring Security。它是一个灵活且可扩展的...
1. **配置数据源**:首先,你需要配置多个数据源,可以使用Spring的AbstractRoutingDataSource作为基础,该类可以根据某种路由策略(如线程本地变量、请求参数等)动态选择数据源。 2. **定义路由逻辑**:创建一个...
这些视频课程结合源码分析和课件学习,可以帮助开发者深入理解SpringBoot和SpringCloud的核心原理,并掌握实际应用中的最佳实践。对于想要在微服务领域深入发展的IT专业人士来说,这是一个非常有价值的资源。通过...
Spring Cloud Zuul就是这样一个功能强大的边缘服务,它实现了动态路由、过滤器等功能,允许对请求进行预处理和后处理。通过Zuul,我们可以实现诸如身份验证、监控、限流等高级功能,同时隐藏内部微服务的复杂性。 ...
总的来说,这个项目是一个典型的前后端分离示例,通过Maven和Spring Boot构建后端服务,而AngularJS则负责前端的动态路由和视图渲染。这样的组合为开发高效、响应式的Web应用提供了坚实的基础。通过深入学习和实践这...
Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具集,它为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)...
7. 实战演练:通过具体的项目案例,逐步引导学习者掌握SpringCloud的实践技巧和最佳实践。 通过这两部视频的学习,开发者不仅能深入理解SpringCloud的基本概念和核心组件,还能通过实战案例提升微服务开发和管理的...
Spring Cloud和Spring Boot是两个非常重要的Java开发框架,它们在微服务架构中...通过学习这个案例,开发者能够了解Spring Boot和Spring Cloud的整合过程,掌握微服务架构的基本实践,提升开发效率,并增强系统健壮性。
3. **API网关**:Zuul或Spring Cloud Gateway是Spring Cloud提供的API网关服务,负责路由请求到相应的服务实例,同时可以进行身份验证、动态路由、负载均衡、熔断等操作。 4. **负载均衡**:Ribbon是Netflix提供的...