Spring JDBC主从数据库访问配置
通过昨天学习的自定义配置注释的知识,探索了解一下web主从数据库的配置:
背景:
主从数据库:主要是数据上的读写分离;
数据库的读写分离的好处?
1. 将读操作和写操作分离到不同的数据库上,避免主服务器出现性能瓶颈;
2. 主服务器进行写操作时,不影响查询应用服务器的查询性能,降低阻塞,提高并发;
3. 数据拥有多个容灾副本,提高数据安全性,同时当主服务器故障时,可立即切换到其他服务器,提高系统可用性;
读写分离的基本原理就是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE)操作,而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到其他从数据库。以SQL为例,主库负责写数据、读数据。读库仅负责读数据。每次有写库操作,同步更新到读库。写库就一个,读库可以有多个,采用日志同步的方式实现主库和多个读库的数据同步。
配置步骤:
此文暂时不包含数据同步问题,数据同步移步
1.配置数据源
<bean id="masterDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="${database.master.jdbcUrl}"></property>
<property name="user" value="${database.master.user}" />
<property name="password" value="${database.master.password}" />
<property name="maxPoolSize" value="${database.master.maxPoolSize}"></property>
<property name="minPoolSize" value="${database.master.minPoolSize}"></property>
<property name="maxIdleTime" value="${database.master.maxIdleTime}"></property>
<property name="idleConnectionTestPeriod" value="${database.master.idleConnectionTestPeriod}"></property>
</bean> <bean id="slaveDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="${database.slave.jdbcUrl}"></property>
<property name="user" value="${database.slave.user}" />
<property name="password" value="${database.slave.password}" />
<property name="maxPoolSize" value="${database.slave.maxPoolSize}"></property>
<property name="minPoolSize" value="${database.slave.minPoolSize}"></property>
<property name="maxIdleTime" value="${database.slave.maxIdleTime}"></property>
<property name="idleConnectionTestPeriod" value="${database.slave.idleConnectionTestPeriod}"></property>
</bean>
2.配置切换数据源类
xml
<-- 此类继承了AbstractRoutingDataSource 类,且 AbstractRoutingDataSource类 为Spring jdbc中提供的类,需要重写其中的determineCurrentLookupKey()方法,获取当前切换到的数据库源名称-->
<bean id="dataSource" class="com.imzhitu.admin.common.dataSourceMasterSlave.DynamicDataSource">
<property name="targetDataSources"><-- 将数据源置入到类中,通过之后的aop拦截到的数据库名称,匹配到指定的数据源进而链接 -->
<map key-type="java.lang.String">
<entry key="master" value-ref="masterDataSource"/>
<entry key="slave" value-ref="slaveDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
DynamicDataSource.java
package com.imzhitu.admin.common.dataSourceMasterSlave; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /**
* 动态数据源,动态获取数据源的实现
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource{ /**
* 用户返回当且切换到的数据库
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();//DynamicDataSourceHolder有获取和设置当前数据库的方法get & put
} }
DynamicDataSourceHolder.java
package com.imzhitu.admin.common.dataSourceMasterSlave; /**
* 动态数据源holder
*
*/
public class DynamicDataSourceHolder {
public static final ThreadLocal<String> holder = new ThreadLocal<String>(); public static void putDataSource(String name) {
holder.set(name);
} public static String getDataSource() {
return holder.get();
}
}
AbstractRoutingDataSource.java <简化版>
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.jdbc.datasource.lookup; import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource; import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.Assert; /**
* Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
* calls to one of various target DataSources based on a lookup key. The latter is usually
* (but not necessarily) determined through some thread-bound transaction context.
*
* @author Juergen Hoeller
* @since 2.0.1
* @see #setTargetDataSources
* @see #setDefaultTargetDataSource
* @see #determineCurrentLookupKey()
*/
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { private Map<Object, Object> targetDataSources;//所有数据源,在xml中有相应的配置 private Object defaultTargetDataSource;//默认数据源,在xml中有相应的配置private Map<Object, DataSource> resolvedDataSources;//将targetDataSources值传入其中,做值的传递 private DataSource resolvedDefaultDataSource;//同targetDataSources /**
* Specify the map of target DataSources, with the lookup key as key.
* The mapped value can either be a corresponding {@link javax.sql.DataSource}
* instance or a data source name String (to be resolved via a
* {@link #setDataSourceLookup DataSourceLookup}).
* <p>The key can be of arbitrary type; this class implements the
* generic lookup process only. The concrete key representation will
* be handled by {@link #resolveSpecifiedLookupKey(Object)} and
* {@link #determineCurrentLookupKey()}.
*/
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
} /**
* Specify the default target DataSource, if any.
* <p>The mapped value can either be a corresponding {@link javax.sql.DataSource}
* instance or a data source name String (to be resolved via a
* {@link #setDataSourceLookup DataSourceLookup}).
* <p>This DataSource will be used as target if none of the keyed
* {@link #setTargetDataSources targetDataSources} match the
* {@link #determineCurrentLookupKey()} current lookup key.
*/
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
} /**
* Specify whether to apply a lenient fallback to the default DataSource
* if no specific DataSource could be found for the current lookup key.
* <p>Default is "true", accepting lookup keys without a corresponding entry
* in the target DataSource map - simply falling back to the default DataSource
* in that case.
* <p>Switch this flag to "false" if you would prefer the fallback to only apply
* if the lookup key was {@code null}. Lookup keys without a DataSource
* entry will then lead to an IllegalStateException.
* @see #setTargetDataSources
* @see #setDefaultTargetDataSource
* @see #determineCurrentLookupKey()
*/
public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
} /**
* Set the DataSourceLookup implementation to use for resolving data source
* name Strings in the {@link #setTargetDataSources targetDataSources} map.
* <p>Default is a {@link JndiDataSourceLookup}, allowing the JNDI names
* of application server DataSources to be specified directly.
*/
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
} @Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
//*** 将targetDataSources的值传递给resolvedDataSources
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
//***
} /**
* Resolve the given lookup key object, as specified in the
* {@link #setTargetDataSources targetDataSources} map, into
* the actual lookup key to be used for matching with the
* {@link #determineCurrentLookupKey() current lookup key}.
* <p>The default implementation simply returns the given key as-is.
* @param lookupKey the lookup key object as specified by the user
* @return the lookup key as needed for matching
*/
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
} /**
* Resolve the specified data source object into a DataSource instance.
* <p>The default implementation handles DataSource instances and data source
* names (to be resolved via a {@link #setDataSourceLookup DataSourceLookup}).
* @param dataSource the data source value object as specified in the
* {@link #setTargetDataSources targetDataSources} map
* @return the resolved DataSource (never {@code null})
* @throws IllegalArgumentException in case of an unsupported value type
*/
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
} /**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/ //通过调用determineCurrentLookupKey()获取当前数据源名称,并匹配到相应的数据源返回;
//此方法在本类的 getConnection() 方法中调用,获取当前数据源的连接Connection,从而进行数据库操作
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
} /**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/ //获取当前数据源的名称;在本类的子类中需要重写此方法
protected abstract Object determineCurrentLookupKey(); }
3.配置AOP,自定义注释,获取访问是应该链接的数据库源名
xml
<!-- 配置数据库注解aop -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="manyDataSourceAspect" class="com.imzhitu.admin.common.dataSourceMasterSlave.DataSourceAspect"/>
<aop:config>
<aop:aspect id="dataSourceCut" ref="manyDataSourceAspect"><-- 切面类 -->
<aop:pointcut expression="execution(* com.imzhitu.admin..*.mapper.*.*(..))" id="dataSourceCutPoint"/><-- 配置切点 -->
<aop:before pointcut-ref="dataSourceCutPoint" method="before"/><-- 切面执行方法 -->
</aop:aspect>
</aop:config>
DataSourceAspect.java
package com.imzhitu.admin.common.dataSourceMasterSlave; import java.lang.reflect.Method; import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature; /**
* 数据源动态选择切面
*
*/
public class DataSourceAspect {
private Logger log = Logger.getLogger(DataSourceAspect.class);
public void before(JoinPoint point){
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
try {
Method m = classz[0].getMethod(method, parameterTypes);
if ( m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);//获取访问mapper中的注释
DynamicDataSourceHolder.putDataSource(data.value());//获取注释中的value值,确定访问的数据源
if(log.isDebugEnabled()){
log.debug("DataSourceAspect:======================="+data.value());
}
}
} catch (Exception e) {
// handle exception
e.printStackTrace();
}
}
}
DataSource.java<-- 自定义注释 -->
package com.imzhitu.admin.common.dataSourceMasterSlave; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 数据库annotation定义
* @DataSource('master') / @DataSource('slave')
*
*/ @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();//唯一值,所以注释中没有写@DataSource(value = 'master');也可以写成 String value() defalut "master";即默认访问主数据库
}
mapper.java<读写分离>
package com.imzhitu.admin.ztworld.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.hts.web.common.pojo.HTWorldSubtitleDto;
import com.imzhitu.admin.common.dataSourceMasterSlave.DataSource;
import com.imzhitu.admin.common.pojo.ZTWorldSubtitle; public interface SubtitleMapper { @DataSource("slave")
public List<HTWorldSubtitleDto> queryCacheSubtitle(@Param("transTo")String transTo,
@Param("limit")Integer limit); @DataSource("slave")
public List<ZTWorldSubtitle> queryTitles(ZTWorldSubtitle title); @DataSource("slave")
public long queryTotal(ZTWorldSubtitle title); @DataSource("master")
public void saveSubtitle(ZTWorldSubtitle title); @DataSource("master")
public void update(ZTWorldSubtitle title); @DataSource("master")
public void deleteByIds(Integer[] ids); @DataSource("master")
public void updateSerialById(@Param("id")Integer id,
@Param("serial")Integer serial); }
数据流转顺序:
1.xml<aop>拦截到数据源名称
2.执行切面DataSourceAspect中的before方法,将数据源名称放入 DynamicDataSourceHolder中
3.Spring JDBC调用determineCurrentLookupKey()方法<DynamicDataSource中重写AbstractRoutingDataSource类中的方法> ,从DynamicDataSourceHolder取出当前的数据库名称,并返回
4.AbstractRoutingDataSource类中determineTargetDataSource()方法调用determineCurrentLookupKey()匹配到指定的数据库,并建立链接,即为切换到相应的数据库;
5.在指定的数据库中执行相应的sql
总结:
1.注释自定义
2.Spring JDBC中 AbstractRoutingDataSource
3.xml中数据源配置,aop配置
以上三者为主从数据库实现的核心技术
Spring JDBC主从数据库访问配置的更多相关文章
- Spring JDBC主从数据库配置
通过昨天学习的自定义配置注释的知识,探索了解一下web主从数据库的配置: 背景:主从数据库:主要是数据上的读写分离: 数据库的读写分离的好处? 1. 将读操作和写操作分离到不同的数据库上,避免主服务器 ...
- Spring主从数据库的配置和动态数据源切换原理
原文:https://www.liaoxuefeng.com/article/00151054582348974482c20f7d8431ead5bc32b30354705000 在大型应用程序中,配 ...
- Spring Mvc和Mybatis的多数据库访问配置过程
Spring Mvc 加Mybatis的多数据库访问源配置访问过程如下: 在applicationContext.xml进行配置 <?xml version="1.0" en ...
- Spring入门(十五):使用Spring JDBC操作数据库
在本系列的之前博客中,我们从没有讲解过操作数据库的方法,但是在实际的工作中,几乎所有的系统都离不开数据的持久化,所以掌握操作数据库的使用方法就非常重要. 在Spring中,操作数据库有很多种方法,我们 ...
- sql2005主从数据库同步配置
网站规模到了一定程度之后,该分的也分了,该优化的也做了优化,但是还是不能满足业务上对性能的要求:这时候我们可以考虑使用主从库.主从库是两台服务器上的两个数据库,主库以最快的速度做增删改操作+最新数据的 ...
- sql server2005主从数据库同步配置
网站规模到了一定程度之后,该分的也分了,该优化的也做了优化,但是还是不能满足业务上对性能的要求:这时候我们可以考虑使用主从库.主从库是两台服务器上的两个数据库,主库以最快的速度做增删改操作+最新数据的 ...
- 19.JDBC和数据库访问.md
1.基本功能: Java通过JDBC完成: 2.基本类型,通常用最后一种 3.JDBC简介 Java连接SQL例子: 参考:http://blog.chinaunix.net/uid-20726500 ...
- Spring JDBC操作数据库示例
1.所需jar包 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncodi ...
- SpringBoot 优雅配置跨域多种方式及Spring Security跨域访问配置的坑
前言 最近在做项目的时候,基于前后端分离的权限管理系统,后台使用 Spring Security 作为权限控制管理, 然后在前端接口访问时候涉及到跨域,但我怎么配置跨域也没有生效,这里有一个坑,在使用 ...
随机推荐
- Python学习笔记(十八)@property
# 请利用@property给一个Screen对象加上width和height属性, # 以及一个只读属性resolution: # -*- coding: utf-8 -*- class Scree ...
- C++类四个默认函数&深复制&浅复制
学习C++语言的同学都知道,C++中类是有默认的几个函数的,主要是有四个函数: 四个函数 默认构造函数:A(void),无参构造函数 拷贝(复制)构造函数:A(const A&a).用一个对象 ...
- windows下gitlab配置 生成ssh key
Git-1.9.5-preview20141217 1. 安装git,从程序目录打开 "Git Bash" 2. 键入命令:ssh-keygen -t rsa -C "e ...
- maven使用过程中遇到的问题总汇
1:web.xml is missing and <failOnMissingWebXml> is set to true 造成原因: 使用maven创建项目时有时在pom.xml的war ...
- MQTT协议及推送服务(二)
MQTT简介 MQTT全称叫做Message Queuing Telemetry Transport,意为消息队列遥测传输,是IBM开发的一个即时通讯协议.由于其维护一个长连接以轻量级低消耗著称,所以 ...
- Oracle笔记之约束
约束用于保证数据库中某些数据的完整性,给某一列添加一个约束可以保证不满足约束的数据是绝对不会被接受的. 约束主要有那么五种类型:非空约束.唯一约束.主键约束.外键约束.校验约束. 使用如下命令检索某个 ...
- CF148A Insomnia cure
公主睡前数龙, 每隔k, l, m, n只都会用不同的技能攻击龙. 假定共数了d只龙, 问共有多少龙被攻击了. 思路: 用一个visit数组记录被攻击过的dragon, 最后遍历visit数组统计被攻 ...
- Android SDK的安装与环境变量的配置
配置Andriod环境变量前提是要先安装好JAVA环境 1.下载Android SDK,点击安装,放在任意不含空格.特殊符号和中文的路径即可. 2.默认路径安装后,安装完成,开始配置环境变量. 3.打 ...
- 自己看之区间DP
//菜鸡制作,看的时候可能三目运算符略烦;;; 区间DP入门题:Brackets 地址:http://59.77.139.92/Problem.jsp?pid=1463 分析(对区间DP的代码原理进行 ...
- 利用__attribute__((section()))构建初始化函数表【转】
转自: https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652663356&idx=1&sn=7797629530 ...