当前位置: 首页 > news >正文

网站开发过程阶段响水哪家专业做网站

网站开发过程阶段,响水哪家专业做网站,网站建设协调会,贵州景点网站建设方案转载自 芋道 Spring Boot 多数据源#xff08;读写分离#xff09;入门 1. 概述 在项目中#xff0c;我们可能会碰到需要多数据源的场景。例如说#xff1a; 读写分离#xff1a;数据库主节点压力比较大#xff0c;需要增加从节点提供读操作#xff0c;以减少压力。 …转载自  芋道 Spring Boot 多数据源读写分离入门 1. 概述 在项目中我们可能会碰到需要多数据源的场景。例如说 读写分离数据库主节点压力比较大需要增加从节点提供读操作以减少压力。 多数据源一个复杂的单体项目因为没有拆分成不同的服务需要连接多个业务的数据源。 本质上读写分离仅仅是多数据源的一个场景从节点是只提供读操作的数据源。所以只要实现了多数据源的功能也就能够提供读写分离。 2. 实现方式 目前实现多数据源有三种方案。我们逐个小节来看。 2.1 方案一 基于 Spring AbstractRoutingDataSource 做拓展。 简单来说通过继承 AbstractRoutingDataSource 抽象类实现一个管理项目中多个 DataSource 的动态 DynamicRoutingDataSource 实现类。这样Spring 在获取数据源时可以通过 DynamicRoutingDataSource 返回实际的 DataSource 。 然后我们可以自定义一个 DS 注解可以添加在 Service 方法、Dao 方法上表示其实际对应的 DataSource 。 如此整个过程就变成执行数据操作时通过“配置”的 DS 注解使用 DynamicRoutingDataSource 获得对应的实际的 DataSource 。之后在通过该 DataSource 获得 Connection 连接最后发起数据库操作。 可能这么说没有实现过多数据源的胖友会比较懵逼比较大概率。所以推荐胖胖看看艿艿的基友写的 《剖析 Spring 多数据源》 文章。 不过呢这种方式在结合 Spring 事务的时候会存在无法切换数据源的问题。具体我们在 「3. baomidou 多数据源」 中结合示例一起来看。 艿艿目前找了一圈开源的项目发现比较好的是 baomidou 提供的 dynamic-datasource-spring-boot-starter 。所以我们在 「3. baomidou 多数据源」 和 「4. baomidou 读写分离」 中会使用到它。 2.2 方案二 不同操作类固定数据源。 关于这个方案解释起来略有点晦涩。以 MyBatis 举例子假设有 orders 和 users 两个数据源。 那么我们可以创建两个 SqlSessionTemplate ordersSqlSessionTemplate 和 usersSqlSessionTemplate 分别使用这两个数据源。 然后配置不同的 Mapper 使用不同的 SqlSessionTemplate 。 如此整个过程就变成执行数据操作时通过 Mapper 可以对应到其  SqlSessionTemplate 使用 SqlSessionTemplate 获得对应的实际的 DataSource 。之后在通过该 DataSource 获得 Connection 连接最后发起数据库操作。 咳咳咳是不是又处于懵逼状态了没事咱在 「5. MyBatis 多数据源」、「6. Spring Data JPA 多数据源」、「7. JdbcTemplate 多数据源」 中结合案例一起看。「Talk is cheap. Show me the code」 不过呢这种方式在结合 Spring 事务的时候也会存在无法切换数据源的问题。淡定淡定。多数据源的情况下这个基本是逃不掉的问题。 2.3 方案三 分库分表中间件。 对于分库分表的中间件会解析我们编写的 SQL 路由操作到对应的数据源。那么它们天然就支持多数据源。如此我们仅需配置好每个表对应的数据源中间件就可以透明的实现多数据源或者读写分离。 目前Java 最好用的分库分表中间件就是 Apache ShardingSphere 没有之一。 那么这种方式在结合 Spring 事务的时候会不会存在无法切换数据源的问题呢答案是不会。在上述的方案一和方案二中在 Spring 事务中会获得对应的 DataSource 再获得 Connection 进行数据库操作。而获得的 Connection 以及其上的事务会通过 ThreadLocal 的方式和当前线程进行绑定。这样就导致我们无法切换数据源。 难道分库分表中间件不也是需要 Connection 进行这些事情么答案是的但是不同的是分库分表中间件返回的 Connection 返回的实际是动态的 DynamicRoutingConnection 它管理了整个请求逻辑过程中使用的所有的 Connection 而最终执行 SQL 的时候DynamicRoutingConnection 会解析 SQL 获得表对应的真正的 Connection 执行 SQL 操作。 难道方案一和方案二不可以这么做吗答案是当然可以。前提是他们要实现解析 SQL 的能力。 那么分库分表中间件就是多数据源的完美方案落从一定程度上来说是的。但是它需要解决多个 Connection 可能产生的多个事务的一致性问题也就是我们常说的分布式事务。关于这块艿艿最近有段时间没跟进 Sharding-JDBC 的版本所以无法给出肯定的答案。不过我相信Sharding-JDBC 最终会解决分布式事务的难题提供透明的多数据源的功能。 在 「8. Sharding-JDBC 多数据源」、「9. Sharding-JDBC 读写分离」 中我们会演示这种方案。 3. baomidou 多数据源 示例代码对应仓库lab-17-dynamic-datasource-baomidou-01 。 本小节我们使用实现开源项目 dynamic-datasource-spring-boot-starter 来实现多数据源的功能。我们会使用 test_orders 和 test_users 两个数据源作为两个数据源然后实现在其上的 SQL 操作。并且会结合在 Spring 事务的不同场景下会发生的结果以及原因。 另外关于 dynamic-datasource-spring-boot-starter 的介绍胖友自己看 官方文档 。 它和 MyBatis-Plus 都是开发者 baomidou 提供的。 3.1 引入依赖 在 pom.xml 文件中引入相关依赖。 ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.1.3.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentmodelVersion4.0.0/modelVersionartifactIdlab-17-dynamic-datasource-baomidou-01/artifactIddependencies!-- 实现对数据库连接池的自动化配置 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependency !-- 本示例我们使用 MySQL --groupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.48/version/dependency!-- 实现对 MyBatis 的自动化配置 --dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.1.1/version/dependency!-- 实现对 dynamic-datasource 的自动化配置 --dependencygroupIdcom.baomidou/groupIdartifactIddynamic-datasource-spring-boot-starter/artifactIdversion2.5.7/version/dependency!-- 不造为啥 dynamic-datasource-spring-boot-starter 会依赖这个 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-actuator/artifactId/dependency!-- 方便等会写单元测试 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies/project具体每个依赖的作用胖友自己认真看下艿艿添加的所有注释噢。 3.2 Application 创建 Application.java 类代码如下 // Application.javaSpringBootApplication MapperScan(basePackages  cn.iocoder.springboot.lab17.dynamicdatasource.mapper) EnableAspectJAutoProxy(exposeProxy  true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html public class Application { }添加 MapperScan 注解cn.iocoder.springboot.lab17.dynamicdatasource.mapper 包路径下就是我们 Mapper 接口所在的包路径。 添加 EnableAspectJAutoProxy 注解重点是配置 exposeProxy true 因为我们希望 Spring AOP 能将当前代理对象设置到 AopContext 中。具体用途我们会在下文看到。想要提前看的胖友可以看看 《Spring AOP 通过获取代理对象实现事务切换》 文章。 3.3 应用配置文件 在 resources 目录下创建 application.yaml 配置文件。配置如下 spring:datasource:# dynamic-datasource-spring-boot-starter 动态数据源的配置内容dynamic:primary: users # 设置默认的数据源或者数据源组默认值即为 masterdatasource:# 订单 orders 数据源配置orders:url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# 用户 users 数据源配置users:url: jdbc:mysql://127.0.0.1:3306/test_users?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# mybatis 配置内容 mybatis:config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径spring.datasource.dynamic 配置项设置 dynamic-datasource-spring-boot-starter 动态数据源的配置内容。 primary 配置项设置默认的数据源或者数据源组默认值即为 master 。 datasource 配置项配置每个动态数据源。这里我们配置了 orders、users两个动态数据源。 mybatis 配置项设置 mybatis-spring-boot-starter MyBatis 的配置内容。 3.4 MyBatis 配置文件 在 resources 目录下创建 mybatis-config.xml 配置文件。配置如下 ?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configurationsettings!-- 使用驼峰命名法转换字段。 --setting namemapUnderscoreToCamelCase valuetrue//settingstypeAliasestypeAlias aliasInteger typejava.lang.Integer/typeAlias aliasLong typejava.lang.Long/typeAlias aliasHashMap typejava.util.HashMap/typeAlias aliasLinkedHashMap typejava.util.LinkedHashMap/typeAlias aliasArrayList typejava.util.ArrayList/typeAlias aliasLinkedList typejava.util.LinkedList//typeAliases/configuration因为在数据库中的表的字段我们是使用下划线风格而数据库实体的字段使用驼峰风格所以通过 mapUnderscoreToCamelCase true 来自动转换。 3.5 实体类 在 cn.iocoder.springboot.lab17.dynamicdatasource.dataobject 包路径下创建 UserDO.java 和 OrderDO.java 类。代码如下 // OrderDO.java /*** 订单 DO*/ public class OrderDO {/*** 订单编号*/private Integer id;/*** 用户编号*/private Integer userId;// 省略 setting/getting 方法}// UserDO.java /*** 用户 DO*/ public class UserDO {/*** 用户编号*/private Integer id;/*** 账号*/private String username;// 省略 setting/getting 方法 }对应的创建表的 SQL 如下 -- 在 test_orders 库中。 CREATE TABLE orders (id int(11) DEFAULT NULL COMMENT 订单编号,user_id int(16) DEFAULT NULL COMMENT 用户编号 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin COMMENT订单表;-- 在 test_users 库中。 CREATE TABLE users (id int(11) NOT NULL AUTO_INCREMENT COMMENT 用户编号,username varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 账号,password varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 密码,create_time datetime DEFAULT NULL COMMENT 创建时间,PRIMARY KEY (id),UNIQUE KEY idx_username (username) ) ENGINEInnoDB AUTO_INCREMENT4 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin;3.6 Mapper 在 cn.iocoder.springboot.lab17.dynamicdatasource.mapper 包路径下创建 UserDO.java 和 UserMapper.java 接口。代码如下 // OrderMapper.java Repository DS(DBConstants.DATASOURCE_ORDERS) public interface OrderMapper {OrderDO selectById(Param(id) Integer id);}// UserMapper.java Repository DS(DBConstants.DATASOURCE_USERS) public interface UserMapper {UserDO selectById(Param(id) Integer id);}DBConstants.java 类枚举了 DATASOURCE_ORDERS 和 DATASOURCE_USERS 两个数据源。 DS 注解是 dynamic-datasource-spring-boot-starter 提供可添加在 Service 或 Mapper 的类/接口上或者方法上。在其 value 属性种填写数据源的名字。 OrderMapper 接口上我们添加了 DS(DBConstants.DATASOURCE_ORDERS) 注解访问 orders 数据源。 UserMapper 接口上我们添加了 DS(DBConstants.DATASOURCE_USERS) 注解访问 users 数据源。 为了让整个测试用例精简我们在 OrderMapper 和 UserMapper 中只添加了根据编号查询单条记录的方法。 在 resources/mapper 路径下创建 OrderMapper.xml 和 UserMapper.xml 配置文件。代码如下 !-- OrderMapper.xml -- ?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecn.iocoder.springboot.lab17.dynamicdatasource.mapper.OrderMappersql idFIELDSid, user_id/sqlselect idselectById parameterTypeInteger resultTypeOrderDOSELECTinclude refidFIELDS /FROM ordersWHERE id  #{id}/select/mapper!-- UserMapper.xml -- ?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecn.iocoder.springboot.lab17.dynamicdatasource.mapper.UserMappersql idFIELDSid, username/sqlselect idselectById parameterTypeInteger resultTypeUserDOSELECTinclude refidFIELDS /FROM usersWHERE id  #{id}/select/mapper3.7 简单测试 创建 UserMapperTest 和 OrderMapperTest 测试类我们来测试一下简单的 UserMapper 和 OrderMapper 的每个操作。代码如下 // OrderMapperTest.java RunWith(SpringRunner.class) SpringBootTest(classes  Application.class) public class OrderMapperTest {Autowiredprivate OrderMapper orderMapper;Testpublic void testSelectById() {OrderDO order  orderMapper.selectById(1);System.out.println(order);}}// UserMapperTest.java RunWith(SpringRunner.class) SpringBootTest(classes  Application.class) public class UserMapperTest {Autowiredprivate UserMapper userMapper;Testpublic void testSelectById() {UserDO user  userMapper.selectById(1);System.out.println(user);}}胖友自己跑下测试用例。如果跑通说明配置就算成功了。 3.8 详细测试 在本小节我们会编写 5 个测试用例尝试阐述 dynamic-datasource-spring-boot-starter 在和 Spring 事务结合碰到的情况以便胖友更好的使用。当然这个不仅仅是 dynamic-datasource-spring-boot-starter 独有的而是方案一【基于 Spring AbstractRoutingDataSource 做拓展】都存在的情况。 在 cn.iocoder.springboot.lab17.dynamicdatasource.service 包路径下创建 OrderService.java 类。代码如下 // OrderService.javaService public class OrderService {Autowiredprivate OrderMapper orderMapper;Autowiredprivate UserMapper userMapper;private OrderService self() {return (OrderService) AopContext.currentProxy();}public void method01() {// ... 省略代码}Transactionalpublic void method02() {// ... 省略代码}public void method03() {// ... 省略代码}public void method04() {// ... 省略代码}TransactionalDS(DBConstants.DATASOURCE_ORDERS)public void method05() {// ... 省略代码}}#self() 方法通过 AopContext 获得自己这个代理对象。举个例子在 #method01() 方法中如果直接使用 this.method02() 方法进行调用因为 this 代表的是 OrderService Bean 自身而不是其 AOP 代理对象。这样会导致无法触发 AOP 的逻辑在此处就是 Spring 事务的逻辑。因此我们通过 AopContext 获得自己这个代理对象。 每一个 #methodXX() 方法都代表一个测试用例胖友可以使用 OrderServiceTest 进行测试。 下面我们来一个一个看。 场景一#method01() // OrderService.javapublic void method01() {// 查询订单OrderDO order  orderMapper.selectById(1);System.out.println(order);// 查询用户UserDO user  userMapper.selectById(1);System.out.println(user); }方法未使用 Transactional 注解不会开启事务。 对于 OrderMapper 和 UserMapper 的查询操作分别使用其接口上的 DS 注解找到对应的数据源执行操作。 这样一看在未开启事务的情况下我们已经能够自由的使用多数据源落。 场景二#method02() // OrderService.javaTransactional public void method02() {// 查询订单OrderDO order  orderMapper.selectById(1);System.out.println(order);// 查询用户UserDO user  userMapper.selectById(1);System.out.println(user); }和 #method01() 方法差异在于方法上增加了 Transactional 注解声明要使用 Spring 事务。 执行方法抛出如下异常 Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table test_users.orders doesnt exist在执行 OrderMapper 查询订单操作时抛出在 test_users 库中不存在 orders表。 这是为什么呢咱不是在 OrderMapper 上声明使用 orders 数据源了么结果为什么会使用 users 数据库路由到 test_users 库上呢。 这里就和 Spring 事务的实现机制有关系。因为方法添加了 Transactional 注解Spring 事务就会生效。此时Spring TransactionInterceptor 会通过 AOP 拦截该方法创建事务。而创建事务势必就会获得数据源。那么TransactionInterceptor 会使用 Spring DataSourceTransactionManager 创建事务并将事务信息通过 ThreadLocal 绑定在当前线程。 而事务信息就包括事务对应的 Connection 连接。那也就意味着还没走到 OrderMapper 的查询操作Connection 就已经被创建出来了。并且因为事务信息会和当前线程绑定在一起在 OrderMapper 在查询操作需要获得 Connection 时就直接拿到当前线程绑定的 Connection 而不是 OrderMapper 添加 DS 注解所对应的 DataSource 所对应的 Connection 。 OK 那么我们现在可以把问题聚焦到 DataSourceTransactionManager 是怎么获取 DataSource 从而获得 Connection 的了。对于每个 DataSourceTransactionManager 数据库事务管理器创建时都会传入其需要管理的 DataSource 数据源。在使用 dynamic-datasource-spring-boot-starter 时它创建了一个 DynamicRoutingDataSource 传入到 DataSourceTransactionManager 中。 而 DynamicRoutingDataSource 负责管理我们配置的多个数据源。例如说本示例中就管理了 orders、users 两个数据源并且默认使用 users 数据源。那么在当前场景下DynamicRoutingDataSource 需要基于 DS 获得数据源名从而获得对应的 DataSource 结果因为我们在 Service 方法上并没有添加 DS 注解所以它只好返回默认数据源也就是 users 。故此就发生了 Table test_users.orders doesnt exist 的异常。 咳咳咳这里涉及 Spring 事务的实现机制如果胖友不是很了解源码会比较懵逼推荐可以尝试将 TransactionInterceptor 作为入口进行调试。当然也欢迎胖友给艿艿留言。 场景三#method03() // OrderService.javapublic void method03() {// 查询订单self().method031();// 查询用户self().method032(); }Transactional // 报错因为此时获取的是 primary 对应的 DataSource 即 users 。 public void method031() {OrderDO order  orderMapper.selectById(1);System.out.println(order); }Transactional public void method032() {UserDO user  userMapper.selectById(1);System.out.println(user); }执行方法抛出如下异常 Table test_users.orders doesnt exist按照艿艿在场景二的解释胖友可以思考下原因。 其实场景三和场景二是等价的。 如果此时我们将 #self() 代码替换成 this 之后诶结果就正常执行。这又是为什么呢胖友在思考一波。 其实这样调整后因为 this 不是代理对象所以 #method031() 和 #method032() 方法上的 Transactional 直接没有作用Spring 事务根本没有生效。所以最终结果和场景一是等价的。 场景四#method04() // OrderService.javapublic void method04() {// 查询订单self().method041();// 查询用户self().method042(); }Transactional DS(DBConstants.DATASOURCE_ORDERS) public void method041() {OrderDO order  orderMapper.selectById(1);System.out.println(order); }Transactional DS(DBConstants.DATASOURCE_USERS) public void method042() {UserDO user  userMapper.selectById(1);System.out.println(user); }和 method03() 方法差异在于#method041() 和 #method042() 方法上添加 DS 注解声明对应使用的 DataSource 。 执行方法正常结束未抛出异常。是不是觉得有点奇怪 在执行 #method041() 方法前因为有 Transactional 注解所以 Spring 事务机制触发。DynamicRoutingDataSource 根据 DS 注解获得对应的 orders 的 DataSource 从而获得 Connection 。所以后续 OrderMapper 执行查询操作时即使使用的是线程绑定的 Connection 也可能不会报错。 嘿嘿实际上此时 OrderMapper 上的 DS 注解也没有作用。 对于 #method042() 也是同理。但是我们上面不是提了 Connection 会绑定在当前线程么那么在 #method042() 方法中应该使用的是 #method041() 的 orders对应的 Connection 呀。在 Spring 事务机制中在一个事务执行完成后会将事务信息和当前线程解绑。所以在执行 #method042() 方法前又可以执行一轮事务的逻辑。 【重要】总的来说对于声明了 Transactional 的 Service 方法上也同时通过 DS 声明对应的数据源。 场景五#method05() // OrderService.javaTransactional DS(DBConstants.DATASOURCE_ORDERS) public void method05() {// 查询订单OrderDO order  orderMapper.selectById(1);System.out.println(order);// 查询用户self().method052(); }Transactional(propagation  Propagation.REQUIRES_NEW) DS(DBConstants.DATASOURCE_USERS) public void method052() {UserDO user  userMapper.selectById(1);System.out.println(user); }和 method04() 方法差异在于我们直接在 #method05() 方法中此时处于一个事务中直接调用了 #method052() 方法。 执行方法正常结束未抛出异常。是不是觉得有点奇怪 我们仔细看看 #method052() 方法我们添加的 Transactionl 注解使用的事务传播级别是 Propagation.REQUIRES_NEW 。此时在执行 #method052() 方法之前TransactionInterceptor 会将原事务挂起暂时性的将原事务信息和当前线程解绑。 所以在执行 #method052() 方法前又可以执行一轮事务的逻辑。 之后在执行 #method052() 方法完成后会将原事务恢复重新将原事务信息和当前线程绑定。 编写这个场景的目的是想告诉胖友如果在使用方案一【基于 Spring AbstractRoutingDataSource 做拓展】在事务中时如何切换数据源。当然一旦切换数据源可能产生多个事务就会碰到多个事务一致性的问题也就是分布式事务。 五个场景胖友在好好理解。可以尝试调试下源码更好的帮助理解。 咳咳咳如果有解释不到位的地方欢迎胖友给艿艿留言。 4. baomidou 读写分离 示例代码对应仓库lab-17-dynamic-datasource-baomidou-02 。 在绝大多数情况下我们使用多数据源的目的是为了实现读写分离。所以在本小节中我们来使用 dynamic-datasource-spring-boot-starter 实现一个读写分离的示例。 4.1 引入依赖 和 「3.1 引入依赖」 一致。 4.2 Application 和 「3.2 Application」 一致。 4.3 应用配置文件 在 resources 目录下创建 application.yaml 配置文件。配置如下 spring:datasource:# dynamic-datasource-spring-boot-starter 动态数据源的配置内容dynamic:primary: master # 设置默认的数据源或者数据源组默认值即为 masterdatasource:# 订单 orders 主库的数据源配置master:url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# 订单 orders 从库数据源配置slave_1:url: jdbc:mysql://127.0.0.1:3306/test_orders_01?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# 订单 orders 从库数据源配置slave_2:url: jdbc:mysql://127.0.0.1:3306/test_orders_02?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# mybatis 配置内容 mybatis:config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径相比 「3.3 应用配置」 来说我们配置了订单库的多个数据源 master 订单库的主库。 slave_1 和 slave_2 订单库的两个从库。 在 dynamic-datasource-spring-boot-starter 中多个相同角色的数据源可以形成一个数据源组。判断标准是数据源名以下划线 _ 分隔后的首部即为组名。例如说slave_1 和  slave_2 形成了 slave 组。 我们可以使用 DS(slave_1) 或 DS(slave_2) 注解明确访问数据源组的指定数据源。 也可以使用 DS(slave) 注解此时会负载均衡选择分组中的某个数据源进行访问。目前负载均衡默认采用轮询的方式。 因为艿艿本地并未搭建 MySQL 一主多从的环境所以是通过创建了 test_orders_01、test_orders_02 库手动模拟作为 test_orders 的从库。 4.4 MyBatis 配置文件 和 「3.4 MyBatis 配置文件」 一致。 4.5 OrderDO 只使用 「3.5 实体类」 的 OrderDO.java 类。 4.6 OrderMapper 在 cn.iocoder.springboot.lab17.dynamicdatasource.mapper 包路径下创建 OrderMapper.java 接口。代码如下 // OrderMapper.javaRepository public interface OrderMapper {DS(DBConstants.DATASOURCE_SLAVE)OrderDO selectById(Param(id) Integer id);DS(DBConstants.DATASOURCE_MASTER)int insert(OrderDO entity);}DBConstants.java 类枚举了 DATASOURCE_MASTER 和 DATASOURCE_SLAVE 两个数据源。 对 #selectById(Integer id) 读操作我们配置了 DS(DBConstants.DATASOURCE_SLAVE) 访问从库。 对 #insert(OrderDO entity) 写操作我们配置了 DS(DBConstants.DATASOURCE_MASTER) 访问主库。 在 resources/mapper 路径下创建 OrderMapper.xml 配置文件。代码如下 ?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecn.iocoder.springboot.lab17.dynamicdatasource.mapper.OrderMappersql idFIELDSid, user_id/sqlselect idselectById parameterTypeInteger resultTypeOrderDOSELECTinclude refidFIELDS /FROM ordersWHERE id  #{id}/selectinsert idinsert parameterTypeOrderDO useGeneratedKeystrue keyPropertyidINSERT INTO orders (user_id) VALUES (#{userId})/insert/mapper3.7 简单测试 创建 UserMapperTest 测试类我们来测试一下简单的 UserMapper 的读写操作。代码如下 // UserMapperTest.javaRunWith(SpringRunner.class) SpringBootTest(classes  Application.class) public class OrderMapperTest {Autowiredprivate OrderMapper orderMapper;Testpublic void testSelectById() {for (int i  0; i  10; i) {OrderDO order  orderMapper.selectById(1);System.out.println(order);}}Testpublic void testInsert() {OrderDO order  new OrderDO();order.setUserId(10);orderMapper.insert(order);}}胖友自己跑下测试用例。如果跑通说明配置就算成功了。 另外在 #testSelectById() 测试方法中艿艿会了看看 slave 分组是不是真的在负载均衡。所以在数据库中分别插入数据如下。 主库[id 1, user_id 1] 从库 01[id 1, user_id 2] 从库 02[id 1, user_id 3]这样通过手动设置相同 id 1 的记录对应不同的 user_id 那么我们就可以观察 #testSelectById() 测试方法的输出结果。如果是user_id 2 和 user_i 3循环输出说明就正常了。 3.8 详细测试 在 cn.iocoder.springboot.lab17.dynamicdatasource.service 包路径下创建 OrderService.java 类。代码如下 // OrderService.javaService public class OrderService {Autowiredprivate OrderMapper orderMapper;TransactionalDS(DBConstants.DATASOURCE_MASTER)public void add(OrderDO order) {// 这里先假模假样的读取一下orderMapper.selectById(order.getId());// 插入订单orderMapper.insert(order);}public OrderDO findById(Integer id) {return orderMapper.selectById(id);}}对于 #add(OrderDO order) 方法我们希望在 Transactional 声明的事务中读操作也访问主库所以声明了 DS(DBConstants.DATASOURCE_MASTER) 。因此后续的所有 OrderMapper 的操作都访问的是订单库的 MASTER 数据源。 对于 #findById(Integer id) 方法读取指定订单信息使用 OrderMapper 的 #selectById(Integer id) 配置的 SLAVE 数据源即可。 创建 OrderServiceTest 测试类测试 OrderService 的读写逻辑。代码如下 // OrderServiceTest.javaRunWith(SpringRunner.class) SpringBootTest(classes  Application.class) public class OrderServiceTest {Autowiredprivate OrderService orderService;Testpublic void testAdd() {OrderDO order  new OrderDO();order.setUserId(20);orderService.add(order);}Testpublic void testFindById() {OrderDO order  orderService.findById(1);System.out.println(order);}}胖友自己跑下测试用例。如果跑通说明配置就算成功了。 另外如果胖友的业务场景是纯的读写分离可以看看 《纯读写分离(mybatis 环境)》 文档。 5. MyBatis 多数据源 示例代码对应仓库lab-17-dynamic-datasource-mybatis 。 本小节我们会基于方案二【不同操作类固定数据源】的方式实现 MyBatis 多数据源。 整个配置过程会相对繁琐胖友请保持耐心。 如果胖友对 Spring Data JPA 不了解的话可以看看 《芋道 Spring Boot MyBatis 入门》》 文章。 5.1 引入依赖 在 pom.xml 文件中引入相关依赖。 ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.1.3.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentmodelVersion4.0.0/modelVersionartifactIdlab-17-dynamic-datasource-mybatis/artifactIddependencies!-- 实现对数据库连接池的自动化配置 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependency !-- 本示例我们使用 MySQL --groupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.48/version/dependency!-- MyBatis 相关依赖 --dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.1.1/version/dependency!-- 保证 Spring AOP 相关的依赖包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactId/dependency!-- 方便等会写单元测试 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies/project具体每个依赖的作用胖友自己认真看下艿艿添加的所有注释噢。 对于 mybatis-spring-boot-starter 依赖这里并不使用它实现对 MyBatis 的自动化配置。这么引入只是单纯方便实际只要引入 mybatis 和 mybatis-spring 即可。 5.2 Application 创建 Application.java 类代码如下 SpringBootApplication EnableAspectJAutoProxy(exposeProxy  true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html public class Application { }我们并没有添加 MapperScan 注解为什么呢?答案我们在 「5.5 配置类」 上看。 5.3 应用配置文件 在 resources 目录下创建 application.yaml 配置文件。配置如下 spring:# datasource 数据源配置内容datasource:# 订单数据源配置orders:jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# 用户数据源配置users:jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# mybatis 配置内容 #mybatis: #  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径 #  type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径在 spring.datasource 配置项中我们设置了 orders 和 users 两个数据源。 注释掉 mybatis 配置项因为我们不使用 mybatis-spring-boot-starter 自动化配置 MyBatis 而是自己写配置类自定义配置 MyBatis 。 5.4 MyBatis 配置文件 和 「3.4 MyBatis 配置文件」 一致。 5.5 MyBatis 配置类 在 cn.iocoder.springboot.lab17.dynamicdatasource.config 包路径下我们会分别创建 MyBatisOrdersConfig 配置类配置使用 orders 数据源的 MyBatis 配置。 MyBatisUsersConfig 配置类配置使用 users 数据源的 MyBatis 配置。 两个 MyBatis 配置类代码是一致的只是部分配置项的值不同。所以我们仅仅来看下 MyBatisOrdersConfig 配置类而 MyBatisUsersConfig 配置类胖友自己看看即可。代码如下 // MyBatisOrdersConfig.javaConfiguration MapperScan(basePackages  cn.iocoder.springboot.lab17.dynamicdatasource.mapper.orders, sqlSessionTemplateRef  ordersSqlSessionTemplate) public class MyBatisOrdersConfig {/*** 创建 orders 数据源*/Bean(name  ordersDataSource)ConfigurationProperties(prefix  spring.datasource.orders)public DataSource dataSource() {return DataSourceBuilder.create().build();}/*** 创建 MyBatis SqlSessionFactory*/Bean(name  ordersSqlSessionFactory)public SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean bean  new SqlSessionFactoryBean();// 2.1 设置 orders 数据源bean.setDataSource(this.dataSource());// 2.2 设置 entity 所在包bean.setTypeAliasesPackage(cn.iocoder.springboot.lab17.dynamicdatasource.dataobject);// 2.3 设置 config 路径bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(classpath:mybatis-config.xml));// 2.4 设置 mapper 路径bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(classpath:mapper/orders/*.xml));return bean.getObject();}/*** 创建 MyBatis SqlSessionTemplate*/Bean(name  ordersSqlSessionTemplate)public SqlSessionTemplate sqlSessionTemplate() throws Exception {return new SqlSessionTemplate(this.sqlSessionFactory());}/*** 创建 orders 数据源的 TransactionManager 事务管理器*/Bean(name  DBConstants.TX_MANAGER_ORDERS)public PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(this.dataSource());}}#dataSource() 方法创建 orders 数据源。 #sqlSessionFactory() 方法创建 MyBatis SqlSessionFactory Bean 。 2.1 处设置 orders 数据源。 2.2 处设置 entity 所在包作为类型别名。 2.3 处设置 config 路径这里我们使用 classpath:mybatis-config.xml配置文件。 2.4 处设置 Mapper 路径这里我们使用 classpath:mapper/orders/*.xml。我们将 resource/mapper 路径下拆分为 orders 路径下的 Mapper XML 用于 orders 数据源users 路径下的 Mapper XML 用于 users 数据源。 通过上述设置我们就创建出使用 orders 数据源的 SqlSessionFactory Bean 对象。 #sqlSessionTemplate() 方法创建 MyBatis SqlSessionTemplate Bean 。其内部的 sqlSessionFactory 使用的就是对应 orders 数据源的 SqlSessionFactory 对象。 在类上有 MapperScan 注解 配置 basePackages 属性它会扫描 cn.iocoder.springboot.lab17.dynamicdatasource.mapper 包下的 orders 包下的 Mapper 接口。和 resource/mapper 路径一样我们也将 mapper 包路径拆分为 orders 包下的 Mapper 接口用于 orders 数据源users 包下的 Mapper 接口用于 users 数据源。 配置 sqlSessionTemplateRef 属性它会使用 #sqlSessionTemplate() 方法创建的 SqlSessionTemplate Bean 对象。 这样我们就能保证 cn.iocoder.springboot.lab17.dynamicdatasource.mapper.orders 下的 Mapper 使用的是操作 orders 数据源的 SqlSessionFactory 从而操作 orders 数据源。 #transactionManager() 方法创建 orders 数据源的 Spring 事务管理器。因为我们项目中一般使用 Spring 管理事务。另外我们在 DBConstants.java 枚举了 TX_MANAGER_ORDERS 和 TX_MANAGER_USERS 两个事务管理器的名字。 艿艿相比来说这种方式会相对繁琐。但是如果项目中大量采用可以封装自己的 Spring Boot Starter 以实现自动化配置。 5.6 实体类 和 「3.5 实体类」 一致。 5.7 Mapper 和 「3.6 Mapper」 基本一致差别在于分出了 orders 和 users 两个。具体看如下两个传送门 Mapper 接口 Mapper XML 5.8 简单测试 创建 UserMapperTest 和 OrderMapperTest 测试类我们来测试一下简单的 UserMapper 和 OrderMapper 的每个操作。代码如下 // OrderMapperTest.java RunWith(SpringRunner.class) SpringBootTest(classes  Application.class) public class OrderMapperTest {Autowiredprivate OrderMapper orderMapper;Testpublic void testSelectById() {OrderDO order  orderMapper.selectById(1);System.out.println(order);}}// UserMapperTest.java RunWith(SpringRunner.class) SpringBootTest(classes  Application.class) public class UserMapperTest {Autowiredprivate UserMapper userMapper;Testpublic void testSelectById() {UserDO user  userMapper.selectById(1);System.out.println(user);}}胖友自己跑下测试用例。如果跑通说明配置就算成功了。 5.9 详细测试 在本小节我们会编写 4 个测试用例尝试方案二【不同操作类固定数据源】存在的情况。 在 cn.iocoder.springboot.lab17.dynamicdatasource.service 包路径下创建 OrderService.java 类。代码如下 // OrderService.javaService public class OrderService {Autowiredprivate OrderMapper orderMapper;Autowiredprivate UserMapper userMapper;private OrderService self() {return (OrderService) AopContext.currentProxy();}public void method01() {// ... 省略代码}Transactional // 报错找不到事务管理器public void method02() {// ... 省略代码}public void method03() {// ... 省略代码}Transactional(transactionManager  DBConstants.TX_MANAGER_ORDERS)public void method05() {// 查询订单OrderDO order  orderMapper.selectById(1);System.out.println(order);// 查询用户self().method052();}}每个测试场景和 「3.8 详细测试」 的测试场景是相对应的按照编号。 每一个 #methodXX() 方法都代表一个测试用例胖友可以使用 OrderServiceTest 进行测试。 下面我们来一个一个看。 场景一#method01() // OrderService.javapublic void method01() {// 查询订单OrderDO order  orderMapper.selectById(1);System.out.println(order);// 查询用户UserDO user  userMapper.selectById(1);System.out.println(user); }方法未使用 Transactional 注解不会开启事务。 对于 OrderMapper 和 UserMapper 的查询操作分别使用其接口对应的 SqlSessionTemplate 找到对应的数据源执行操作。 这样一看在未开启事务的情况下我们已经能够自由的使用多数据源落。 场景二#method02() // OrderService.javaTransactional // 报错找不到事务管理器 public void method02() {// 查询订单OrderDO order  orderMapper.selectById(1);System.out.println(order);// 查询用户UserDO user  userMapper.selectById(1);System.out.println(user); }和 #method02() 方法差异在于方法上增加了 Transactional 注解声明要使用 Spring 事务。 执行方法抛出如下异常 NoUniqueBeanDefinitionException: No qualifying bean of type org.springframework.transaction.PlatformTransactionManager available: expected single matching bean but found 2: ordersTransactionManager,usersTransactionManager在 Transactional 注解上如果未设置使用的事务管理器它会去选择一个事务管理器。但是我们这里创建了 ordersTransactionManager 和 usersTransactionManager 两个事务管理器它就不知道怎么选了。此时它只好抛出 NoUniqueBeanDefinitionException 异常。 场景三#method03() // OrderService.javapublic void method03() {// 查询订单self().method031();// 查询用户self().method032(); }Transactional(transactionManager  DBConstants.TX_MANAGER_ORDERS) public void method031() {OrderDO order  orderMapper.selectById(1);System.out.println(order); }Transactional(transactionManager  DBConstants.TX_MANAGER_USERS) public void method032() {UserDO user  userMapper.selectById(1);System.out.println(user); }执行方法正常结束未抛出异常。 #method031() 和 #method032() 方法上声明的事务管理器和后续 Mapper 操作是同一个 DataSource 数据源从而保证不报错。 场景四#method05() // OrderService.javaTransactional(transactionManager  DBConstants.TX_MANAGER_ORDERS) public void method05() {// 查询订单OrderDO order  orderMapper.selectById(1);System.out.println(order);// 查询用户self().method052(); }Transactional(transactionManager  DBConstants.TX_MANAGER_USERS,propagation  Propagation.REQUIRES_NEW) public void method052() {UserDO user  userMapper.selectById(1);System.out.println(user); }执行方法正常结束未抛出异常。 我们仔细看看 #method052() 方法我们添加的 Transactionl 注解使用的事务传播级别是 Propagation.REQUIRES_NEW 。此时在执行 #method052() 方法之前TransactionInterceptor 会将原事务挂起暂时性的将原事务信息和当前线程解绑。 所以在执行 #method052() 方法前又可以执行一轮事务的逻辑。 之后在执行 #method052() 方法完成后会将原事务恢复重新将原事务信息和当前线程绑定。 编写这个场景的目的是想告诉胖友如果在使用方案二【不同操作类固定数据源】在事务中时如何切换数据源。当然一旦切换数据源可能产生多个事务就会碰到多个事务一致性的问题也就是分布式事务。 四个场景胖友在好好理解。可以尝试调试下源码更好的帮助理解。 咳咳咳如果有解释不到位的地方欢迎胖友给艿艿留言。 5.10 读写分离 按照这个思路如果想要实现 MyBatis 读写分离。还是类似的思路。只是将从库作为一个“特殊”的数据源需要做的是 应用配置文件增加从库的数据源。 增加一套从库的 MyBatis 配置类。 增加一套从库相关的 MyBatis Mapper 接口、Mapper XML 文件。 相比方案一【基于 Spring AbstractRoutingDataSource 做拓展】来说更加麻烦。并且万一有多从呢嘿嘿。 所以呢实际项目在选型时方案一会优于方案二被更普遍的采用。 6. Spring Data JPA 多数据源 示例代码对应仓库lab-17-dynamic-datasource-springdatajpa 。 本小节我们会基于方案二【不同操作类固定数据源】的方式实现 Spring Data JPA 多数据源。 整个配置过程会相对繁琐胖友请保持耐心。 艿艿整个过程和 「5. MyBatis 多数据源」 是类似的所以讲解会想对精简一些。 内心 OS 就是想偷懒嘿嘿。 如果胖友对 Spring Data JPA 不了解的话可以看看 《芋道 Spring Boot JPA 入门》》 文章。 6.1 引入依赖 在 pom.xml 文件中引入相关依赖。 ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.1.3.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentmodelVersion4.0.0/modelVersionartifactIdlab-17-dynamic-datasource-springdatajpa/artifactIddependencies!-- 实现对数据库连接池的自动化配置 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependency !-- 本示例我们使用 MySQL --groupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.48/version/dependency!-- JPA 相关依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependency!-- 方便等会写单元测试 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies/project具体每个依赖的作用胖友自己认真看下艿艿添加的所有注释噢。 对于 spring-boot-starter-data-jpa 依赖这里并不使用它实现对 JPA 的自动化配置。这么引入只是单纯方便不然需要引入 spring-data-jpa 和 hibernate-core等等依赖。 6.2 Application 创建 Application.java 类代码如下 SpringBootApplication EnableAspectJAutoProxy(exposeProxy  true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html public class Application { }6.3 应用配置文件 在 resources 目录下创建 application.yaml 配置文件。配置如下 spring:# datasource 数据源配置内容datasource:# 订单数据源配置orders:jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# 用户数据源配置users:jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:jpa:show-sql: true # 打印 SQL 。生产环境建议关闭# Hibernate 配置内容对应 HibernateProperties 类hibernate:ddl-auto: none在 spring.datasource 配置项中我们设置了 orders 和 users 两个数据源。 6.4 Spring Data JPA 配置类 在 cn.iocoder.springboot.lab17.dynamicdatasource.config 包路径下创建 HibernateConfig.java 配置类。代码如下 // HibernateConfig.javaConfiguration public class HibernateConfig {Autowiredprivate JpaProperties jpaProperties;Autowiredprivate HibernateProperties hibernateProperties;/*** 获取 Hibernate Vendor 相关配置*/Bean(name  hibernateVendorProperties)public MapString, Object hibernateVendorProperties() {return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());}}目的是获得 Hibernate Vendor 相关配置。不用纠结它是什么知道需要获得即可。 在 cn.iocoder.springboot.lab17.dynamicdatasource.config 包路径下我们会分别创建 JpaOrdersConfig 配置类配置使用 orders 数据源的 Spring Data JPA 配置。 JpaUsersConfig 配置类配置使用 users 数据源的 Spring Data JPA 配置。 两个 Spring Data JPA 配置类代码是一致的只是部分配置项的值不同。所以我们仅仅来看下 JpaOrdersConfig 配置类而 JpaUsersConfig 配置类胖友自己看看即可。代码如下 // JpaOrdersConfig.javaConfiguration EnableJpaRepositories(entityManagerFactoryRef  DBConstants.ENTITY_MANAGER_FACTORY_ORDERS,transactionManagerRef  DBConstants.TX_MANAGER_ORDERS,basePackages  {cn.iocoder.springboot.lab17.dynamicdatasource.repository.orders}) // 设置 Repository 接口所在包 public class JpaOrdersConfig {Resource(name  hibernateVendorProperties)private MapString, Object hibernateVendorProperties;/*** 创建 orders 数据源*/Bean(name  ordersDataSource)ConfigurationProperties(prefix  spring.datasource.orders)Primary // 需要特殊添加否则初始化会有问题public DataSource dataSource() {return DataSourceBuilder.create().build();}/*** 创建 LocalContainerEntityManagerFactoryBean*/Bean(name  DBConstants.ENTITY_MANAGER_FACTORY_ORDERS)public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {return builder.dataSource(this.dataSource()) // 数据源.properties(hibernateVendorProperties) // 获取并注入 Hibernate Vendor 相关配置.packages(cn.iocoder.springboot.lab17.dynamicdatasource.dataobject) // 数据库实体 entity 所在包.persistenceUnit(ordersPersistenceUnit) // 设置持久单元的名字需要唯一.build();}/*** 创建 PlatformTransactionManager*/Bean(name  DBConstants.TX_MANAGER_ORDERS)public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {return new JpaTransactionManager(entityManagerFactory(builder).getObject());}}#dataSource() 方法创建 orders 数据源。 #entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder) 方法创建 LocalContainerEntityManagerFactoryBean Bean 它是创建 EntityManager 实体管理器的工厂 Bean 最终会创建对应的 EntityManager Bean 。 2.1 处设置使用的数据源是 orders 。 2.2 处设置 Hibernate Vendor 相关配置。 2.3 处设置数据库实体 Entity 所在包。 2.4 处设置持久单元的名字需要唯一。 #transactionManager(EntityManagerFactoryBuilder builder) 方法创建使用上述 EntityManager 的 JpaTransactionManager Bean 对象。这样该事务管理器使用的也是 orders 数据源。 最终通过 EnableJpaRepositories 注解串联在一起 entityManagerFactoryRef 属性保证了使用 orders 数据源的 EntityManager 实体管理器的工厂 Bean 。 transactionManagerRef 属性保证了使用 orders 数据源的 PlatformTransactionManager 事务管理器 Bean 。 basePackages 属性它会扫描 cn.iocoder.springboot.lab17.dynamicdatasource.repository 包下的 orders 包下的 Repository 接口。我们将 repository 包路径拆分为 orders 包下的 Repository 接口用于 orders 数据源users 包下的 Repository 接口用于 users 数据源。 另外我们在 DBConstants.java 类中枚举了 TX_MANAGER_ORDERS 和 TX_MANAGER_USERS 两个事务管理器的名字方便代码中使用。 ENTITY_MANAGER_FACTORY_ORDERS 和 ENTITY_MANAGER_FACTORY_USERS 两个实体管理器的名字。 艿艿相比来说这种方式会相对繁琐。但是如果项目中大量采用可以封装自己的 Spring Boot Starter 以实现自动化配置。 6.5 实体类 和 「3.5 实体类」 基本一致差别在于增加了 JPA 相关注解。具体看如下两个传送门 OrderDO.java UserDO.java 6.6 Repository 和 「3.6 Mapper」 基本一致差别在于使用 Spring Data Repository 接口。具体看如下两个传送门 OrderRepository UserRepository 6.7 简单测试 和 「5.8 简单测试」 基本一致具体看如下两个传送门 OrderRepositoryTest UserRepositoryTest 6.8 详细测试 和 「5.9 详细测试」 基本一致具体看如下两个传送门 OrderService OrderServiceTest 6.9 读写分离 和 「5.10 读写分离」 思路基本一致。 7. JdbcTemplate 多数据源 示例代码对应仓库lab-17-dynamic-datasource-jdbctemplate 。 本小节我们会基于方案二【不同操作类固定数据源】的方式实现 Spring JdbcTemplate 多数据源。 整个配置过程会相对繁琐胖友请保持耐心。 艿艿整个过程和 「5. MyBatis 多数据源」 是类似的所以讲解会想对精简一些。 内心 OS 我只是想赶紧进入 Sharding-JDBC 的环节真的不是想偷懒哈哈哈哈。 如果胖友对 Spring JdbcTemplate 不了解的话可以看看 《芋道 Spring Boot JdbcTemplate 入门》》 文章。 7.1 引入依赖 在 pom.xml 文件中引入相关依赖。 ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.1.3.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentmodelVersion4.0.0/modelVersionartifactIdlab-17-dynamic-datasource-jdbctemplate/artifactIddependencies!-- 实现对数据库连接池的自动化配置 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependency !-- 本示例我们使用 MySQL --groupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.48/version/dependency!-- 保证 Spring AOP 相关的依赖包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactId/dependency!-- 方便等会写单元测试 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies/project具体每个依赖的作用胖友自己认真看下艿艿添加的所有注释噢。 7.2 Application 创建 Application.java 类代码如下 SpringBootApplication EnableAspectJAutoProxy(exposeProxy  true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html public class Application { }7.3 应用配置文件 在 resources 目录下创建 application.yaml 配置文件。配置如下 spring:# datasource 数据源配置内容datasource:# 订单数据源配置orders:jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# 用户数据源配置users:jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSLfalseuseUnicodetruecharacterEncodingUTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:在 spring.datasource 配置项中我们设置了 orders 和 users 两个数据源。 7.4 JdbcTemplate 配置类 在 cn.iocoder.springboot.lab17.dynamicdatasource.config 包路径下我们会分别创建 JdbcTemplateOrdersConfig 配置类配置使用 orders 数据源的 MyBatis 配置。 JdbcTemplateUsersConfig 配置类配置使用 users 数据源的 MyBatis 配置。 两个 JdbcTemplate 配置类代码是一致的只是部分配置项的值不同。所以我们仅仅来看下 JdbcTemplateOrdersConfig 配置类而 JdbcTemplateUsersConfig 配置类胖友自己看看即可。代码如下 // JdbcTemplateOrdersConfig.javaConfiguration public class JdbcTemplateOrdersConfig {/*** 创建 orders 数据源*/Bean(name  ordersDataSource)ConfigurationProperties(prefix  spring.datasource.orders)public DataSource dataSource() {return DataSourceBuilder.create().build();}/*** 创建 orders JdbcTemplate*/Bean(name  DBConstants.JDBC_TEMPLATE_ORDERS)public JdbcTemplate jdbcTemplate() {return new JdbcTemplate(this.dataSource());}/*** 创建 orders 数据源的 TransactionManager 事务管理器*/Bean(name  DBConstants.TX_MANAGER_ORDERS)public PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(this.dataSource());}}#dataSource() 方法创建 orders 数据源。 #jdbcTemplate() 方法创建使用 orders 数据源的 JdbcTemplate Bean 。 #transactionManager() 方法创建 orders 数据源的 Spring 事务管理器。因为我们项目中一般使用 Spring 管理事务。另外我们在 DBConstants.java 枚举了 TX_MANAGER_ORDERS 和 TX_MANAGER_USERS 两个事务管理器的名字。 艿艿相比来说这种方式会相对繁琐。但是如果项目中大量采用可以封装自己的 Spring Boot Starter 以实现自动化配置。 7.5 实体类 和 「3.5 实体类」 一致。 7.6 Dao 和 「5.8 简单测试」 基本一致具体看如下两个传送门 OrderDao UserDao 7.7 简单测试 和 「5.8 简单测试」 基本一致具体看如下两个传送门 OrderDaoTest UserDaoTest 7.8 详细测试 和 「5.9 详细测试」 基本一致具体看如下两个传送门 OrderService OrderServiceTest 7.9 读写分离 和 「5.10 读写分离」 思路基本一致。 8. Sharding-JDBC 多数据源 示例代码对应仓库lab-17-dynamic-datasource-sharding-jdbc-01 。 Sharding-JDBC 是 Apache ShardingSphere 下基于 JDBC 的分库分表组件。对于 Java 语言来说我们推荐选择 Sharding-JDBC 优于 Sharding-Proxy 主要原因是 减少一层 Proxy 的开销性能更优。 去中心化无需多考虑一次 Proxy 的高可用。 下面我们来使用 Sharding-JDBC 来实现多数据源。整个的示例我们会和 「2. baomidou 多数据源」 是一样的功能方便胖友做类比。 8.1 引入依赖 在 pom.xml 文件中引入相关依赖。 ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.1.3.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentmodelVersion4.0.0/modelVersionartifactIdlab-17-dynamic-datasource-sharding-jdbc-01/artifactIddependencies!-- 实现对数据库连接池的自动化配置 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependency !-- 本示例我们使用 MySQL --groupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.48/version/dependency!-- 实现对 MyBatis 的自动化配置 --dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.1.1/version/dependency!-- 实现对 Sharding-JDBC 的自动化配置 --dependencygroupIdorg.apache.shardingsphere/groupIdartifactIdsharding-jdbc-spring-boot-starter/artifactIdversion4.0.0-RC2/version/dependency!-- 保证 Spring AOP 相关的依赖包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactId/dependency!-- 方便等会写单元测试 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies/project具体每个依赖的作用胖友自己认真看下艿艿添加的所有注释噢。 8.2 Application 创建 Application.java 类代码如下 // Application.javaSpringBootApplication MapperScan(basePackages  cn.iocoder.springboot.lab17.dynamicdatasource.mapper) EnableAspectJAutoProxy(exposeProxy  true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html public class Application { }和 「3.2 Application」 是完全一致的。 8.3 应用配置文件 在 resources 目录下创建 application.yaml 配置文件。配置如下 spring:# ShardingSphere 配置项shardingsphere:datasource:# 所有数据源的名字names: ds-orders, ds-users# 订单 orders 数据源配置ds-orders:type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池driver-class-name: com.mysql.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSLfalseuseUnicodetruecharacterEncodingUTF-8username: rootpassword:# 订单 users 数据源配置ds-users:type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池driver-class-name: com.mysql.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSLfalseuseUnicodetruecharacterEncodingUTF-8username: rootpassword:# 分片规则sharding:tables:# orders 表配置orders:actualDataNodes: ds-orders.orders # 映射到 ds-orders 数据源的 orders 表# users 表配置users:actualDataNodes: ds-users.users # 映射到 ds-users 数据源的 users 表# mybatis 配置内容 mybatis:config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径spring.shardingsphere.datasource 配置项下我们配置了 ds_orders 和 ds_users 两个数据源。 spring.shardingsphere.sharding 配置项下我们配置了分片规则将 orders 逻辑表的操作路由到 ds-orders 数据源的 orders 真实表 将 users 逻辑表的操作路由到 ds-users 数据源的 users 真实表 。 艿艿这里涉及到了一些 ShardingSphere 的概念后续胖友最好可以看看 官方文档 。 mybatis 配置项设置 mybatis-spring-boot-starter MyBatis 的配置内容。 8.4 MyBatis 配置文件 和 「3.4 MyBatis 配置文件」 一致。 8.5 实体类 和 「3.5 实体类」 一致。 8.6 Mapper 和 「3.6 Mapper」 一致。 8.7 简单测试 和 「3.7 简单测试」 一致。 8.8 详细测试 和 「3.8 详细测试」 代码一致结果略有差异。 在 「3.8 详细测试」 的场景二 #method02() 的测试它会抛出异常。而对于本小节使用 Sharding-JDBC 的情况下正常跑通。这是为什么呢 原因实际在 「2.3 方案三」 已经解释了分库分表中间件返回的 Connection 返回的实际是动态的 DynamicRoutingConnection 它管理了整个请求逻辑过程中使用的所有的 Connection 而最终执行 SQL 的时候DynamicRoutingConnection 会解析 SQL 获得表对应的真正的 Connection 执行 SQL 操作。 所以即使在和 Spring 事务结合的时候会通过 ThreadLocal 的方式将 Connection 和当前线程进行绑定。此时这个 Connection 也是一个 动态的 DynamicRoutingConnection 连接。 9. Sharding-JDBC 读写分离 示例代码对应仓库lab-17-dynamic-datasource-sharding-jdbc-02 。 Sharding-JDBC 已经提供了读写分离的支持胖友可以看看如下两个文档 ShardingSphere 概念 功能 读写分离 ShardingSphere 用户手册 Sharding-JDBC 使用手册 读写分离 当然也可以先不看。 下面我们来使用 Sharding-JDBC 来实现读写分离。整个的示例我们会和 「3. baomidou 读写分离」 是一样的功能方便胖友做类比。 9.1 引入依赖 和 「8.1 引入依赖」 一致。 9.2 Application 和 「8.2 Application」 一致。 9.3 应用配置文件 在 resources 目录下创建 application.yaml 配置文件。配置如下 spring:# ShardingSphere 配置项shardingsphere:# 数据源配置datasource:# 所有数据源的名字names: ds-master, ds-slave-1, ds-slave-2# 订单 orders 主库的数据源配置ds-master:type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池driver-class-name: com.mysql.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSLfalseuseUnicodetruecharacterEncodingUTF-8username: rootpassword:# 订单 orders 从库数据源配置ds-slave-1:type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池driver-class-name: com.mysql.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders_01?useSSLfalseuseUnicodetruecharacterEncodingUTF-8username: rootpassword:# 订单 orders 从库数据源配置ds-slave-2:type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池driver-class-name: com.mysql.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders_02?useSSLfalseuseUnicodetruecharacterEncodingUTF-8username: rootpassword:# 读写分离配置对应 YamlMasterSlaveRuleConfiguration 配置类masterslave:name: ms # 名字任意需要保证唯一master-data-source-name: ds-master # 主库数据源slave-data-source-names: ds-slave-1, ds-slave-2 # 从库数据源# mybatis 配置内容 mybatis:config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径spring.shardingsphere.datasource 配置项下我们配置了 一个主数据源 ds-master 、两个从数据源 ds-slave-1、ds-slave-2 。 spring.shardingsphere.masterslave 配置项下配置了读写分离。对于从库来说Sharding-JDBC 提供了多种负载均衡策略默认为轮询。 mybatis 配置项设置 mybatis-spring-boot-starter MyBatis 的配置内容。 因为艿艿本地并未搭建 MySQL 一主多从的环境所以是通过创建了 test_orders_01、test_orders_02 库手动模拟作为 test_orders 的从库。 9.4 MyBatis 配置文件 和 「3.4 MyBatis 配置文件」 一致。 9.5 OrderDO 只使用 「3.5 实体类」 的 OrderDO.java 类。 9.6 OrderMapper 和 「4.6 OrderMapper」 基本一致差别是无需 DS 注解具体看如下两个传送门 OrderMapper OrderMapper.xml 9.7 简单测试 创建 OrderMapperTest 测试类我们来测试一下简单的 OrderMapper 的读写操作。代码如下 // OrderMapper.javaRunWith(SpringRunner.class) SpringBootTest(classes  Application.class) public class OrderMapperTest {Autowiredprivate OrderMapper orderMapper;Testpublic void testSelectById() { // 测试从库的负载均衡for (int i  0; i  10; i) {OrderDO order  orderMapper.selectById(1);System.out.println(order);}}Testpublic void testSelectById02() { // 测试强制访问主库try (HintManager hintManager  HintManager.getInstance()) {// 设置强制访问主库hintManager.setMasterRouteOnly();// 执行查询OrderDO order  orderMapper.selectById(1);System.out.println(order);}}Testpublic void testInsert() { // 插入OrderDO order  new OrderDO();order.setUserId(10);orderMapper.insert(order);}}#testSelectById() 方法测试从库的负载均衡查询。 #testSelectById02() 方法测试强制访问主库。在一些业务场景下对数据延迟敏感所以只能强制读取主库。此时可以使用 HintManager 强制访问主库。 不过要注意在使用完后需要去清理下 HintManager HintManager 是基于线程变量透传给 Sharding-JDBC 的内部实现避免污染下次请求一直强制访问主库。 Sharding-JDBC 比较贴心HintManager 实现了 AutoCloseable 接口可以通过 Try-with-resources 机制自动关闭。 #testInsert() 方法测试主库的插入。 胖友自己跑下测试用例。如果跑通说明配置就算成功了。 另外在 #testSelectById() 测试方法中艿艿会了看看 slave 分组是不是真的在负载均衡。所以在数据库中分别插入数据如下。 主库[id 1, user_id 1] 从库 01[id 1, user_id 2] 从库 02[id 1, user_id 3]这样通过手动设置相同 id 1 的记录对应不同的 user_id 那么我们就可以观察 #testSelectById() 测试方法的输出结果。如果是user_id 2 和 user_i 3循环输出说明就正常了。 9.8 详细测试 在 cn.iocoder.springboot.lab17.dynamicdatasource.service 包路径下创建 OrderService.java 类。代码如下 // OrderService.javaService public class OrderService {Autowiredprivate OrderMapper orderMapper;Transactionalpublic void add(OrderDO order) {// 1.1 这里先假模假样的读取一下。读取从库OrderDO exists  orderMapper.selectById(1);System.out.println(exists);// 1.2 插入订单orderMapper.insert(order);// 1.3 这里先假模假样的读取一下。读取主库exists  orderMapper.selectById(1);System.out.println(exists);}public OrderDO findById(Integer id) {return orderMapper.selectById(id);}}我们创建了 OrderServiceTest 测试类可以测试上面编写的两个方法。 在 #add(OrderDO order) 方法中开启事务插入一条订单记录。 1.1 处往从库发起一次订单查询。在 Sharding-JDBC 的读写分离策略里默认读取从库。 1.2 处往主库发起一次订单写入。写入肯定是操作主库的。 1.3 处往主库发起一次订单查询。在 Sharding-JDBC 中读写分离约定同一线程且同一数据库连接内如有写入操作以后的读操作均从主库读取用于保证数据一致性。 在 #findById(Integer id) 方法往从库发起一次订单查询。 666. 彩蛋 我们看完了三种多数据源的方案实际场景下怎么选择呢 首先我们基本排除了方案二【不同操作类固定数据源】。配置繁琐使用不变。艿艿也去问了一圈朋友暂时没有这么做的。这种方案更加适合不同类型的数据源例如说一个项目中既有 MySQL 数据源又有 MongoDB、Elasticsarch 等其它数据源。 然后对于大多数场景下方案一【基于 SpringAbstractRoutingDataSource 做拓展】基本能够满足。这种方案目前是比较主流的方案大多数项目都采用。在实现上我们可以比较容易的自己封装一套当然也可以考虑使用 dynamic-datasource-spring-boot-starter 开源项目。不过呢建议可以把它的源码撸一下核心代码估计 1000 行左右不要慌。 当然方案一和方案二会存在和 Spring 事务结合的时候在事务中无法切换数据源。这是因为 Spring 事务会将 Connection 和当前线程变量绑定定后续会通过线程变量重用该 Connection 导致无法切换数据源。所以方案一和方案二可以理解成 DataSource 级别上实现的数据源方案。 最后方案三【分库分表中间件】是完美解决方案基本满足了所有的场景。艿艿个人强烈推荐使用 Apache ShardingSphere 的 Sharding-JDBC 组件无论胖友是有多数据源还是分库分表还是读写分离都能完美的匹配。并且Apache ShardingSphere 已经提供多种分布式事务方案也能解决在文章的开头艿艿提到的分布式事务的问题。这种类型的方案目前很多大厂都是这样去玩的。 京东采用 client 模式的读写分离和分库分表。 美团采用 client 模式的读写分离和分库分表。 陌陌采用 client 模式的读写分离和分库分表。 ... 继续补充调研 ing 。 因为本文写的相对匆忙如果有表述不正确或者错误的地方烦请胖友指出。感谢~ 推荐阅读 《芋道 Spring Boot 分库分表入门》 对应 lab-18 。 《Spring MyBatis 实现数据库读写分离方案》
http://www.huolong8.cn/news/433551/

相关文章:

  • 网站建设与推广长春做医美设计的网站
  • 网站ui设计软件做网站应怎么缴税
  • 关于卖零食网站建设需求分析免费网络推广方式
  • 电子商务网站建设的步骤一般为网站友链怎么做
  • 安顺市哪里可以做网站深圳网站建设哪个最好
  • 做网站付多少定金网站正能量
  • wordpress站长邮箱小程序开发注意事项
  • 上海网站开发招聘水滴查企业查询官网
  • 医疗类网站安国手机网站设计
  • 免费自助网站建站赣州网上房地产官网
  • 网站建设好后怎样形成app决定网站打开的速度
  • 为网站制定推广计划中国建筑网官网查询人员证书查
  • seo网站排名优化建行个人网站
  • 怎么看网站是否被收录公司网站建设知识
  • 漳州网站开发制作google搜索中文入口
  • 国内好的seo网站一级建造师报名官网入口
  • 静态网站中切换图片怎么做北京网站开发团队
  • 浙江华企做网站php网站开发人员
  • 哪个网站好设置网站404
  • 上海建筑网站大全网站备案地址查询
  • wap移动建站系统上海市城乡住房建设厅网站
  • 长春市网站优化公司建站宝盒视频
  • 上海市建设工程备案查询网站58重庆网站建设
  • 品牌网站建设哪里好免费的wordpress企业模板
  • 织梦配置手机网站南京企业制作网站
  • 一个人做网站用什么技术重庆双八自助建设网站
  • 织梦网站301重定向wordpress技术教程
  • wap织梦手机网站wordpress中文破解主题
  • 网站自己建机房网站域名注册信息查询
  • 西安哪家网站建设公司好wordpress建站 购物