使用spring的动态路由实现数据库负载均衡
系统中存在的多台服务器是“地位相当”的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这几台数据库服务器之间进行合理分配, 这个时候,通过统一的一个DataSource来屏蔽这种请求分配的需求,从而屏蔽数据访问类与具体DataSource的耦合;
系统中存在的多台数据库服务器现在地位可能相当也可能不相当,但数据访问类在系统启动时间无法明确到底应该使用哪一个数据源进行数据访问,而必须在系统运行期间通过某种条件来判定到底应该使用哪一个数据源,这个时候,我们也得使用这种“合纵连横”的方式向数据访问类暴露一个统一的DataSource,由该DataSource来解除数据访问类与具体数据源之间的过紧耦合;
更多场景需要读者根据具体的应用来判定,不过,并非所有的应用要做这样的处理,如果能够保持简单,那尽量保持简单.要实现这种“合纵连横”的多数据源管理方式,总的指导原则就是实现一个自定义的DataSource,让该DataSource来管理系统中存在的多个与具体数据库挂钩的数据源, 数据访问类只跟这个自定义的DataSource打交道即可。在spring2.0.1发布之前,各个项目中可能存在多种针对这种情况下的多数据源管理方式, 不过,spring2.0.1发布之后,引入了AbstractRoutingDataSource,使用该类可以实现普遍意义上的多数据源管理功能。
假设我们有三台数据库用来实现负载均衡,所有的数据访问请求最终需要平均的分配到这三台数据库服务器之上,那么,我们可以通过继承AbstractRoutingDataSource来快速实现一个满足这样场景的原型(Prototype):
- public class PrototypeLoadBalanceDataSource extends AbstractRoutingDataSource {
- private Lock lock = new ReentrantLock();
- private int counter = 0;
- private int dataSourceNumber = 3;
- @Override
- protected Object determineCurrentLookupKey() {
- lock.lock();
- try{
- counter++;
- int lookupKey = counter % getDataSourceNumber();
- return new Integer(lookupKey);
- }finally{
- lock.unlock();
- }
- }
- // ...
- }
我们在介绍AbstractRoutingDataSource的时候说过,要继承该类,通常只需要给出determineCurrentLookupKey()方法的逻辑即可。 下面是针对PrototypeLoadBalanceDataSource的配置:
- <bean id="dataSourc1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
- <property name="url" value=".."/>
- <property name="driverClassName" value=".."/>
- <property name="username" value=".."/>
- <property name="password" value=".."/>
- <!-- other property settings -->
- </bean>
- <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
- <property name="url" value=".."/>
- <property name="driverClassName" value=".."/>
- <property name="username" value=".."/>
- <property name="password" value=".."/>
- <!-- other property settings -->
- </bean>
- <bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
- <property name="url" value=".."/>
- <property name="driverClassName" value=".."/>
- <property name="username" value=".."/>
- <property name="password" value=".."/>
- <!-- other property settings -->
- </bean>
- <util:map id="dataSources">
- <entry key="0" value-ref="dataSource1"/>
- <entry key="1" value-ref="dataSource2"/>
- <entry key="2" value-ref="dataSource3"/>
- </util:map>
- <bean id="dataSourceLookup" class="org.springframework.jdbc.datasource.lookup.MapDataSourceLookup">
- <constructor-arg>
- <ref bean="dataSources"/>
- </constructor-arg>
- </bean>
- <bean id="dataSource" class="..PrototypeLoadBalanceDataSource">
- <property name="defaultTargetDataSource" ref="dataSourc1"/>
- <property name="targetDataSources" ref="dataSources"/>
- <property name="dataSourceLookup" ref=""/>
- </bean>
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"/>
- </bean>
- <bean id="someDao" class="...">
- <property name=""jdbcTemplate"" ref=""jdbcTemplate""/>
- <!-- other property settings -->
- </bean>
使用spring的动态路由实现数据库读写分离
Spring2.0.1以后的版本已经支持配置多数据源,并且可以在运行的时候动态加载不同的数据源。通过继承AbstractRoutingDataSource就可以实现多数据源的动态转换。目前做的项目就是需要访问2个数据源,每个数据源的表结构都是相同的,所以要求数据源的变动对于编码人员来说是透明,也就是说同样SQL语句在不同的环境下操作的数据库是不一样的。具体的流程如下:
1.建立一个获得和设置上下文的类
- package com.lvye.base.dao.impl.jdbc;
- /**
- *连接哪个数据源的环境变量
- */
- public class JdbcContextHolder {
- private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
- public static void setJdbcType(String jdbcType) {
- contextHolder.set(jdbcType);
- }
- public static void setSlave(){
- setJdbcType("slave");
- }
- public static void setMaster(){
- clearJdbcType();
- }
- public static String getJdbcType(){
- return (String) contextHolder.get();
- }
- public static void clearJdbcType() {
- contextHolder.remove();
- }
- }
2.建立动态数据源类,这个类必须继承AbstractRoutingDataSource
- package com.lvye.base.dao.impl.jdbc;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- public class DynamicDataSource extends AbstractRoutingDataSource{
- /*(non-Javadoc)
- *@see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
- *@author wenc
- */
- @Override
- protected Object determineCurrentLookupKey() {
- return JdbcContextHolder.getJdbcType();
- }
- }
这个类实现了determineCurrentLookupKey方法,该方法返回一个Object,一般是返回字符串。该方法中直接使用了JdbcContextHolder.getJdbcType();方法获得上下文环境并直接返回。
3.编写spring的配置文件配置数据源
- <beans>
- <bean id="master" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
- <property name="driverClass">
- <value>com.mysql.jdbc.Driver</value>
- </property>
- <property name="jdbcUrl">
- <value>jdbc:mysql://192.168.18.143:3306/wenhq?useUnicode=true&characterEncoding=utf-8</value>
- </property>
- <property name="user">
- <value>root</value>
- </property>
- <property name="password">
- <value></value>
- </property>
- </bean>
- <bean id="slave" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
- <property name="driverClass">
- <value>com.mysql.jdbc.Driver</value>
- </property>
- <property name="jdbcUrl">
- <value>jdbc:mysql://192.168.18.144:3306/ wenhq?useUnicode=true&characterEncoding=utf-8</value>
- </property>
- <property name="user">
- <value>root</value>
- </property>
- <property name="password">
- <value></value>
- </property>
- </bean>
- <bean id="mySqlDataSource" class="com.lvye.base.dao.impl.jdbc.DynamicDataSource">
- <property name="targetDataSources">
- <map>
- <entry key="slave" value-ref="slave"/>
- </map>
- </property>
- <property name="defaultTargetDataSource" ref="master"/>
- </bean>
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="mySqlDataSource" />
- </bean>
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="mySqlDataSource" />
- </bean>
- </beans>
在这个配置中可以看到首先配置两个真实的数据库连接,使用的msyql数据库;master和slave是按照mysql配置的主从关系的数据库,数据会自动实时同步mySqlDataSource会根据上下文选择不同的数据源。在这个配置中第一个property属性配置目标数据源,<entry key="slave" value-ref=" slave"/>中key的值必须要和JdbcContextHolder类中设置的参数值相同,如果有多个值,可以配置多个<entry>标签。第二个property属性配置默认的数据源,我们一般默认为主数据库。有些朋友喜欢使用hibernate,只需要把上面的jdbcTemplate替换为hibernate的就可以了。
4.多数据库连接配置完毕,简单测试
- public void testSave() throws Exception{
- jdbcContextHolder.setSlave();//设置从数据源
- Test test = new Test();
- test.setTest("www.wenhq.com.cn");
- mydao.save(test);//使用dao保存实体
- jdbcContextHolder.setMaster();//设置主数据源
- mydao.save(test);//使用dao保存实体到另一个库中
- }
5.实现读写分离,上面的测试通过了,现在就简单了我的程序是使用jdbc实现的保存数据,只是使用了c3p0的数据库连接池而已。把所有访问数据库的方法包装一下,统一调用。把执行更新的sql发送到主数据库了
- public void execute(String sql) {
- JdbcContextHolder.setMaster();
- log.debug("execute-sql:" + sql);
- jdbcTemplate.execute(sql);
- }
把查询的发送到从数据库,需要注意的是像LAST_INSERT_ID这类的查询需要特殊处理,必须发送到主数据库,建议增加专门的方法,用于获取自增长的主键。
- public List findObject(String queryString, Class clazz) {
- JdbcContextHolder.setSlave();
- log.debug("findObject-sql:" + queryString);
- List list = jdbcTemplate.queryForList(queryString);
- try {
- list = StringBase.convertList(list, clazz);// 将List转化为List<clazz>
- } catch (Exception e) {
- log.error("List convert List<Object> error:" + e);
- }
- AbstractRoutingDataSourcereturn list;
- }
1. 前提
好长时间不写博客了,应该吐槽,写点什么东西了!最近在研究数据库读写分离,分表分库的一些东西。其实这个问题好早之前就想好,只是以前使用hibernate,难点是不好判断什么样的sql走读库,什么样的sql走主库?用正则匹配开头或许可以,/^select 没想出什么好的解决方法,mybatis就不一样了,mappedstatement有commandtype属性,象select,update,delete等类型,为实现读写分离打下来良好的基础。
2. 解决方法
LazyConnectionProxy + RoutingDataSource + Plugin
在SqlSessionTemplate,创建DefaultSqlSession的时候,使用connection proxy的代理,这时并没有真正的获取connection,因为我们不知道是要取读还是写的数据源。待到StatementHandler的prepare()使用connection创建PreparedStatement的时候再根据mappedstatement的commandType去路由获取真实的connection。
RoutingDataSource支持一主一从,或者一主多从并采用round robin的方式简单负载均衡,预留接口路由和负载均衡策略可自定义。
不支持事务,适合auto commit为true的场景。表述能力
applicationContext-common.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- 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" xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd">
- <!-- 导入属性配置文件 -->
- <context:property-placeholder location="classpath*:*.properties" />
- <bean id="abstractDataSource" abstract="true"
- class="com.mchange.v2.c3p0.ComboPooledDataSource"
- destroy-method="close">
- <property name="driverClass" value="com.mysql.jdbc.Driver" />
- <property name="user" value="root" />
- <property name="password" value="" />
- </bean>
- <bean id="readDS" parent="abstractDataSource">
- <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
- </bean>
- <bean id="writeDS" parent="abstractDataSource">
- <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
- </bean>
- <!--简单的一个master和一个slaver 读写分离的数据源 -->
- <bean id="routingDS" class="com.test.rwmybatis.RoutingDataSource">
- <property name="targetDataSources">
- <map key-type="java.lang.String">
- <entry key="read" value-ref="readDS"></entry>
- <entry key="write" value-ref="writeDS"></entry>
- </map>
- </property>
- <property name="defaultTargetDataSource" ref="writeDS"></property>
- </bean>
- <!-- 适用于一个master和多个slaver的场景,并用roundrobin做负载均衡 -->
- <bean id="roundRobinDs" class="com.test.rwmybatis.RoundRobinRWRoutingDataSource">
- <property name="writeDataSource" ref="writeDS"></property>
- <property name="readDataSoures">
- <list>
- <ref bean="readDS"/>
- <ref bean="readDS"/>
- <ref bean="readDS"/>
- </list>
- </property>
- <property name="readKey" value="READ"></property>
- <property name="writeKey" value="WRITE"></property>
- </bean>
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="routingDS" />
- <property name="configLocation" value="classpath:mybatis-config.xml" />
- <!-- mapper和resultmap配置路径 -->
- <property name="mapperLocations">
- <list>
- <value>classpath:com/test/rwmybatis/mapper/**/*-Mapper.xml
- </value>
- </list>
- </property>
- </bean>
- <bean id="sqlSessionTemplate" class="com.test.rwmybatis.RWSqlSessionTemplate">
- <constructor-arg ref="sqlSessionFactory" />
- </bean>
- <!-- 通过扫描的模式,扫描目录下所有的mapper, 根据对应的mapper.xml为其生成代理类-->
- <bean id="mapper" class="com.test.rwmybatis.RWMapperScannerConfigurer">
- <property name="basePackage" value="com.test.rwmybatis.mapper" />
- <property name="sqlSessionTemplate" ref="sqlSessionTemplate"></property>
- </bean>
- <!-- <bean id="monitor" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"></bean> -->
- <!-- <aop:config> -->
- <!-- <aop:pointcut expression="execution(* com.taofang.smc.persistence..*.*(..))" id="my_pc"/> -->
- <!-- <aop:advisor advice-ref="monitor" pointcut-ref="my_pc"/> -->
- <!-- </aop:config> -->
- </beans>
相关推荐
基于SpringMVC+Spring+MyBatis+Maven项目案例源码+数据库.zip 基于SpringMVC+Spring+MyBatis+Maven项目案例源码+数据库.zip 基于SpringMVC+Spring+MyBatis+Maven项目案例源码+数据库.zip 基于SpringMVC+Spring+...
"spring+hibernate和spring+myBatis实现连接多个数据库,同时操作的项目"是针对这种需求的一个解决方案,旨在提供一种灵活且动态的数据源切换机制。 首先,Spring框架作为Java领域中最受欢迎的应用框架之一,其强大...
在数据库读写分离中,MyBatis可以方便地配置多个数据源,从而在执行查询时选择读库,而在保存或更新数据时选择写库。 Maven是一个项目管理和综合工具,它管理项目的构建、报告和文档,依赖关系的解决,以及构建过程...
spring +springboot+mybatis+maven 读写分离,数据库采用mysql, 采用springboot 采用项目框架搭建,继承spring 中的AbstractRoutingDataSource,实现 determineCurrentLookupKey 进行数据源的动态切换,采用Spring ...
SSM(spring+spring MVC+mybatis)开发学生信息后台管理系统,实现学生增删改查...spring+spring mvc+mybatis+jquery 数据库文件 压缩包根目录test3\dbyyx.sql jar包文件 压缩包根目录\test3\WebContent\WEB-INF\lib
SpringMVC+Spring+Mybatis集成开发环境SpringMVC+Spring+Mybatis集成开发环境SpringMVC+Spring+Mybatis集成开发环境SpringMVC+Spring+Mybatis集成开发环境SpringMVC+Spring+Mybatis集成开发环境SpringMVC+Spring+...
在企业级应用开发中,Spring 和 MyBatis 是两个非常重要的框架。Spring 作为一个全面的后端开发框架,提供依赖注入、AOP(面向切面编程)、MVC(模型-视图-控制器)等核心功能,极大地提高了开发效率和代码的可维护...
总的来说,"struts2+spring+mybatis+easyui"的实现是一个标准的Java Web项目结构,它利用Maven进行构建管理,通过整合四个组件,实现了后端的业务逻辑处理、数据访问和前端的用户界面展示。这种架构在实际开发中具有...
使用环境:MyEclipse/Eclipse + Tomcat + MySQL。 使用技术:Spring MVC + Spring + MyBatis / JSP + Servlet + JavaBean + JDBC。
"SpringMvc+Spring+Mybatis+Maven+注解方式"是一个经典的Java后端技术栈,它整合了四个关键组件,为开发人员提供了强大的工具和框架支持。下面将详细讲解这四个组件及其整合方式。 1. **Spring Framework**: ...
SSM(Spring MVC + Spring + MyBatis)是Java Web开发中常见的三层架构组合,它将Spring MVC作为表现层框架,Spring作为业务层容器,MyBatis作为数据访问层框架。下面将详细介绍这三个组件以及它们如何协同工作。 *...
基于SpringMVC+Spring+MyBatis的博客网站系统源码+数据库+使用说明(毕业设计)基于SpringMVC+Spring+MyBatis的博客网站系统源码+数据库+使用说明(毕业设计)基于SpringMVC+Spring+MyBatis的博客网站系统源码+...
本资源提供的"SpringMvc+Spring+MyBatis+Maven整合视频源码+数据库"是针对这四个框架的集成实践,通过源码分析,有助于开发者深入理解它们的协同工作方式。 Spring MVC 是Spring框架的一部分,它作为控制器,负责...
使用环境:MyEclipse/Eclipse + Tomcat + MySQL。 使用技术:Spring MVC + Spring + MyBatis / JSP + Servlet + JavaBean + JDBC。
Struts+Spring+Mybatis+EasyUI(jQuery)注解案例。采用注解的方式跟踪代码依赖性,实现替代配置文件功能。里面包含了平时用的增删改查及分页,分页查询时用的是存储过程,提高了数据库的性能。代码简单、易懂。
在Struts2+Spring+MyBatis的架构中,MyBatis负责与数据库交互,通过XML或注解方式配置SQL语句,使得数据库操作更加灵活且易于维护。 **整合过程** 1. **配置Spring**:创建Spring配置文件,定义Bean,包括Action、...
基于SSM(Spring+SpringMVC+Mybatis)的新闻管理系统源码+数据库.zip 基于SSM(Spring+SpringMVC+Mybatis)的新闻管理系统源码+数据库.zip 基于SSM(Spring+SpringMVC+Mybatis)的新闻管理系统源码+数据库.zip 基于SSM...
在数据库读写分离的场景下,MyBatis可以灵活地配置数据源,分别处理读操作和写操作。 3. **Druid**:Druid是阿里巴巴开源的数据库连接池实现,它具有监控、扩展性好、性能优秀等特点。在读写分离的架构中,Druid...
springboot+mybatis+mysql实现读写分离 先在建好mysql主从数据库的配置,然后在代码中根据读写分离或强制读取master数据库中的数据 mysql数据库设置主从,参考: ...
本项目将这些框架进行了整合,并进行了数据库读写分离的配置,以提高系统的稳定性和性能。 首先,Spring框架作为基础,提供了强大的依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented ...