`

43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

阅读更多

 à悟空学院:https://t.cn/Rg3fKJD

学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!

SpringBoot视频:http://t.cn/A6ZagYTi

Spring Cloud视频:http://t.cn/A6ZagxSR

SpringBoot Shiro视频:http://t.cn/A6Zag7IV

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringData和JPA视频:http://t.cn/A6Zad1OH

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

Sharding-JDBC分库分表实战http://t.cn/A6ZarrqS

分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr

 

 

在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库。那么这一节我们将要介绍怎么进行多数据源动态切换。添加本文实现的代码之后,只需要配置要数据源就可以直接通过注解使用,在实际使用的时候特别的简单。那么本章主要分以下几个步骤进行实战。

本章大纲 写道
(1)新建maven java project;
(2)在pom.xml添加依赖包;
(3)编写启动类App.java
(4)编写配置文件application.properties;
(5)动态数据源路由类;
(6)注册多数据源;
(7)测试类测试;

 

 

       接下来我们看看每一步具体的实现吧:

 

1)新建maven java project;

       新建一个maven project,取名为spring-boot-multi-ds

2)在pom.xml添加依赖包;

       pom.xml文件中加入依赖的库文件,主要是spring boot基本的,数据库驱动,spring-jpa支持即可,具体pom.xml文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

 

  <groupId>com.kfit</groupId>

  <artifactId>spring-boot-multids</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>jar</packaging>

 

  <name>spring-boot-multids</name>

  <url>http://maven.apache.org</url>

 

  <properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <!-- jdk版本号,这里需要你本地进行的jdk进行修改,这里angel使用的是1.8的版本. -->

    <java.version>1.8</java.version>

  </properties>

 

 

   <!--

       spring boot 父节点依赖,

       引入这个之后相关的引入就不需要添加version配置,

       spring boot会自动选择最合适的版本进行添加。

       在这里使用的1.3.3版本,可能目前官方有最新的版本了,大家可以

       使用最新的版本。

     -->

    <parent>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-parent</artifactId>

       <version>1.3.3.RELEASE</version>

    </parent>

 

  <dependencies>

    <!-- 单元测试包,在这里没有使用到. -->

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <scope>test</scope>

    </dependency>

   

    <!-- spring boot web支持:mvc,aop...

        这个是最基本的,基本每一个基本的demo都是需要引入的。

    -->

    <dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

   

    <!-- mysql驱动.

       我们的demo是多数据源,在这里使用Mysql数据库.

    -->

    <dependency>

       <groupId>mysql</groupId>

       <artifactId>mysql-connector-java</artifactId>

    </dependency>

   

   

    <!-- spring jpa

       spring jpa中带有自带的tomcat数据连接池;

       在代码中我们也需要用到.

     -->

    <dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-data-jpa</artifactId>

    </dependency>

   

  </dependencies>

</project>

       在上面的配置文件中都有相应的解释,大家可以自己解读下。

 

 

3)编写启动类App.java

       编写spring boot的启动类:

com.kfit.App:

package com.kfit;

 

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

 

/**

 *

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

@SpringBootApplication

public class App {

    public static void main(String[] args) {

       SpringApplication.run(App.class, args);

    }

}

 

 

4)编写配置文件application.properties;

       在这里主要是多数据源和jpa的配置:

src/main/resources/application.properties:

########################################################

###配置文件包括1个主数据源和多个数据源,

###其中主数据源在Spring中的beanName默认为dataSource

###另外几个数据源的beanName分包为:ds1ds2ds3

###其中datasourcetype属性可以具体指定到我们需要的数据源上面,

###不指定情况下默认为:org.apache.tomcat.jdbc.pool.DataSource

###当然你也可以把这些数据源配置到主dataSource数据库中,然后读取数据库生成多数据源。当然这样做的必要性并不大,难不成数据源还会经常变吗。

########################################################

 

# 主数据源,默认的

#spring.datasource.type=com.zaxxer.hikari.HikariDataSource

spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://localhost:3306/test

spring.datasource.username=root

spring.datasource.password=root

 

 

# 更多数据源

custom.datasource.names=ds1,ds2,ds3

#custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource

custom.datasource.ds1.driverClassName =com.mysql.jdbc.Driver

custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1

custom.datasource.ds1.username=root

custom.datasource.ds1.password=root

 

#custom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource

custom.datasource.ds2.driverClassName =com.mysql.jdbc.Driver

custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test

custom.datasource.ds2.username=root

custom.datasource.ds2.password=root

 

#custom.datasource.ds3.type=com.zaxxer.hikari.HikariDataSource

custom.datasource.ds3.driverClassName =com.mysql.jdbc.Driver

custom.datasource.ds3.url=jdbc:mysql://localhost:3306/test

custom.datasource.ds3.username=root

custom.datasource.ds3.password=root

 

 

# 下面为连接池的补充设置,应用到上面所有数据源中

spring.datasource.maximum-pool-size=100

spring.datasource.max-idle=10

spring.datasource.max-wait=10000

spring.datasource.min-idle=5

spring.datasource.initial-size=5

spring.datasource.validation-query=SELECT 1

spring.datasource.test-on-borrow=false

spring.datasource.test-while-idle=true

spring.datasource.time-between-eviction-runs-millis=18800

 

 

 

########################################################

### Java Persistence Api

########################################################

# Specify the DBMS

spring.jpa.database = MYSQL

# Show or not log for each sql query

spring.jpa.show-sql = true

# Hibernate ddl auto (create, create-drop, update)

spring.jpa.hibernate.ddl-auto = update

# Naming strategy

#[org.hibernate.cfg.ImprovedNamingStrategy  #org.hibernate.cfg.DefaultNamingStrategy]

spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy

# stripped before adding them to the entity manager)

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

 

 

 

5)动态数据源路由类;

       动态数据源能进行自动切换的核心就是spring底层提供了AbstractRoutingDataSource类进行数据源的路由的,我们主要继承这个类,实现里面的方法即可实现我们想要的,这里主要是实现方法:determineCurrentLookupKey(),而此方法只需要返回一个数据库的名称即可,所以我们核心的是有一个类来管理数据源的线程池,这个类才是动态数据源的核心处理类。还有另外就是我们使用aop技术在执行事务方法前进行数据源的切换。所以这里有几个需要编码的类,具体如下:

 

动态数据源上下文>com.kfit.config.datasource.DynamicDataSourceContextHolder

package com.kfit.config.datasource.dynamic;

 

import java.util.ArrayList;

import java.util.List;

 

/**

 * 动态数据源上下文.

 *

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

public class DynamicDataSourceContextHolder {

   

    /*

     * 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,

     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

     */

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

    /*

     * 管理所有的数据源id;

     * 主要是为了判断数据源是否存在;

     */

    public static List<String> dataSourceIds = new ArrayList<String>();

 

    /**

     * 使用setDataSourceType设置当前的

     * @param dataSourceType

     */

    public static void setDataSourceType(String dataSourceType) {

       contextHolder.set(dataSourceType);

    }

 

    public static String getDataSourceType() {

       return contextHolder.get();

    }

 

    public static void clearDataSourceType() {

       contextHolder.remove();

    }

   

    /**

     * 判断指定DataSrouce当前是否存在

     *

     * @param dataSourceId

     * @return

     * @author SHANHY

     * @create  2016124

     */

    public static boolean containsDataSource(String dataSourceId){

        return dataSourceIds.contains(dataSourceId);

    }

}

 

数据源路由类>com.kfit.config.datasource.dynamic.DynamicDataSource

package com.kfit.config.datasource.dynamic;

 

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

 

/**

 * 动态数据源.

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

public class DynamicDataSource extends AbstractRoutingDataSource {

   

    /*

     * 代码中的determineCurrentLookupKey方法取得一个字符串,

     * 该字符串将与配置文件中的相应字符串进行匹配以定位数据源,配置文件,即applicationContext.xml文件中需要要如下代码:(non-Javadoc)

     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()

     */

    @Override

    protected Object determineCurrentLookupKey() {

       /*

        * DynamicDataSourceContextHolder代码中使用setDataSourceType

        * 设置当前的数据源,在路由类中使用getDataSourceType进行获取,

        *  交给AbstractRoutingDataSource进行注入使用。

        */

       return DynamicDataSourceContextHolder.getDataSourceType();

    }

}

 

指定数据源注解类>com.kfit.config.datasource.dynamic.TargetDataSource

package com.kfit.config.datasource.dynamic;

 

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

 

/**

 * 在方法上使用,用于指定使用哪个数据源

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

@Target({ ElementType.METHOD, ElementType.TYPE })

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface TargetDataSource {

    String value();

}

 

切换数据源Advice>com.kfit.config.datasource.dynamic.DynamicDataSourceAspect

package com.kfit.config.datasource.dynamic;

 

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

 

/**

 * 切换数据源Advice

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

@Aspect

@Order(-10)//保证该AOP@Transactional之前执行

@Component

public class DynamicDataSourceAspect {

   

    /*

     * @Before("@annotation(ds)")

     * 的意思是:

     *

     * @Before:在方法执行之前进行执行:

     * @annotation(targetDataSource)

     * 会拦截注解targetDataSource的方法,否则不拦截;

     */

    @Before("@annotation(targetDataSource)")

    public void changeDataSource(JoinPoint point, TargetDataSourcetargetDataSource) throws Throwable {

       //获取当前的指定的数据源;

        String dsId = targetDataSource.value();

        //如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。

        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {

            System.err.println("数据源[{}]不存在,使用默认数据源 > {}"+targetDataSource.value()+point.getSignature());

        } else {

            System.out.println("Use DataSource : {} > {}"+targetDataSource.value()+point.getSignature());

            //找到的话,那么设置到动态数据源上下文中。

            DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());

        }

    }

   

    @After("@annotation(targetDataSource)")

    public void restoreDataSource(JoinPoint point, TargetDataSourcetargetDataSource) {

       System.out.println("Revert DataSource : {} > {}"+targetDataSource.value()+point.getSignature());

       //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。

        DynamicDataSourceContextHolder.clearDataSourceType();

    }

   

}

 

 

6)注册多数据源;

       以上都是动态数据源在注入的时候使用的代码,其实很重要的一部分代码就是注册我们在application.properties配置的多数据源,这才是重点,这里我们使用

ImportBeanDefinitionRegistrar进行注册,具体的代码如下:

package com.kfit.config.datasource.dynamic;

 

import java.util.HashMap;

import java.util.Map;

 

import javax.sql.DataSource;

 

import org.springframework.beans.MutablePropertyValues;

import org.springframework.beans.PropertyValues;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.beans.factory.support.GenericBeanDefinition;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;

import org.springframework.boot.bind.RelaxedDataBinder;

import org.springframework.boot.bind.RelaxedPropertyResolver;

import org.springframework.context.EnvironmentAware;

import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.convert.ConversionService;

import org.springframework.core.convert.support.DefaultConversionService;

import org.springframework.core.env.Environment;

import org.springframework.core.type.AnnotationMetadata;

 

/**

 * 动态数据源注册;

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

public class DynamicDataSourceRegister  implements ImportBeanDefinitionRegistrar, EnvironmentAware {

   

    //如配置文件中未指定数据源类型,使用该默认值

    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";

    private ConversionService conversionService = new DefaultConversionService();

    private PropertyValues dataSourcePropertyValues;

   

    // 默认数据源

    private DataSource defaultDataSource;

   

    private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();

   

    /**

     * 加载多数据源配置

     */

    @Override

    public void setEnvironment(Environment environment) {

       System.out.println("DynamicDataSourceRegister.setEnvironment()");

       initDefaultDataSource(environment);

        initCustomDataSources(environment);

    }

   

    /**

     * 加载主数据源配置.

     * @param env

     */

    private void initDefaultDataSource(Environment env){

       // 读取主数据源

       RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");

       Map<String, Object> dsMap = new HashMap<String, Object>();

       dsMap.put("type", propertyResolver.getProperty("type"));

        dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));

        dsMap.put("url", propertyResolver.getProperty("url"));

        dsMap.put("username", propertyResolver.getProperty("username"));

        dsMap.put("password", propertyResolver.getProperty("password"));

       

        //创建数据源;

        defaultDataSource = buildDataSource(dsMap);

        dataBinder(defaultDataSource, env);

    }

   

    /**

     * 初始化更多数据源

     *

     * @author SHANHY

     * @create 2016124

     */

    private void initCustomDataSources(Environment env) {

        // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源

        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");

        String dsPrefixs = propertyResolver.getProperty("names");

        for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源

            Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");

            DataSource ds = buildDataSource(dsMap);

            customDataSources.put(dsPrefix, ds);

            dataBinder(ds, env);

        }

    }

   

    /**

     * 创建datasource.

     * @param dsMap

     * @return

     */

    @SuppressWarnings("unchecked")

    public DataSource buildDataSource(Map<String, Object> dsMap) {

       Object type = dsMap.get("type");

        if (type == null){

            type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource

        }

        Class<? extends DataSource> dataSourceType;

       

       try {

           dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);

           String driverClassName = dsMap.get("driverClassName").toString();

            String url = dsMap.get("url").toString();

            String username = dsMap.get("username").toString();

            String password = dsMap.get("password").toString();

            DataSourceBuilder factory =   DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);

            returnfactory.build();

       } catch (ClassNotFoundException e) {

           e.printStackTrace();

       }

       returnnull;

    }

   

    /**

     * DataSource绑定更多数据

     * @param dataSource

     * @param env

     */

    private void dataBinder(DataSource dataSource, Environment env){

       RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);

       dataBinder.setConversionService(conversionService);

       dataBinder.setIgnoreNestedProperties(false);//false

        dataBinder.setIgnoreInvalidFields(false);//false

        dataBinder.setIgnoreUnknownFields(true);//true

       

        if(dataSourcePropertyValues == null){

            Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");

            Map<String, Object> values = new HashMap<>(rpr);

            // 排除已经设置的属性

            values.remove("type");

            values.remove("driverClassName");

            values.remove("url");

            values.remove("username");

            values.remove("password");

            dataSourcePropertyValues = new MutablePropertyValues(values);

        }

        dataBinder.bind(dataSourcePropertyValues);

       

    }

   

 

    @Override

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

       System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");

       Map<Object, Object> targetDataSources = new HashMap<Object, Object>();

       // 将主数据源添加到更多数据源中

        targetDataSources.put("dataSource", defaultDataSource);

        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");

        // 添加更多数据源

        targetDataSources.putAll(customDataSources);

        for (String key : customDataSources.keySet()) {

            DynamicDataSourceContextHolder.dataSourceIds.add(key);

        }

       

        // 创建DynamicDataSource

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

        beanDefinition.setBeanClass(DynamicDataSource.class);

       

        beanDefinition.setSynthetic(true);

        MutablePropertyValues mpv = beanDefinition.getPropertyValues();

        //添加属性:AbstractRoutingDataSource.defaultTargetDataSource

        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);

        mpv.addPropertyValue("targetDataSources", targetDataSources);

        registry.registerBeanDefinition("dataSource", beanDefinition);

    }

 

}

这里还有一个步骤很重要,由于我们是使用的ImportBeanDefinitionRegistrar的方式进行注册的,所以我们需要在App.java类中使用@Import进行注册,具体改造之后的App.java代码如下:

package com.kfit;

 

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.Import;

import com.kfit.config.datasource.dynamic.DynamicDataSourceRegister;

 

/**

 *

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

@SpringBootApplication

//注册动态多数据源

@Import({DynamicDataSourceRegister.class})

public class App {

    public static void main(String[] args) {

       SpringApplication.run(App.class, args);

    }

}

 

 

7)测试类测试;

       核心的代码都编写完毕了,当然我们肯定是需要编写代码进行测试下,我们才放心了。

com.kfit.demo.bean.Demo

package com.kfit.demo.bean;

 

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

 

/**

 * 测试demo.

 * @author Angel(QQ:412887952)

 * @version v.0.1

 */

@Entity

public class Demo {

    @Id @GeneratedValue

    private longid;

   

    private String name;

 

    public long getId() {

       return id;

    }

 

    public void setId(longid) {

       this.id = id;

    }

 

    public String getName() {

       return name;

    }

 

    publicvoid setName(String name) {

       this.name = name;

    }

 

    @Override

    public String toString() {

       return"Demo [id=" + id + ", name=" + name + "]";

    }

}

 

com.kfit.demo.dao.TestDao

package com.kfit.demo.dao;

 

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.List;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.core.RowMapper;

import org.springframework.stereotype.Service;

 

import com.kfit.demo.bean.Demo;

 

@Service

public class TestDao {

   

    @Autowired

    private JdbcTemplate jdbcTemplate;

   

    /**

     * 不指定数据源使用默认数据源

     * @return

     */

    public List<Demo> getList(){

       String sql = "select *from Demo";

        return (List<Demo>) jdbcTemplate.query(sql, new RowMapper<Demo>(){

            @Override

            public Demo mapRow(ResultSet rs, introwNum) throws SQLException {

                Demo demo = new Demo();

                demo.setId(rs.getLong("id"));

                demo.setName(rs.getString("name"));;

                returndemo;

            }

        });

    }

   

     /**

     * 指定数据源

     * 在对应的service进行指定;

     * @return

     * @author SHANHY

     * @create  2016124

     */

    public List<Demo> getListByDs1(){

        /*

         * 这张表示复制的主库到ds1的,在ds中并没有此表.

         * 需要自己自己进行复制,不然会报错:Table 'test1.demo1' doesn't exist

         */

       String sql = "select *from Demo1";

        return (List<Demo>) jdbcTemplate.query(sql, new RowMapper<Demo>(){

 

            @Override

            public Demo mapRow(ResultSet rs, introwNum) throws SQLException {

                Demo demo = new Demo();

                demo.setId(rs.getLong("id"));

                demo.setName(rs.getString("name"));;

                returndemo;

            }

 

        });

    }

}

 

com.kfit.demo.service.TestService :

package com.kfit.demo.service;

 

import java.util.List;

 

import javax.annotation.Resource;

 

import org.springframework.stereotype.Service;

 

import com.kfit.config.datasource.dynamic.TargetDataSource;

import com.kfit.demo.bean.Demo;

import com.kfit.demo.dao.TestDao;

 

@Service

public class TestService {

   

    @Resource

    private TestDao testDao;

   

    /**

     * 不指定数据源使用默认数据源

     * @return

     */

    public List<Demo> getList(){

       returntestDao.getList();

    }

   

    /**

     * 指定数据源

     * @return

     */

    @TargetDataSource("ds1")

    public List<Demo> getListByDs1(){

        returntestDao.getListByDs1();

    }

}

 

com.kfit.demo.controller.TestController:

package com.kfit.demo.controller;

 

import javax.annotation.Resource;

 

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

 

import com.kfit.demo.bean.Demo;

import com.kfit.demo.service.TestService;

 

@RestController

public class TestController {

   

    @Resource

    private TestService testService;

   

    @RequestMapping("/test1")

    public String test(){

//     for(Demo d:testService.getList()){

//         System.out.println(d);

//     }

       for(Demo d:testService.getListByDs1()){

           System.out.println(d);

       }

       return"ok";

    }

   

}

 

好了,测试代码就这么多了,运行App.java进行测试把,访问:

http://127.0.0.1:8080/test1 查看控制台的打印。

 

这里需要提醒下,这种方式spring-jpa的方式好像不能自动路由,博主打算在之后的一篇文章介绍spring-jpa多数据源的问题。

  Spring Boot 系列博客】

à悟空学院:https://t.cn/Rg3fKJD

学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!

SpringBoot视频:http://t.cn/A6ZagYTi

Spring Cloud视频:http://t.cn/A6ZagxSR

SpringBoot Shiro视频:http://t.cn/A6Zag7IV

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringData和JPA视频:http://t.cn/A6Zad1OH

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

Sharding-JDBC分库分表实战http://t.cn/A6ZarrqS

分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr

 

网易云课堂视频最新更新

第十一章 Spring Boot 日志

1、spring boot日志—理论

2、Spring Boot日志-logback

3、Spring Boot日志-log4j2

第十二章 Spring Boot 知识点2

1、spring boot 服务配置和部署

2、Spring Boot 定制URL匹配规则

 

 

历史章节

 

第一章 快速开始

1、Spring Boot之Hello World

2、Spring Boot之Hello World访问404

 

第二章 Spring Boot之JSON

1、spring boot返回json数据

2、Spring Boot完美使用FastJson解析JSON数据

 

第三章 Spring Boot热部署

1、Spring Boot热部署(springloader)

2、springboot + devtools(热部署)

 

第四章 Spring Boot数据库

1、Spring Boot JPA/Hibernate/Spring Data概念

2、Spring Boot JPA-Hibernate

3、Spring Boot Spring Data JPA介绍

4、Spring Boot JdbcTemplate

5、Spring Boot集成MyBatis

 

第五章 web开发

1、全局异常捕捉

2、配置server信息

3、spring boot使用thymeleaf

4、Spring Boot 使用freemarker

5、Spring Boot添加JSP支持

 

第六章 定时任务

1、Spring Boot定时任务

2、Spring Boot 定时任务升级篇(动态修改cron参数)

3、Spring Boot 定时任务升级篇(动态添加修改删除定时任务)

4、Spring Boot 定时任务升级篇(集群/分布式下的定时任务说明)

5、Spring Boot Quartz介绍

6、Spring Boot Quartz在Java Project中使用

7、Spring Boot 集成Quartz普通使用

8、Spring Boot 集成Quartz升级版

9、Spring Boot 集成Quartz二次升级版

10、Spring Boot 集成Quartz-Job如何自动注入Spring容器托管的对象

 

第七章 Spring Boot MyBatis升级篇

1、Spring Boot MyBatis升级篇-注解

2、Spring Boot MyBatis升级篇-注解-自增ID

3、Spring Boot MyBatis升级篇-注解-增删改查

4、Spring Boot MyBatis升级篇-注解-分页查询

5、Spring Boot MyBatis升级篇-注解-分页PageHelper不生效

6、Spring Boot MyBatis升级篇-注解- mybatic insert异常:BindingException: Parameter 'name' not found

7、Spring Boot MyBatis升级篇-注解- #和$符号特别篇

8、Spring Boot MyBatis升级篇-注解-@Result

9、Spring Boot MyBatis升级篇-注解-动态SQL(if test)-方案一:<script>

10、Spring Boot MyBatis升级篇-注解-动态SQL(if test)-方案二:@Provider

11、Spring Boot MyBatis升级篇-注解-动态SQL-参数问题

12、Spring Boot MyBatis升级篇-注解-特别篇:@MapperScan和@Mapper

13、Spring Boot MyBatis升级篇-XML

14、Spring Boot MyBatis升级篇-XML-自增ID

15、Spring Boot MyBatis升级篇-XML-增删改查

16、Spring Boot MyBatis升级篇-XML-分页查询

17、Spring Boot MyBatis升级篇-XML-分页PageHelper不生效

18、Spring Boot MyBatis升级篇-XML-动态SQL(if test)

19、Spring Boot MyBatis升级篇-XML-注解-初尝试

20、Spring Boot MyBatis升级篇- pagehelper替换为pagehelper-spring-boot-starter

 

第八章 Spring Boot 知识点1

1、Spring Boot 拦截器HandlerInterceptor

2、Spring Boot启动加载数据CommandLineRunner

3、Spring Boot环境变量读取和属性对象的绑定

4、Spring Boot使用自定义的properties

5、Spring Boot使用自定义的properties

6、Spring Boot使用@SpringBootApplication

7、Spring Boot 监控和管理生产环境

 

第十章 Spring Boot 打包部署

1、Spring Boot打包部署((提供Linux的sh文件))

 

第十一章 Spring Boot 日志

1、spring boot日志—理论

2、Spring Boot日志-logback

3、Spring Boot日志-log4j2

 

 

分享到:
评论
25 楼 cqh520llr 2019-01-23  
zzp1994114 写道
能成功路由,但是无法使用第二个数据源的事务,只有默认数据源的事务会生效


这种方式事务不起作用。
24 楼 zzp1994114 2018-07-25  
能成功路由,但是无法使用第二个数据源的事务,只有默认数据源的事务会生效
23 楼 林祥纤 2018-06-03  
joekey 写道
老大,这里有一个问题,如果我一个Controller调用一个service的两个方法,其中一个没有注解数据源(默认数据源),一个注解某个数据源,结果两个方法访问的都是同一个数据源,没有实现切换,请问怎么解决?谢谢。。。。


正常情况下是会切换的!
22 楼 joekey 2018-05-24  
老大,这里有一个问题,如果我一个Controller调用一个service的两个方法,其中一个没有注解数据源(默认数据源),一个注解某个数据源,结果两个方法访问的都是同一个数据源,没有实现切换,请问怎么解决?谢谢。。。。
21 楼 bnna8356586 2018-04-12  
bnna8356586 写道
您好,我想问下,如果要支持jdni数据源,是不是把xx.datasource.type=jndi就行了吗?还是需要加什么配置或者修改什么东西吗?求大神赐教

还有,如果使用com.zaxxer.hikari.HikariDataSource也是报错找不到类。
20 楼 bnna8356586 2018-04-12  
您好,我想问下,如果要支持jdni数据源,是不是把xx.datasource.type=jndi就行了吗?还是需要加什么配置或者修改什么东西吗?求大神赐教
19 楼 林祥纤 2017-12-23  
qingyun67 写道
弱问一下,历史章节怎么查看,貌似没有连接呀?


在左边的分类选择spring boot
18 楼 qingyun67 2017-12-22  
弱问一下,历史章节怎么查看,貌似没有连接呀?
17 楼 gdlcsoft 2017-09-01  
这种方式spring-jpa的方式好像不能自动路由

经测试,是可以路由jpa的。
环境是使用druid-spring-boot  1.1.2版本,
但是druid监控那里不出现多个数据源,只出第一个默认的。
16 楼 林祥纤 2017-08-16  
华家小谁 写道
不错,收x藏了

推荐下,分表分库数据库中间件 Sharding-JDBC 源码分析 17篇:http://www.yunai.me/categories/Sharding-JDBC/?iteye&802


感谢分享。
15 楼 董洪臣 2017-04-12  
林祥纤 写道
qq1488888 写道
林祥纤 写道
weir2009 写道
林祥纤 写道
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。


我刚刚用1.4.1   没有了


没有了,只能寻找其它方式了。


1.4.0版本的我试验了一下,可以成功切换,但确实是用不了JPA,而是用jdbctemplate,这里想问个问题,我用druid的话如何配置监控多个数据源,我现在只能监控主数据源??


这个问题我还没有去尝试过,你可以看看druid有没有人碰到这样的问题。


使用一下代码替换掉
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
return dataSource;


 DataSourceBuilder factory = DataSourceBuilder.create().         driverClassName(driverClassName).url(url).username(username).password(password);
 return factory.build();


可以实现druid 监控多数据源。
14 楼 林祥纤 2017-03-16  
nucleus 写道
林祥纤 写道
weir2009 写道
林祥纤 写道
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。


我刚刚用1.4.1   没有了


没有了,只能寻找其它方式了。

lz,AbstractRoutingDataSource是spring里面的吧。。自己都用了,却被人蒙是spring boot里的类?


你了解下spring boot和spring 的关系就明白了!
13 楼 nucleus 2017-03-15  
林祥纤 写道
weir2009 写道
林祥纤 写道
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。


我刚刚用1.4.1   没有了


没有了,只能寻找其它方式了。

lz,AbstractRoutingDataSource是spring里面的吧。。自己都用了,却被人蒙是spring boot里的类?
12 楼 小兵1234 2017-02-08  
你好,想问一下,你这样配置,在一个controller里的一个action方法里面。连续调用两个不同数据源的方法,会报错,第二个方法使用的始终是前一个方法的数据源,这个怎么解决?
11 楼 eric023 2016-12-14  
楼主,多数据源如何事务控制呀?
10 楼 林祥纤 2016-10-04  
qq1488888 写道
林祥纤 写道
weir2009 写道
林祥纤 写道
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。


我刚刚用1.4.1   没有了


没有了,只能寻找其它方式了。


1.4.0版本的我试验了一下,可以成功切换,但确实是用不了JPA,而是用jdbctemplate,这里想问个问题,我用druid的话如何配置监控多个数据源,我现在只能监控主数据源??


这个问题我还没有去尝试过,你可以看看druid有没有人碰到这样的问题。
9 楼 qq1488888 2016-10-04  
林祥纤 写道
weir2009 写道
林祥纤 写道
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。


我刚刚用1.4.1   没有了


没有了,只能寻找其它方式了。


1.4.0版本的我试验了一下,可以成功切换,但确实是用不了JPA,而是用jdbctemplate,这里想问个问题,我用druid的话如何配置监控多个数据源,我现在只能监控主数据源??
8 楼 林祥纤 2016-09-28  
weir2009 写道
林祥纤 写道
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。


我刚刚用1.4.1   没有了


没有了,只能寻找其它方式了。
7 楼 weir2009 2016-09-27  
林祥纤 写道
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。


我刚刚用1.4.1   没有了
6 楼 林祥纤 2016-09-27  
weir2009 写道
AbstractRoutingDataSource   
这个 在spring boot 包中并没有 
这种方式  不适合spring boot吧


在spring boot .1.3.3测试通过,新版本,我现在不确定。

相关推荐

    多图表实现员工满意度调查数据分析python

    员工满意度是指员工对于工作环境、待遇、职业发展和组织管理等方面的满意程度。它是衡量员工对工作的整体感受和情绪状态的重要指标。

    2020届软件工程本科毕业生毕业设计项目.zip

    2020届软件工程本科毕业生毕业设计项目

    基于stm32平衡小车

    平衡小车 基于stm32 平衡小车 基于stm32 平衡小车 基于stm32

    c语言火车票订票管理源码.rar

    c语言火车票订票管理源码.rar

    施耐德PLC例程源码四台水泵的轮换

    施耐德PLC例程源码四台水泵的轮换提取方式是百度网盘分享地址

    node-v16.13.2-linux-s390x.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    digix算法创新大赛2019baseline.zip

    digix算法创新大赛2019baseline

    三菱PLC小型立体仓库项目.zip

    三菱PLC小型立体仓库项目.zip 叉手篮具到位检测 入库2电机前限

    node-v15.3.0-linux-s390x.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    软考高项-ITTO背诵打印必过版-2024-高项已通过

    软考高项_ITTO背诵打印必过版_2024_高项已通过

    巧解HTTP三次握手四次挥手流程(超详细).docx

    TCP三次握手及四次挥手详细图解 相对于SOCKET开发者,TCP创建过程和链接折除过程是由TCP/IP协议栈自动创建的.因此开发者并不需要控制这个过程.但是对于理解TCP底层运作机制,相当有帮助. TCP三次握手 所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。 三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。 1.TCP建立流程 第一次握手:建立连接时,客户端发送SYN(Seq = J)包到服务器,并进入到syn_sent状态。等待服务器确认。 第二次握手:服务器收到SYN包,知道了Client端想建立连接. 它会向客户端发送SYN+ ACK包(ack =J+1 TCP 四次挥手 TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作

    node-v13.5.0-linux-arm64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    欧母龙PLC例程源码扎钢机程序

    欧母龙PLC例程源码扎钢机程序提取方式是百度网盘分享地址

    基于RGB相机的驾驶员注视区域估计_刘觅涵.pdf

    驾驶员注意力检测,驾驶员分神驾驶检测,DMS,汽车智能驾驶,智能座舱

    Unity实现二维码扫描,二维码生成

    Unity实现二维码扫描,二维码生成 提供ZXing.unity.dll可以进行二维码的生成和扫描,扫描的方式是在场景中发射射线,射线获取rawimage的texture并读取,然后作为二维码进行解析。 提供源代码。

    中山大学-计科-操作系统实验.zip

    中山大学-计科-操作系统实验.zip

    施耐德PLC例程源码恒压供水程序(用施耐德TWIDOPLC编的)

    施耐德PLC例程源码恒压供水程序(用施耐德TWIDO PLC编的)提取方式是百度网盘分享地址

    node-v14.8.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v14.6.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    python实现小说词频统计

    小说词频统计是指对一本小说中出现的各个词语进行计数和分析,以确定每个词语在整篇小说中的出现频率。 以下是对小说词频统计的一些基本说明: 数据收集:首先需要获取目标小说的文本数据。这可以通过手动输入、复制粘贴文本内容或使用自动化工具来提取文本。 文本处理:在进行词频统计之前,通常需要对文本数据进行预处理。这可能包括去除标点符号、停用词(如“的”、“是”等)和特殊字符,将文本转换为统一的格式。 词语计数:进行词频统计时,遍历整个文本,逐个词语地计数它们的出现次数。通常会使用字典、哈希表或其他数据结构来存储词语及其计数。

Global site tag (gtag.js) - Google Analytics