Spring中的动态数据源解读
作者:daliucheng
动态数据源的原理得先说清。
原理
平常在使用Mysql的时候是通过JDBC的,得给一个url,userName,和password,如下:
jdbc:mysql://localhost:3306/t_db1?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
一个url对应一个Connection对象,需要在url中指定需要连接的库。
之后的Mysql的CRUD的操作都是通过Connection对象来做的。所以,动态,就是在这个时候操作的,在获取Connection的时候来通过指定的key来判断要用哪个数据源。这得有一个前提,得先建立Connection,之后在获取Connection的时候在判断,
1.通过什么判断呢?存放key和Connection的数据结构是什么?
只要指定一个回调方法,key随便,存放key和Connection的数据结构是Map。在获取Connection的时候,先确定Key,在获取Connection就好了。
再看Spring
Java连接Mysql指定了接口,需要实现DataSource
接口,Spring中提供了抽象类(AbstractDataSource
)
解释说明
DriverManagerDataSource
这个没有什么可说,只是封装了一下连接mysql需要用的一些属性,比如userName,password,url,driver,就是就老一套的获取Connection封装了一下
AbstractRoutingDataSource
Spring提供的动态数据源的抽象类
首先,它里面有一个Map,Map的key是Object(这是自定义的),V是Object(虽然是Object,代码里面规定了,只能为String,和DataSource),其实它本质就应该是DataSource,这里是String的目的是为了可以通过DataSourceLookup
来获取数据源。(比如,可以在配置数据源的时候,直接配置数据源的bean的名字,然后自己写一个DataSourceLookup的实现类,从BeanFactory中获取DataSource)。
还提供了一个determineCurrentLookupKey()
方法来判断当前数据源的key,其实就是map中的key。在最终获取Connection的时候通过当前的key获取DataSource,在通过DataSource获取真正的Connection。
代码分析
1.属性
2.实现接口
实现了InitializingBean那它肯定在afterPropertiesSet
会做操作。等会看看
3.重要方法
afterPropertiesSet
获取Connection之前确定Key
`determineCurrentLookupKey`方法是留给子类拓展的。
determineCurrentLookupKey怎么来确定key呢?
它是一个无参的方法,一般来说,都是放在ThreadLocal中,在执行sql操作之前,在对应的ThreadLocal放这次需要的key,就可以在determineCurrentLookupKey中获取ThreadLocal中的值,确定key。
<font color='red'>Spring提供了一个动态数据源的实现类,`IsolationLevelDataSourceRouter`,key是事务的隔离级别。意思就是可以根据不同的隔离级别来选择DataSource</font>
还有,一般都是用切面来操作ThreadLocal的
例子
我这里为了简单,就不写切面了。直接代码cv。
1.配置类
package datasource.dynamic; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class Config { @Bean public DriverManagerDataSource dataSource1(){ String url = "jdbc:mysql://localhost:3306/t_db1?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; String userName = "root"; String password = "123456"; return new DriverManagerDataSource(url,userName,password); } @Bean public DriverManagerDataSource dataSource2(){ String url = "jdbc:mysql://localhost:3306/t_db2?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; String userName = "root"; String password = "123456"; return new DriverManagerDataSource(url,userName,password); } @Bean public DriverManagerDataSource dataSource3(){ String url = "jdbc:mysql://localhost:3306/t_db3?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; String userName = "root"; String password = "123456"; return new DriverManagerDataSource(url,userName,password); } @Bean public MyDynamicDataSource dynamicDataSource(Map<String,DriverManagerDataSource> map){ HashMap<Object, Object> original = new HashMap<>(map); MyDynamicDataSource myDynamicDataSource = new MyDynamicDataSource(); myDynamicDataSource.setTargetDataSources(original); return myDynamicDataSource; } @Bean public JdbcTemplate jdbcTemplate(AbstractRoutingDataSource dataSource){ return new JdbcTemplate(dataSource); } }
配置了三个数据库,也就是三个数据源,利用JdbcTemplate来快捷的执行sql,JdbcTemplate需要指定用自己定义的动态数据源(MyDynamicDataSource)。
此外,之前说了,动态数据源里面有个map,它得需要数据源,此外还需要key,这里用bean的名字做为key,创建MyDynamicDataSource之后,设置TargetDataSources。当MyDynamicDataSource被Spring创建的时候,InitializingBean#afterPropertiesSet()
会通过DataSourceLookup转换。
2.ThreadLocal
package datasource.dynamic; import org.springframework.util.Assert; public class DataSourceKeyHolder { private static final ThreadLocal<String> keyHolder = new ThreadLocal<>(); public static void setKey(String key) { keyHolder.remove(); keyHolder.set(key); } public static String getKey() { String s = keyHolder.get(); Assert.notNull(s, "key 不能为空"); return s; } public static void clear(){ keyHolder.remove(); } }
3.动态数据源实现类
package datasource.dynamic; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class MyDynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceKeyHolder.getKey(); } }
只是从ThreadLocal中获取当前的key就好了。
4.实体对象
public class TestBean { private Integer id; private String name; private Double age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getAge() { return age; } public void setAge(Double age) { this.age = age; } @Override public String toString() { return "TestBean{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
5.sql
-- auto-generated definition create table t_test ( id int auto_increment primary key, name text null, age double null );
创建三个数据库,在三个数据库里面都创建这个表,填写点数据。
6.主要测试类
package datasource.dynamic; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; public class MainTest { public static void main(String[] args) { try { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); // 这里从头到尾用的都是一个JdbcTemplate dataSource1(jdbcTemplate); // 动态数据源1 dataSource2(jdbcTemplate);// 动态数据源2 dataSource3(jdbcTemplate);// 动态数据源3 }catch (Exception e){ e.printStackTrace(); } } public static void dataSource1( JdbcTemplate jdbcTemplate ){ DataSourceKeyHolder.setKey("dataSource1"); try { TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), 1); System.out.println(testBean); }finally { DataSourceKeyHolder.clear(); } } public static void dataSource2( JdbcTemplate jdbcTemplate ){ DataSourceKeyHolder.setKey("dataSource2"); try { TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), 1); System.out.println(testBean); }finally { DataSourceKeyHolder.clear(); } } public static void dataSource3( JdbcTemplate jdbcTemplate ){ DataSourceKeyHolder.setKey("dataSource3"); try { TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), 1); System.out.println(testBean); }finally { DataSourceKeyHolder.clear(); } } }
7.结果
总结
关于这篇文章,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。