springboot主从数据库

是从springmvc的思路上来做的,主要就是配置主、从DataSource,再继承AbstractRoutingDataSource,重写determineCurrentLookupKey方法,通过Context结合 aop 进行数据主、从库的切换。

上代码:

路由,即实现多数据库的切换源

/*
*    重写的函数决定了最后选择的DataSource
*    因为AbstractRoutingDataSource中获取连接方法为:
@Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }
*/

public class MultiRouteDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }

}

注解,即用以标识选择主还是从数据库

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

常规配置项,具体主从继承并通过

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #777777 }
span.s1 { color: #000000 }
span.s2 { color: #3933ff }

@ConfigurationProperties(prefix = "master.datasource") 进行配置读取

public class BaseDataSourceConfig {

    private String url;
    private String username;
    private String password;
    private String driverClassName;
    // 添加上getter、setter方法
}

多数据源设置

@Configuration
public class DataSourceComponent {

    @Resource
    MasterDataSourceConfig masterDataSourceConfig;

    @Resource
    FirstDataSourceConfig firstDataSourceConfig;

    @Resource
    SecondDataSourceConfig secondDataSourceConfig;

    /*
     * 一开始以为springboot的自动配置还是会生效,直接加了@Resource DataSource dataSource;
     * 显示是不work的,会报create bean 错误
     */
    public DataSource masterDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUrl(masterDataSourceConfig.getUrl());
        dataSource.setUsername(masterDataSourceConfig.getUsername());
        dataSource.setPassword(masterDataSourceConfig.getPassword());
        dataSource.setDriverClassName(masterDataSourceConfig.getDriverClassName());
        return dataSource;
    }

    /*
     * 一开始在这里加了@Bean的注解,当然secondDataSource()也加了
     * 会导致springboot识别的时候,发现有多个
     * 所以,其实都不要加@Bean,最终有效的的DataSource就只需要一个multiDataSource即可
     */
    public DataSource firstDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUrl(firstDataSourceConfig.getUrl());
        dataSource.setUsername(firstDataSourceConfig.getUsername());
        dataSource.setPassword(firstDataSourceConfig.getPassword());
        dataSource.setDriverClassName(firstDataSourceConfig.getDriverClassName());
        return dataSource;
    }

    public DataSource secondDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUrl(secondDataSourceConfig.getUrl());
        dataSource.setUsername(secondDataSourceConfig.getUsername());
        dataSource.setPassword(secondDataSourceConfig.getPassword());
        dataSource.setDriverClassName(secondDataSourceConfig.getDriverClassName());
        return dataSource;
    }

    @Bean(name = "multiDataSource")
    public MultiRouteDataSource exampleRouteDataSource() {
        MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        targetDataSources.put("first", firstDataSource());
        targetDataSources.put("second", secondDataSource());
        multiDataSource.setTargetDataSources(targetDataSources);
        multiDataSource.setDefaultTargetDataSource(masterDataSource());
        return multiDataSource;
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager dataSourceTransactionManager() {
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(exampleRouteDataSource());
        return manager;
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactoryBean sqlSessionFactory() {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(exampleRouteDataSource());
        return sessionFactoryBean;
    }
}

当然少不了DataSourceContextHolder,用以保持当前线程的数据源选择。

public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String value) {
        contextHolder.set(value);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}

最后,自然就是AOP+注解实现数据源切换啦

@Aspect
@Component
public class DynamicDataSourceAspect {

    @Around("execution(public * com.wdm.example.service..*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method targetMethod = methodSignature.getMethod();
        if(targetMethod.isAnnotationPresent(TargetDataSource.class)){
            String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).value() ;
            DataSourceContextHolder.setDataSource(targetDataSource);
        }
        Object result = pjp.proceed();
        DataSourceContextHolder.clearDataSource();
        return result;
    }
}

那用法就是如下了:

package com.wdm.example.service;

import java.util.Date;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.wdm.example.dao.UserDao;
import com.wdm.example.datasource.TargetDataSource;
import com.wdm.example.model.User;
import com.wdm.example.service.UserService;

/*
 * @author wdmyong
 * 20170416
 */
@Service
public class UserService {

    @Resource
    UserDao userDao;

    public User getById(Integer id) {
        return userDao.getById(id);
    }

    @TargetDataSource("master")
    public User getById0(Integer id) {
        return userDao.getById(id);
    }

    @TargetDataSource("first")
    public User getById1(Integer id) {
        return userDao.getById(id);
    }

    @TargetDataSource("second")
    public User getById2(Integer id) {
        return userDao.getById(id);
    }

    public void insert(User user) {
        Date now = new Date();
        user.setCreateTime(now);
        user.setModifyTime(now);
        userDao.insert(user);
    }

    public void update(User user) {
        user.setModifyTime(new Date());
        userDao.update(user);
    }

}

自己在网上找的时候不是全的,包括上文注释中提到的出现的问题,也是根据错误提示多个DataSource目标,以及没设置就是没有DataSource了。

PS:其实之前一直以为DataSource听起来挺悬乎,没去细想,当然主要由于自己是半路出家的Java、web开发,本身也没那么熟悉,所以没理解哈,

现在想想DataSource其实就是保存了些配置,说白了是url和账号密码,就是连接数据库的,相当于你用命令行连接了数据库进行了操作一样,各种

数据库DataSource的实现高功能多半应该是做了些连接池的管理,以及连接的打开关闭之类,其实实质上我觉得应该就是说最后用的就是那个url加

上账号密码就能连接并操作了。这样的话,多数据源的切换就好理解了,结合 aop 在函数入口之前设置好当前线程数据源,以及根据路由数据库类

AbstractRoutingDataSource将选择数据源留给子类实现的方法

determineCurrentLookupKey,从而在service方法入口设置数据源,在使用时取到数据源。

大PS:这应该算是我写的最全的一次Java的博客了!!!

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco }

时间: 2024-10-19 04:37:45

springboot主从数据库的相关文章

Spring主从数据库的配置和动态数据源切换原理

原文:https://www.liaoxuefeng.com/article/00151054582348974482c20f7d8431ead5bc32b30354705000 在大型应用程序中,配置主从数据库并使用读写分离是常见的设计模式.在Spring应用程序中,要实现读写分离,最好不要对现有代码进行改动,而是在底层透明地支持. Spring内置了一个AbstractRoutingDataSource,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的数据源.因为Abst

mysql配置主从数据库

1.目的 1.1 实现数据备份 1.2 项目访问时可以实现读写分离,提高访问和操作数据的速度<读写分离好处> 2.背景 这次的主从配置主要实现主库数据的改变可以实现同步到从库中: 此次试验的主库为liunx<ubuntu4.8.2> 下mysql 5.6.19,从库为windows10 下mysql 5.6.24:  3.准备知识 3.1 在liunx下mysql的配置文件为: /etc/mysql/my.conf 3.2 在windows下mysql的配置文件为: mysqlpa

MySQL主从数据库同步延迟问题解决(转)

最近在做MySQL主从数据库同步测试,发现了一些问题,其中主从同步延迟问题是其中之一,下面内容是从网上找到的一些讲解,记录下来以便自己学习: MySQL的主从同步是一个很成熟的架构,优点为:①在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力;②在从主服务器进行备份,避免备份期间影响主服务器服务;③当主服务器出现问题时,可以切换到从服务器. MySQL主从同步故障-Slave_SQL_Running: No http://www.linuxidc.com/Linux/2014-0

mysql主从数据库不同步的2种解决方法(转)

今天发现Mysql的主从数据库没有同步 先上Master库: mysql>show processlist; 查看下进程是否Sleep太多.发现很正常. show master status; 也正常. mysql> show master status; +-------------------+----------+--------------+-------------------------------+ | File | Position | Binlog_Do_DB | Binlo

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

SQL Server 2008 R2 主从数据库同步

一.准备工作: 主数据库服务器: OS:Windows Server 2008 R2    DB: SQL Server 2008 R2 Hostname : CXMasterDB  IP: 192.168.1.224/24    dg: 192.168.1.1 DNS: 192.168.1.19    DNS: 202.96.209.133 从数据库服务器: OS:Windows Server 2008 R2    DB: SQL Server 2008 R2 Hostname : CXSla

问题:主从数据库getConnection死循环

Mysql主从数据库,使用c3p0数据源,测试使用主从都是同一个数据库,出错,getConnection死循环. 这是c3p0的BUG,多亏了这篇文章,不然又得浪费好多时间 http://www.2cto.com/database/201411/349966.html 我将数据源换成dbcp立马就好了,至于这篇文章的util工具类没有试验.

SQL Server 2008 R2 主从数据库同步(日志传送的方式 Log Shipping)

注意事项: 1.为主从服务器添加新的系统用户并设置好密码: 2.主从服务器都开启SQL Server的代理服务,并设置为开机自动启动 3.在数据库配置管理其中把SQL Server服务和SQL Server的代理服务的登录信息设置为上边添加的系统用户,并设置好密码.(记得主从服务器都需要这样设置,不要忘记了,我都是忘记了,怎么弄都不行) 4.用户共享的文件目录,共享访问时需要密码,记得要先访问共享并记住凭证,不然会提示失败. 5.SQL Server的备份,是主库的数据库服务器自动备份数据库,生

解决主从数据库同步延迟问题

场景:需要在主机写入之后,保证在备机一定能够读取到已经写入的数据,也就是需要主从架构下的强一致性. 主机与备机之间的物理延迟是不可控的,也是无法避免的.但是如果仅仅需要满足这种强一致性,是相对简单的事情:只需要在主机写入时,确认更新已经同步到备机之后,再返回写操作成功即可.主从数据库支持这种完全的同步模式. 不过一般不建议使用这种同步模式.显而易见,如果写操作必须等待更新同步完成,肯定会极大影响性能. 问题的关键在于,主从架构是一种用于数据容错的高可用性解决方案,而不是一种处理高并发压力的解决方