读完本篇文章你将学到:原生jdbc开发流程,datasource数据源层,多数据源组件原理。以及它们三者之间的关系架构
使用原生jdbc来访问数据库的流程为:
Class.forName(“com.mysql.jdbc.Driver”);
String url = “jdbc:mysql://hostip:3306/test?user=root&password=123456″;
Connection con = DriverManager.getConnection(url);
Statement statement = con.createStatement();
在原生的jdbc访问过程中,重要的领域对象包括:Driver ,DriverManager ,Connection ,Statement 等等。
其实主要与数据库交互的是connection对象,连接的创建会向操作系统申请网络连接资源并占用,直到conneciton对象销毁才还给操作系统,因为每次都要申请并释放操作系统资源,所以原生开发在业务繁忙的项目中资源的释放和申请就会非常频繁。
为了避免这种频繁资源申请和释放情况,DataSource层使用连接池方式解决了这个问题。
DataSource领域模型也是建立在这些原生领域对象之上建立的代理层。
原生jdbc访问过程包括:
- Class.forName加载驱动对象并注册到DriverManager,Driver初始化的静态代码如下
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException(“Can’t register driver!”);
}
}
- DriverManager.getConnection:注册完驱动对象后,随后传入数据库IP地址和登入用户名密码,使用Driver对象创建Connection对象,几段代码如下:
Connection con = aDriver.driver.connect(url, info);
return (con);
- 使用connection对象结合statement和preparestatement等方式执行sql
分析:我们每次执行sql我们都需要去初始化connection,执行完后又释放连接,这个过程中包括锁的金证以及与远程数据库的交互过程,因此每次connection的建立和释放所需要的成本。因此为了降低连接的建立和释放的消耗,这里就需要datasource领域模型来实现。
原生jdbc提到的连接重复建立和释放的成本问题,在datasource领域模型中使用了池技术思想,实现了连接的重复使用,避免了每次使用时都重新建立与数据库之间的连接,会产生较大的系统开销。以空间换时间的方式来提高效率。
datasource的主要API如下
DataSource只有两个方法(确切的说是一个方法的两个重载版本),用于建立与此 DataSource 对象所表示的数据源的连接。
Connection getConnection()
Connection getConnection(String username, String password)
在阿里的DruidDataSource实现类中,我们能看到类的connections连接对象容器。
// store
private volatile DruidConnectionHolder[] connections;
connections连接数组结合datasource的池技术机制实现来管理数据源的连接对象。我们也可以把datasource看作业务程序和DriverManager之间的中间层,这个中间层有管理connection对象功能以及实现事务机制。
看下图,清楚DataSource与原生jdbc的关系架构。
从关系图能看出:
- DataSource是基于jdbc的一层抽象,实现了两个主要功能:维护连接池,实现事务管理。
- jdbc才是真正去与数据库打交道的角色,而与用户程序交互的是DataSource代理层。
3. 多数据源组件AbstractRoutingDataSource 多数据源按字面理解就是 DataSource有多个,用户程序可以自动选择不同的DataSource来为自己的业务服务。因此。
- 多数据源组件具有维护多个DataSource对象的功能。
- 而且数据源的切换动作由用户程序触发。
- 多数据源组件是更高的代理层,直接管理DataSource层。
- datasource的连接池维护这数据库连接,多数据源组件的datasource池维护所有datasource。
根据Java面向对象的思想,用户程序会觉得多数据源组件AbstractRoutingDataSource就是数据源对象,因为多数据源组件实现了DataSource接口,有数据源接口的两个api方法,如下所示:
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
因此,我们可以把多数据源组件看作维护多个数据源的代理层,它们之间的关系如下图所示:
从关系图可以看出:
- 多数据源代理是数据源层中所有数据源对象的代理对象
- 多数据源组件维护一个Map<StringKey,Datasource>数据结构
- 用户线程通过ThreadLocal来传递变量StringKey信息,指定对应的数据源
- 用户通过Object determineCurrentLookupKey()方法来指定StringKey
根据多数据源的关系结构图,可以分析出:只要用户程序根据自己的业务,在执行某项业务时候指定Stringkey,就能做到切换数据源的目的。
因此在读写分离的应用场景中,客户端程序需要识别Mybatis的Dao层执行的方法是读还是写。
Mybatis框架提供了获取方法执行的sql类型的Api
SqlCommandType type = sqlSessionFactory.getConfiguration().getMappedStatement(key).getSqlCommandType();
key的值能通过切面的JoinPoint来获得,这里我不再赘述,大家可以结合项目的RwSeparateDataSourceAspect类代码来看。
这里涉及到Spring容器中的@Aspect切面编程,这里就不对切面开发进行剖析。
有的公司产品的后台管理项目,如果只是给运营使用的话,一般项目都是一个体量比较大的maven聚合项目,在这样的一个项目中,我们就需要去访问不同的产品的数据库,这里我们就要主动的去动态切换数据源。
这种聚合项目也是要进行切面开发的,不像读写分离场景需要在Dao层进行切面开发,这里是基于业务层面进行切面开发,因此我们切点会放在Controller层或者service层。
总而言之,从多数据源的原理和应用来看,多数据源结合了ThreadLcoal传参+切面编程+池技术等多种技术方案。读写分离场景还结合了Mybatis框架的API技术,来判断sql的类型。
因此多数据源分可以简单拆分成两个阶段:用户程序指定Datasource的key;用户程序根据Key来获取对应的connection对象。
执行业务时动态切换数据源
- 准备两个数据库myutilproject0和myutilproject1,并创建user表
create database myutilproject0;
create database myutilproject1;
create table user(
id int(11) not null primary key,
name varchar(100),
gender varchar(10));
- 分别往两个数据库插入数据,执行DynamicDatasourceMethodApplicationTests测试类
测试类执行完成后,两张表中有了数据,分别往两个库插入了数据。
分别插入两个不同库中的表,由下面两个方法完成,不同的业务使用@DBType注解来动态切换数据源
@DBType(DataSourceType.MASTER)
public void insert2Master(User user){
userMapper.insertSelective(user);
}
@DBType(DataSourceType.SLAVE)
public void insert2Slave(User user){
userMapper.insertSelective(user);
}
- 我在这里埋一个坑
看UserService类的几个方法
- select2查询slave库数据
- select1查询master数据并调用select2
- select3查询master数据并调用select2
执行DynamicDatasourceAnnoApplicationTests测试类,分别运行select1和select3。这两个方法调用了select2,打印结果如下
select1
MASTER:11--jackdking-master==male
select2
SLAVE:11--jackdking-master==male
select3
MASTER:11--jackdking-master==male
select2
SLAVE:11--jackdking-slave==male
你会发现: select1调用select2时候,select2切换数据源失败;但是select3调用select1时候,数据源切换生效了。 这个坑大家自己可以思考下,有疑问可来咨询我!
读写分离切换数据源
- 打开读写分离开关
将RwSeparateDataSourceAspect类中的下的pointcut注释去掉。
// @Pointcut("execution(* org.jackdking.core.dao.*.*(..))")
public void declareJoinPoint(){
}
- 运行RwSeparateDataSourceAspect测试类
读写方法执行后,你会发现select方法查出从库数据:11--jackdking-slave==male。而主库写入了一条数据。
至此,读写分离就实现了,注意在测试数据动态源切换的时候一定要把读写注掉,否则会影响动态切换测试。
到这里,文章就学完了,识别下面小程序码,进入本文的技术知识点做题页面,来看看你能掌握多少,满分10分。
查看更多 “Java架构师方案” 系列文章 以及 SpringBoot2.0学习示例
架构方案系列文章
- Java架构师方案——Spring Security(一)快速入门(附完整项目代码):jackdking-springsecurity-one
- Java架构师方案—基于JWT的Token认证(附完整项目代码):jackdking-login-jwt-token
- Java架构师方案—分布式session基于redis的共享机制(附完整项目代码):jackdking-login-redis-token
- Java架构师方案—多数据源开发详解及原理(二)(附完整项目代码):jackdking-shardjdbcyaml
- Java架构师方案—多数据源原理及应用(附完整项目代码):jackdking-muti-datasource
- Java架构师方案—分布式事务2PC方案Atomikos(附完整项目代码):jackdking-JTA-Atomikos
- Java架构师方案—重试机制原理和应用,还有比这个讲的更好的吗(附完整项目代码) :jackdking-retry
- Java架构师方案—透彻理解「状态机」的全套机制 (附完整项目代码) :jackdking-statemachine
- Java架构师方案—状态机(附完整项目代码) :jackdking-statemachine-demo
- Java架构师方案——模拟Spring Security,我徒手写了一个简单的安全框架(附完整项目代码)
Spring Boot2.0 学习示例
介绍
SpringBoot2.0-Jackdking 使用的各种示例,整合流行的中间件,此开源项目中的每个示例都是站在初学者角度,细心剖析技术实现细节,帮助初学者快速掌握 Spring Boot2.0 各组件的使用。
文章
-
SpringBoot学习示例—整合Redis之StringRedisTemplate(附完整项目代码):SpringBoot2.0-Redis-StringRedisTemplate
-
SpringBoot学习示例—Redis多个实例简单的手动配置之整合Lettuce池(附完整项目代码):SpringBoot2.0-Redis-MultilDb
-
SpringBoot学习示例—全面解析RedisTemplate配置及序列化、与StringRedisTemplate序列化区别(附完整项目代码):SpringBoot2.0-Redis-RedisTemplate
还要配置状态转移信息以及事件处理器逻辑的开发。完整的demo项目,请关注公众号“前沿科技bot“并发送"数据源切换"获取。