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配置

以上三者为主从数据库实现的核心技术

时间: 2024-10-05 23:48:20

Spring JDBC主从数据库配置的相关文章

Spring+MyBatis双数据库配置

Spring+MyBatis双数据库配置 近期项目中遇到要调用其它数据库的情况.本来仅仅使用一个MySQL数据库.但随着项目内容越来越多,逻辑越来越复杂. 原来一个数据库已经不够用了,须要分库分表.所以决定扩充数据库,正好Spring能够灵活的扩充数据库.以下简单写一篇博文,记录下多数据库配置的过程. 1.项目结构例如以下图: 当中mkhl和ulab分别相应两个数据库模块.同一时候也相应两个不同的功能模块. 2.整个Maven项目的配置文件:pom.xml <project xmlns="

mysql主从数据库配置

在这里吧昨天做的主从数据库配置记录下来,免得以后折腾 数据库主从配置心得: master : 192.168.16.247 slave1 : 192.168.16.248 1 修改配置文件 /etc/mysql/my.cnf(如果my.cnf已有该配置项,则相应的进行修改) 主数据库: server-id = 1 log-bin = mysql-bin log-bin-index = mysql-bin.index #log_bin = /var/log/mysql/mysql-bin.log

Spring JDBC 访问数据库

Spring JDBC是Spring所提供的持久层技术,它以一种更直接.更简单的方式使用JDBC API.在Spring JDBC里,用户仅需要做那些必不可杀的事儿,而将资源获取.Statement创建.异常处理.资源释放等繁杂的工作交给Spring. 虽然ORM的框架已经很成熟,但是JDBC灵活直接的特性依旧让它有自己的用武之地. 本节的主要内容:使用JdbcTemplate模板类进行CRUD数据操作.BLOB和CLOB类型数据的操作,支持命名参数绑定NamedParameterJdbcTem

Spring+Hibernate框架下Mysql读写分离、主从数据库配置

1. 配置AOP切面类 DataSourceAdvice.java package until; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.ThrowsAdvice; public class DataSo

spring+mybatis 多数据库配置

这几天需要在ssm项目中配置多一个数据库,就去网上找多数据源的例子,发现都没有成功就自己试了下自己写的 原理: 创建两个数据源,两个seesionFactory,两个事务管理器,然后两个事务管理器分别配置aop,扫描不同的package(网上看到的都是使用注解方式,这里使用aop方式) 1.环境: Mybatis + springmvc +spring + druid + log4j 如果没使用druid  的话将将数据源的bean 修改回org.apache.commons.dbcp.Basi

Spring访问oracle数据库配置步骤

1.spring 对数据库访问的支持 当我们开发持久层的时候,我们面临着多种选择,比如使用JDBC.Hibernate.java持久化API或其它持久化框架.幸好的是spring能够支持所有这些持久化机制. DAO(data access boject)数据访问对象,这个名字就很形象描述了DAO在应用程序中所扮演的角色.DAO提供了数据的读取.写入到数据库中的一种方式.它们应该以接口的方式发布功能,而应用程序的其它部分就可以通过接口来进行访问了. 2.配置数据源 spring提供了在spring

Sql Server 主从数据库配置

网站规模到了一定程度之后,该分的也分了,该优化的也做了优化,但是还是不能满足业务上对性能的要求:这时候我们可以考虑使用主从库.主从库是两台服务器上的两个数据库,主库以最快的速度做增删改操作+最新数据的查询操作;从库负责查询较旧数据,做一些对实效性要求较小的分析,报表生成的工作.这样做将数据库的压力分担到两台服务器上从而保证整个系统响应的及时性.如果还无法满足业务需求,我们就要考虑创建服务器群,这里我们不做考虑! 1. 打开sql server企业管理器,在对象资源管理器里面选择复制à本地发布,右

spring-spring mvc-mybatis 实现主从数据库配置

一.配置文件 1.spring-mybatis.xml master_driverUrl=jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true master_username=root master_password=12345 slave_driverUrl=j

[转]Sql Server 主从数据库配置

本文转自:http://www.cnblogs.com/yukaizhao/archive/2010/06/02/sql-server-master-slave-mode.html 网站规模到了一定程度之后,该分的也分了,该优化的也做了优化,但是还是不能满足业务上对性能的要求:这时候我们可以考虑使用主从库.主从库是两台服务器上的两个数据库,主库以最快的速度做增删改操作+最新数据的查询操作;从库负责查询较旧数据,做一些对实效性要求较小的分析,报表生成的工作.这样做将数据库的压力分担到两台服务器上从