jpa和jdbcTemplate类都使用的是DataSource作为数据源,它只是一个接口类,主要就是对调用方提供了:


public Connection getConnection();


这样一个核心函数,返回一个标准的java.sql的连接对象Connection,以供给jpa或jdbcTemplate类使用来操作数据库表。

所以实现原理很简单,就是自写个dynamicDataSource的类,继承DataSource接口,重写getConnection()主函数即可。在多数据源的情况下,做一个字典Map,以用户选择的数据源标签String作为key,以真实数据源对象作为value,按需返回不同的真实数据源的Connection对象。

本文标题中的后期是指数据源的加入不是在配置文件生成bean的初如化时期,而是进程执行的任何时期。

本文标题中的动态是指数据源可以随时加入,也可以随时卸载。

本文标题中的多数据源是指多个和多种的意思,目前主要举例了:


DruidDataSource,HikariDataSource,PooledDataSource这三种连接池数据源,另外新增了一种jdbc短连接数据源(就是把一个jdbc短连接暂时作为数据源只用一次就关闭释放)。


本源码的作用在于:从一个数据库的表中取得那些读取其它数据库的信息之后,并发对N多个不同主机的数据库进行访问。对于数据库隔离级别的SAAS系统很适用。

本源码用到的关键技术是:

1.Aop切面编程,用它来实现以注解方式按需选择数据源和还原线程状态。

2.ThreadLocal(线程本地存储区)对定义的同一个变量,不同线程各自有一份独立的不同的内存变量的空间和值。

下面列出几处关键调用代码,完整的源码见后。

Controller控制器中的 “调用方法” 举例:

@MyOtherDataSource(value = "my_other_ds_white")//直接指明数据源标签
@GetMapping("/getbooks")//取多本书所有
public List<Book> getBooks() {
    List<Book> books=bookService.getBooks();
    return books;
}//不加注解则使用默认数据源
@MyOtherDataSource(is_use_method_last_para = true)//指明下面方法的最后一个参数是数据源TAG
@GetMapping("/getbook")//只取一本书,按书名
public String getBook(String name,String ds_tag) {
    Book book=bookService.getBook(name);//更建议把上面的注解用在服务层的方法上
    return book.toString();
}

第三种是在@MyOtherDataSource中指定一个回调函数来获取数据源TAG。此处暂略见开源。

下面是第四种:

@GetMapping("/getbooksd")
public List<Book> getBooksd() {
    List<Book> books=null;
    try {
        ThreadDataSourceTag.setThreadDSTag("my_other_ds_white");
        //不用注解,直接修改数据源标签来便捷地选择数据源
        books = bookService.getBooks();//执行JPA数据访问
    }catch (Exception e){e.printStackTrace();}
    finally {
        ThreadDataSourceTag.freeThreadDSTag();
        //但是注意这一条不论上面是否异常都要运行。否则会对后续数据访问形成影响。
    }

    return books;
}

最后一种是短连接数据源调用方式:

@GetMapping("/getbookbyid2")//只取一本书,按id
public String getBookbyid2(Long id) {
//调用服务层的方法,重点是传入后三个参数
 book=bookService.getBook2( id,       //查书的id"jdbc:mysql://209.91.145.87:33062/white?useUnicode=true",
"super",   //数据库的帐号
"123456" );
 return  book.toString(); }//下面这几行代码是服务层service里的方法,在服务层方法上加的注解
@MyShortConnect(value = "com.mysql.cj.jdbc.Driver")
public Book getBook2(Long id,String jdbc_url,String uid,String pwd)
{  //上面后三个参数是AOP切面使用来建立短连接的,与本函数逻辑无关。
   return bookRepository.findBookById(id);//这个是JPA提供的函数了
}

上面是“讲一讲”,下面是"拿来用"

下面是目录结构图:

(本文列出了项目必须的所有源码文件,粘下来即可组成完整项目)

Java动态数据源 jpa动态数据源_java

下面是POM.XML文件:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.jpa</groupId>
    <artifactId>sb-jpa-plus</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sb-jpa-demo</name>
    <description>sb-jpa-plus</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>4.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--快重启-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional> <!-- 可选 -->
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.jpa.Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

下面是必要源码文件:

首先是在配置中建立原始的一两个数据源(其它更多数据源可以后期加入)

下面的代码来自文件:DyDataSourceConfig.java

@Configuration
public class DyDataSourceConfig {
    @Value("${spring.datasource.password}")
    private String ds_pwd;
    @Value("${spring.datasource.driver-class-name}")
    private String ds_dcn;
    @Value("${spring.datasource.url}")
    private String ds_url;
    @Value("${spring.datasource.username}")
    private String ds_uid;

    private Object create_default_datasource()
    {
        HikariDataSource dataSource1 = new HikariDataSource();
        dataSource1.setPoolName(ds_pool_name+"_self_define");
        dataSource1.setDriverClassName(ds_dcn);
        dataSource1.setJdbcUrl(ds_url);
        dataSource1.setUsername(ds_uid);
        dataSource1.setPassword(ds_pwd);

        dataSource1.setMaxLifetime(38800*1000);
        dataSource1.setIdleTimeout(28800*1000);

        dataSource1.setMaximumPoolSize(1024);
        dataSource1.setMinimumIdle(64);
        //dataSource1.setKeepaliveTime(60*1000);//发送下面这个心跳数据包的时隔
        dataSource1.setConnectionInitSql("select 1");

        return dataSource1;
    }//end function


    private Object create_other_datasource()
    {
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setPoolMaximumActiveConnections(100);
        dataSource.setPoolMaximumIdleConnections(100);
        dataSource.setDriver("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://209.91.145.87:33062/white?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
        dataSource.setUsername("super");
        dataSource.setPassword("123456");
        return dataSource;
    }


    @Bean 
    public DyDataSource dataSource(){
        DyDataSource dyd=new DyDataSource();
        //上面这个是虚动态数据源(继承了DataSource接口),下面将包入建立的真实数据源
        Object ds1=create_default_datasource();//这里建立一个实体数据源,作为默认数据源
        //当没有加注解或没有做数据源选择时,将用它,这样让代码修改量最小
        DyDataSource.add_into_default_DataSource((DataSource) ds1);//这个作为默认数据源
        Object ds2=create_other_datasource();//这里建立一个实体数据源,作为第二自定义另外的数据源
        DyDataSource.add_into_MyOther_DataSource("my_other_ds_white",(DataSource)ds2);
        //以后还可以在控制器中按需动态再增删其它数据源,或短连接数据源。
        return dyd;//把这个虚动态数据源提供给spring容器.而把实体数据源们hide起来备选。
    }//end func

-----本段代码文件结束-----

文件名:DyDataSource.java

import com.alibaba.druid.pool.DruidDataSource;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Getter;
import lombok.Setter;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.springframework.jdbc.datasource.AbstractDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


@Setter
@Getter
public class DyDataSource extends AbstractDataSource {

    private static Map<String,DataSource> dy_ds=new ConcurrentHashMap<>();
    //上面是标签tag作为key,实体数据源作为value
    public static final  String default_ds_tag="my_default_ds_20220607";
    public static final  String short_single_connect_tag ="my_short_single_connect_20220607";

    //检查标签是否已加入
    public static Boolean is_ds_already_add_into(String ds_tag)
    {
        return dy_ds.containsKey(ds_tag);
    }
    //default默认数据源的添加
    public static Boolean add_into_default_DataSource(DataSource obj)
    {
        if( obj==null )return false;
        dy_ds.put(default_ds_tag,obj);
        return true;
    }

    //下面是最简的建立另外一个数据源的函数供用户调用的范例,用户最好是自行实现一个函数
   public static DataSource create_other_DataSource(String un,String pwd,String jdbc_url,String driver_class_name)
    {
        DruidDataSource ds2= new DruidDataSource();//阿里连接池
        ds2.setName("myOtherDS");
        ds2.setDriverClassName(driver_class_name);
        ds2.setUrl(jdbc_url);
        ds2.setUsername(un);
        ds2.setPassword(pwd);
        return (DataSource)ds2;
    }

    //动态多数据源的添加
    public static Boolean add_into_MyOther_DataSource(String ds_tag_key, DataSource obj)
    {
        if(ds_tag_key==null ||  ds_tag_key.isEmpty())return false;
        if( obj==null )return false;

        dy_ds.put(ds_tag_key,obj);
        return true;
    }
    //下面是资源回收,关闭长期不用的数据源
    public static Boolean delone_MyOther_DataSource(String ds_tag_key)
    {
        if(ds_tag_key==null ||  ds_tag_key.isEmpty())return false;

        Object ds=dy_ds.get(ds_tag_key);
        if(null==ds)return true;//已经删除了

        if(ds instanceof DruidDataSource)//阿里池
        {
            ((DruidDataSource)ds).close();
        }
        else if(ds instanceof HikariDataSource)//spring默认池
        {
            ((HikariDataSource)ds).close();
        }
        else if(ds instanceof PooledDataSource)//mybatis的连接池
        {
            ((PooledDataSource) ds).forceCloseAll();
        }
        //PooledDataSource,这个是mybatis的池数据源,也可以加上,但它没有继承closeable,所以没close();
        //PooledDataSource有一条自定义的"forcecloseall"函数,来做io_close.

        dy_ds.remove(ds_tag_key);
        ds=null;

        return true;//成功删除掉一个数据源
    }
    //上面是静态函数,被用户调用的


    //下面这个是内部自调用核心函数,根据标签取出数据源
    private DataSource get_Cur_Set_Thread_DS() throws Exception {
        String tag= ThreadDataSourceTag.getThreadDSTag();//取得线程数据源标签

        if(tag==null || tag.isEmpty())
        {
            tag=default_ds_tag;
            //发生这种情形,一般是数据库操作的相关服务的方法,没有使用MyOtherDataSource注解。
            //这样一来,只需要把注解加到要换成"特殊另外数据库"的地方即可,让代码改动量最小
        }

        System.out.println("last_action_of_ds_tag:"+tag);
        DataSource ds=dy_ds.get(tag);
        if (null != ds) { return ds;}
        else
        {
            throw new Exception("动态数据源类之:按标签:ds_tag=" + tag + "获取数据源失败!你的数据源都还没有加入进来呢?");
        }
    }

    //下面两个函数,是真正那些JPA,jdbcTEMPLATE,这样一些数据源使用类会调用到的函数
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        try {
            return get_Cur_Set_Thread_DS().getConnection(username,password);
        }catch (Exception E){E.printStackTrace();}
        return null;
    }

    @Override //下面这个就是核心实现函数
    public Connection getConnection() throws SQLException {
        try {
            String tag= ThreadDataSourceTag.getThreadDSTag();//1.取得线程数据源标签
            //上面这个String标签是用户通过加函数注解方式按需设置的
            if(tag==null || tag.isEmpty())
            {
                tag=default_ds_tag;
     //发生这种情形,一般是数据库操作的相关服务的方法,没有使用MyOtherDataSource注解。
     //这样一来,只需要把注解加到要换成"特殊另外数据库"的地方即可,让代码改动量最小
            }
            System.out.println("last_action_of_ds_tag: "+tag);

            if(!tag.equals(short_single_connect_tag)) {
                DataSource ds = dy_ds.get(tag);//2.根据标签key从Map中取出真实数据源
                if (null != ds) { return ds.getConnection();}//3.返回连接对象给上家
                else
                {
                    throw new Exception("动态数据源类之:按标签:ds_tag=" + tag + "获取数据源失败!你的数据源都还没有加入进来呢?");
                }
            }
            else //4.下面是新增的一次性短连接数据源的处理方式,类同。
            {
                //取得"短连接"来返回
                Connection short_conn= ThreadDataSourceTag.getThreadConn();
                if(short_conn!=null){return short_conn;}//返回正确值
                {
                    throw new SQLException("短连接失败,可能是连接参数不正确或服务器服务关闭.");
                }
            }
        }catch (Exception E){E.printStackTrace();}
        return null;
    }
}//end class define

文件名:ThreadDataSourceTag.java

import java.sql.Connection;
import java.sql.SQLException;

//下面这个是一个静态类,可以在任何操作数据库表之前与之后调用
public class ThreadDataSourceTag {

    // 使用ThreadLocal保证线程安全
    private static final ThreadLocal<String> ds_tag = new ThreadLocal<String>();
    //说土点就是,在每个线程上,上面这个变量都有一个独立的实体,有10个线程,上面这个变量就有分别独立的10个

    public static void setThreadDSTag(String  tag) throws Exception {
        if (tag == null ) {
            throw new NullPointerException("zw报错:setThreadDSTag函数的传入参数tag为null或empty!");
        }
        ds_tag.set(tag);
    }

    public static String getThreadDSTag() {
        return ds_tag.get();//这里有可能返回null,如果之前没有调用ds_tag.set(tag),或这前进行了ds_tag.remove();
    }

    //注意:set后,下面这个必须调用,否则会形成内存泄漏!!!,如果没有set tag就调用,会让进程结束
    public static void freeThreadDSTag() {
        if(null!=ds_tag.get()) { //这里不验证null会引发进程退出(if null)
            ds_tag.remove();
        }
    }

    ///
    ///
    //下面的类同代码应用于"short单连接"
    private static final ThreadLocal<Connection> t_conn = new ThreadLocal<>();
    public static void setThreadConn(Connection  _conn) throws Exception {
        if (_conn == null ) {
            throw new NullPointerException("zw报错:setThreadConn函数的传入参数conn为null!");
        }
        t_conn.set(_conn);
    }

    public static Connection getThreadConn() {
        return t_conn.get();//这里有可能返回null,如果之前没有调用ds_tag.set(tag),或这前进行了ds_tag.remove();
    }

    //注意:set后,下面这个必须调用,否则会形成内存泄漏!!!,如果没有set tag就调用,会让进程结束
    public static void freeThreadConn() {
        if(null!=t_conn.get()) {

            try {
                ((Connection) t_conn.get()).close();//关闭连接
            }catch (SQLException s){s.printStackTrace();}

            t_conn.remove();//删除内存对象
        }
    }//end func
}

文件名:DyDataSourceAOP.java

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.stereotype.Component;

import java.sql.*;

///
@Aspect //把当前类标识成一个切面,供SPRING容器回调,这个需要重点深入学习
//
@Component
public class DyDataSourceAOP {
    //注意:实测能拦载的只有控制器和服务的函数,我另外做了个子函数被控制器的方法调用,却不能拦到。
    @Before("@annotation(ds_tag)")//拦截我们的注解-“加在JPA的数据库操作指令QAUD方法”
    //一个重点注意:因为下面有free,所以上面setThreadDSTag(value)时,value一定不能为null!,否则进程退出
    public void setCurDataSourceOfTag(JoinPoint point, MyOtherDataSource ds_tag) throws Throwable {
        System.out.println("===========> aop before do.....DS");
        if(!ds_tag.is_use_method_last_para() && !ds_tag.is_use_callback()) {
            String value = ds_tag.value();
            ThreadDataSourceTag.setThreadDSTag(value);//在方法调用之前加上动态数据源标签
        }
        else if( ds_tag.is_use_method_last_para() )
        {
            //取当前方法的最后一个参数值,作为标签
            int args=point.getArgs().length;
            if(args>=1) { //方法至少要有一个参数
                Object obj = point.getArgs()[args - 1];
                if(obj!=null){ ThreadDataSourceTag.setThreadDSTag(obj.toString());}//在方法调用之前加上动态数据源标签
                else{ throw new Exception(point.toShortString()+ "方法的最后一个参数作为数据源的ds_tag标签,但是其值却为null");}
            }
            else{ throw new Exception(point.toShortString()+"你定义的方法没有设置参数,却选择了最后一个参数作为ds_tag标签,这是矛盾的。");}
        }
        else
        {
            Class<?> clazz=ds_tag.SelectTagClass();//得到test_callback.class
            SelectTagHandler sth =(SelectTagHandler) MySpringUtil.getBean(clazz);
            //用接口指向(根据类类型)获得的bean
            String value=sth.select_ds_tag(ds_tag.params());//用接口调用bean的public函数
            if(value==null)
            {
                throw new NullPointerException(point.toShortString()+"你定义的回调方法select_ds_tag获取ds_tag,但是取得的是null!");
            }
            ThreadDataSourceTag.setThreadDSTag(value);//在方法调用之前加上动态数据源标签
        }
    }//end func
    //一个重点注意:因为下面有free,所以上面setThreadDSTag(value)时,value一定不能为null!,否则进程退出

    @After("@annotation(ds_tag)") //清除数据源的配置
    public void free_thread_Tag_memory(JoinPoint point, MyOtherDataSource ds_tag) {
        ThreadDataSourceTag.freeThreadDSTag();//在方法调用之后回收线程内存片
        System.out.println("===========> aop after over do.....DS");
    }


    /
    /
    @Before("@annotation(shortConnect)")//拦截我们的注解-“加在JPA的数据库操作指令QAUD方法”
    public void setSingleConnect(JoinPoint point, MyShortConnect shortConnect) throws Exception {
        System.out.println("===========> aop before do.....shortConnect");
        ThreadDataSourceTag.setThreadDSTag(DyDataSource.short_single_connect_tag);

        String driver_name=null;
        String pwd=null;
        String uid=null;
        String jdbc_url=null;

        int args=point.getArgs().length;
        if(args>=3) { //方法至少要有一个参数
           driver_name=shortConnect.value();
           pwd = point.getArgs()[args - 1].toString();
           uid =   point.getArgs()[args - 2].toString();
           jdbc_url = point.getArgs()[args - 3].toString();
        }
        else{ throw new Exception(point.toShortString()+"你定义的方法没有设置参数,却选择了最后3个参数作为数据库的connect信息参数,这是矛盾的。");}

        try {
            Class.forName(driver_name);
            Connection conn=(Connection) DriverManager.getConnection(jdbc_url,uid,pwd);
            //重要说明:如果上面这条报错,则下面无法运行,线程conn=null

            //下面把连接对象放入线程存储
            ThreadDataSourceTag.setThreadConn(conn);
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }//end func



    @After("@annotation(shortConnect)") //清除数据源的配置
    public void free_singleConnect_memory(JoinPoint point, MyShortConnect shortConnect) {
        ThreadDataSourceTag.freeThreadConn();
        ThreadDataSourceTag.freeThreadDSTag();
        System.out.println("===========> aop after over do.....shortConnect");
    }

}

文件名:MyOtherDataSource.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyOtherDataSource {
     //注意:下面三者只能选一种方式,默认是第一种,如果设置了后两种的bool=true,则放弃第一种,自动选用后两种之一
     String value() default "my_default_ds_20220607";  //1.通过值选择数据源
     boolean is_use_method_last_para() default false;   //2.通过函数的最后一个参数选择数据源
     //3.通过回调函数选择数据源
     boolean is_use_callback() default false;
     String[] params() default {};//对下一个函数的传参
     Class<? extends SelectTagHandler> SelectTagClass() default SelectTagHandler.class;
}

文件名:MyShortConnect.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyShortConnect {
     //注意:下面三者只能选一种方式,默认是第一种,如果设置了后两种的bool=true,则放弃第一种,自动选用后两种之一
     String value() default "com.mysql.cj.jdbc.Driver";  //取得DIRVER
     //2.通过参数取得url,un,pwd
}

文件名:MySpringUtil.java

import lombok.SneakyThrows;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;

import java.util.function.Consumer;

/**
 * @author YuePeng
 * date 1/24/19.
 */
@Component
public class MySpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (MySpringUtil.applicationContext == null) {
            MySpringUtil.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过class获取Bean.
    @SneakyThrows
    public static <T> T getBean(Class<T> clazz) {

            return getApplicationContext().getBean(clazz);

    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getProperty(String key, Class<T> clazz) {
        return getApplicationContext().getEnvironment().getProperty(key, clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    //根据类路径获取bean
    public static <T> T getBeanByPath(String path, Class<T> clazz) throws ClassNotFoundException {
        return clazz.cast(getBean(Class.forName(path)));
    }

}

文件名:SelectTagHandler.java


public interface SelectTagHandler {
    String select_ds_tag(String[] params);
}


文件名:test_callback.java

import org.springframework.stereotype.Service;

//这里必须要做成一个bean
@Service
public class test_callback implements SelectTagHandler{

    @Override
    public String select_ds_tag(String[] params) {
        //这里只是测试,这边用户可以随便写代码的了,按逻辑实现数据源标签的选择
        if(params.length>=1)
        {
            return "my_other_ds_"+params[0];
        }
        return DyDataSource.default_ds_tag;
    }
}

文件名:BookServiceImpl.java

import com.jpa.database.MyOtherDataSource;
import com.jpa.database.MyShortConnect;
import com.jpa.mapper.BookRepository;
import com.jpa.entity.Book;
import com.jpa.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookRepository bookRepository;

    @Override
    public List<Book> getBooks() {
        return bookRepository.findAll();
    }

    @Override
    public Page<Book> getPages(int pcode,int psize) {
        Pageable pga =  PageRequest.of(pcode,psize);

        Page<Book> pb=bookRepository.findAll(pga);//向下跳一页
        System.out.println("pagecode="+pga.getPageNumber());
        System.out.println("totalids="+pb.getTotalElements());
        System.out.println("totalpages="+pb.getTotalPages());

        return pb;
    }

    @Override
    public  List<Book> getBooksByUrl(String bookUrl)
    {
        return bookRepository.findAllByBookUrlContains(bookUrl);
    }


    @Override
    public Book getBook(String bookName)
    {
        return bookRepository.findBookByBookName(bookName);
    }

    //下面是在服务层添加动态数据源的演示
    @MyOtherDataSource(is_use_method_last_para = true)
    public Book getBook(Long id,String ds_tag)
    {
        return bookRepository.findBookById(id);
    }

    @MyShortConnect(value = "com.mysql.cj.jdbc.Driver")//短连接测试(非池)
    public Book getBook2(Long id,String jdbc_url,String uid,String pwd)
    {
      return bookRepository.findBookById(id);
    }

    //如果book中的id存在,就是更新,如果不存在,就是添加。
    @Override
    public void saveBook(Book book) {
        bookRepository.save(book);
    }

    @Override
    public Long getCount() {
        return bookRepository.count();
    }
}
文件名:BookService.java

import com.jpa.entity.Book;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import java.util.List; 
public interface BookService {

    List<Book> getBooks();
    Page<Book> getPages(int pcode,int psize);

    void saveBook(Book book);
    Long getCount();

    Book getBook(String bookName);
    List<Book> getBooksByUrl(String bookUrl);

    Book getBook(Long id,String ds_tag);
    Book getBook2(Long id,String jdbc_url,String uid,String pwd);
}


文件名:BookRepository.java

import com.jpa.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface BookRepository extends JpaRepository<Book, Long> {
    //zw注意:下面这些函数的实现代码,都由JPA框架代为自动完成了
    //注意三,add/update不需要在这里重写,可以直接调用save,但是:
    //按"字段"查询类的函数必要写在这里,但也只要写个名称就行了。
    Book findBookByBookName(String bookName);//这里假设书名是唯一性的。
    Book findBookById(Long bookId);
    List<Book> findAllByBookUrlContains(String bookUrl);
}

文件名:BookController.java

import com.jpa.database.DyDataSource;
import com.jpa.database.MyOtherDataSource;
import com.jpa.database.test_callback;
import com.jpa.entity.Book;
import com.jpa.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class BookController {
    @Autowired
    private DyDataSource dds;

    @Autowired
    private BookService bookService;

    /**
   为了做下面的测试,我一共建立了三个数据库tjpa,white,gray,都有同一张表,但记录的内容不同。
     */
    @MyOtherDataSource(value = "my_other_ds_white")//直接指明数据源标签
    @GetMapping("/getbooks")//取多本书所有
    public List<Book> getBooks() {
        List<Book> books=bookService.getBooks();
        return books;
    }

    //下面是什么都没有改的原来的代码,将使用默认池
    @GetMapping("/getbooksb")//取多本书所有
    public List<Book> getBooksB() {
        List<Book> books=bookService.getBooks();
        return books;
    }


    @MyOtherDataSource(is_use_method_last_para = true)//指明方法的最后一个参数是数据源TAG
    @GetMapping("/getbook")//只取一本,按书名
    public String getBook(String name,String ds_tag) {
        Book book=bookService.getBook(name);
        return book.toString();//本来这里应处理查不到,使不报异常,
        //但就是要用它来查看异常后,after切片是否执行,保证线程上没有ds_tag,从而保证多数据源选择的正确性,
        //尤其是大量没加标签使用默认数据源的,要保证它们的原始正确性,不要切到OTHER数据源上去了。
    }

    @MyOtherDataSource( is_use_callback = true, //用回调函数结合传入参数来共同指定数据源标签
                        params = {"white"},
                        SelectTagClass = test_callback.class)
    @GetMapping("/getbooksurl")//只取一本,按书名
    public List<Book> getBooksByUrl(String burl) {
        List<Book> books=bookService.getBooksByUrl(burl);
        return books;//本来这里应处理查不到,使不报异常,
        //但就是要用它来查看异常后,after切片是否执行,保证线程上没有ds_tag,从而保证多数据源选择的正确性,
        //尤其是大量没加标签使用默认数据源的,要保证它们的原始正确性,不要切到OTHER数据源上去了。
    }


    @GetMapping("/getbookbyid")//只取一本,按id
    public String getBookbyid(Long id) {
        if(null==id){id=1L;}

        //下面DEMO的是根据一些分析条件,在服务层做数据源的动态选择
        //在实际开发中,可能下面的可能性更大些,而且效果会更好。
        Book book=null;
        if(id==5)book=bookService.getBook(id,"my_other_ds_white");
        else if(id==4)book=bookService.getBook(id,"my_other_ds_gray");
        //如果这之前上面两个的数据源没有真正ADD,则会报异常。
        else book=bookService.getBook(id,DyDataSource.default_ds_tag);

        if(book==null)return "no found book";
        return book.toString();
    }



    //下面动态添加一个数据源并启用它,实际上也可以指向别的主机或端口
    @GetMapping("/addds")
    public String addds(String dbname) {
        if(dbname==null)dbname="white";//如果传入的数据库名为空,就暂时指向这个other数据库

        String m_cur_ds_tag ="my_other_ds_"+dbname;//数据源标签

        if(!DyDataSource.is_ds_already_add_into(m_cur_ds_tag)) {
            String dcn = "com.mysql.cj.jdbc.Driver";
            String url = "jdbc:mysql://209.91.145.87:33062/" + dbname + "?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8";
            String username = "super";
            String password = "123456";
            DyDataSource.add_into_MyOther_DataSource(m_cur_ds_tag, DyDataSource.create_other_DataSource(username, password, url, dcn));
            return "add ok";//自动转为JSON数组输出到数据流
        }
        return "already have.";
    }

    @GetMapping("/free_ds")
    public String free_ds(String dbname) {
        if(dbname==null)dbname="white";//如果传入的数据库名为空,就暂时指向这个other数据库

        String m_cur_ds_tag = "my_other_ds_" + dbname;//数据源标签
        if (DyDataSource.is_ds_already_add_into(m_cur_ds_tag)) {
            DyDataSource.delone_MyOther_DataSource(m_cur_ds_tag);
            return "free ok";//成功删除一个已创建立的动态数据源
        }
        return "no found obj";
    }

    //下面这个就是原有代码,没加ds_tag标签,使用默认数据源的范例
    //下面这个最好是用POSTMAN调试,返回的是两个JSON对象,一个是content数据数组,一个是分页信息
    @GetMapping("/getpage")//分页取多本书
    public Page<Book> getBooks(int p_code,int p_size) {
        Page<Book> pb=bookService.getPages(p_code,p_size);
        return pb;//自动转为JSON数组输出到数据流
    }


    //add,update,请用postman来添加,传入json,其中有id是update,没有是add
    @ResponseBody
    @RequestMapping(value = "/savebook", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
    public String AddBook(@RequestBody Book book) {
        bookService.saveBook(book);
        return "{\"statue\":\"ok\",\"msg\":\"添加成功\"}";
    }

}

文件名:BookController2.java

import com.jpa.database.*;
import com.jpa.entity.Book;
import com.jpa.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.web.bind.annotation.*;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class BookController2 {

    @Autowired
    private BookService bookService;

    @GetMapping("/getbooksc")//这个是测试不用注解的情形,将保持使用默认数据源
    public List<Book> getBooksc() {
        List<Book> books=bookService.getBooks();
        return books;
    }

    @GetMapping("/getbooksd")
    public List<Book> getBooksd() {
        List<Book> books=null;

        try {
            ThreadDataSourceTag.setThreadDSTag("my_other_ds_white");//直接修改数据源标签来最便捷地选择数据源
            books = bookService.getBooks();//执行JPA数据访问
        }catch (Exception e){e.printStackTrace();}
        finally {
            ThreadDataSourceTag.freeThreadDSTag();
            //但是注意这一条不论上面是否异常都要运行。否则会对后续数据访问形成影响。
        }

        return books;
    }


    @GetMapping("/getbookbyid2")//只取一本,按id
    public String getBookbyid2(Long id) {
        if(null==id){id=1L;}
        //下面DEMO的是根据一些分析条件,在服务层加注解做数据源的动态选择,请参见服务层的对应函数bookService.getBook2
        //在实际开发中,可能下面的可能性更大些,而且效果会更好,连接池很耗资源,是高并发和常用数据库的选择。
        //对于那些从数据库表中取出的数据连接,偶尔连接访问一两次的,适用于下面的方式---"一次性短连接",速度比前者慢些,但资源回收快,没有日常资源占用。
        Book book=null;
        if(id==5)book=bookService.getBook2(
                id,
                "jdbc:mysql://209.91.145.87:33062/white?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai",
                "super",
                "123456"
                );

        else if(id==4)book=bookService.getBook2(
                id,
                "jdbc:mysql://209.91.145.87:33062/gray?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai",
                "super",
                "123456"
        );
        //如果这之前上面两个的数据源没有真正ADD,则会报异常。
       else book=bookService.getBook2(
                    id,
                    "jdbc:mysql://209.91.145.87:33062/tjpa?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai",
                    "super",
                    "123456"
                    );

        if(book==null)return "no found book";
        return book.toString();
    }



    //下面是模板类使用动态数据源的测试
    @MyOtherDataSource(is_use_method_last_para = true)
    @GetMapping("/getbook2")//这个是测试不用注解的情形
    public String getBook2(String name,String ds_tag) {
        String ret_str="no found data!";
        if(name==null)name="";

       DataSource ds=MySpringUtil.getBean(DataSource.class);//获取数据源,就是在配置文件中返回的那个dydatesouce的bean


        try {
            JdbcTemplate jt = new JdbcTemplate(ds);//JDBC模板类,使用动态数据源
            //下为查找一行
            Book book = jt.queryForObject("select * from zhuwei_book where book_name='" + name + "'", new BeanPropertyRowMapper<Book>(Book.class));

            if (null != book) {
                return book.toString();
            }
        }catch (Exception e){e.printStackTrace();}



        return ret_str;
    }//end func

}

文件名:Book.java

import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;


@Table(name = "zhuwei_book")
@Data
@Entity
public class Book {

    /** 主键ID */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /** 图书名称 */
    @Column
    private String bookName;

    /** 图书publish出版社*/
    @Column
    private String bookCover;

    /** 图书作者 */
    @Column
    private String bookAuthor;

    /** 简介 */
    @Column
    private String bookInfo;

    //ISDN编码
    @Column
    private String bookISDN;

    //图书对应网址
    @Column
    private String bookUrl;

    //图书备注
    @Column
    private String bookRemark;
}

 -----全文结束-----