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提供的函数了
}
上面是“讲一讲”,下面是"拿来用"
下面是目录结构图:
(本文列出了项目必须的所有源码文件,粘下来即可组成完整项目)
下面是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;
}
-----全文结束-----