SpringBoot整合(六)多数据源和 JPA、MyBatis、JdbcTemplate 的集成

news2024/11/16 7:02:09

在springboot项目中,我们可能会碰到需要多数据源的场景。例如说:

  • 读写分离:数据库主节点压力比较大,需要增加从节点提供读操作,以减少压力。
  • 多数据源:一个复杂的单体项目,因为没有拆分成不同的服务,需要连接多个业务的数据源。

本质上,读写分离,仅仅是多数据源的一个场景,从节点是只提供读操作的数据源。所以只要实现了多数据源的功能,也就能够提供读写分离。

实现多数据源大体上有三种方案,下面逐个进行介绍。

一、基于 Spring AbstractRoutingDataSource]做拓展

1.1 概述

简单来说,通过继承 AbstractRoutingDataSource 抽象类,实现一个管理项目中多个 DataSource 的动态 DynamicRoutingDataSource 实现类。这样,Spring 在获取数据源时,可以通过 DynamicRoutingDataSource 返回实际的 DataSource 。

然后,我们可以自定义一个 @DS 注解,可以添加在 Service 方法、Dao 方法上,表示其实际对应的 DataSource 。

如此,整个过程就变成,执行数据操作时,通过配置@DS 注解,使用 DynamicRoutingDataSource 获得对应的实际的 DataSource 。之后,在通过该 DataSource 获得 Connection 连接,最后发起数据库操作。

开源的项目中,比较好的是 baomidou 提供的 dynamic-datasource-spring-boot-starter

1.2 baomidou 多数据源案例详解

使用 test_orderstest_users 两个数据源作为两个数据源,然后实现在其上的 SQL 操作。

1.2.1 pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>boot-start</artifactId>
        <groupId>com.yyds</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>baomidou-multiple-db</artifactId>


    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!-- 实现对 MyBatis 的自动化配置 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!-- 实现对 dynamic-datasource 的自动化配置 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>2.5.7</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>

        <!-- 方便写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--小辣椒-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency> 
    </dependencies>


</project>

1.2.2 yml配置

server:
  port: 6789


spring:
  datasource:
    # dynamic-datasource-spring-boot-starter 动态数据源的配置内容
    dynamic:
      primary: users # 设置默认的数据源或者数据源组,默认值即为 master
      datasource:
        # 订单 orders 数据源配置
        orders:
          url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password: root
        # 用户 users 数据源配置
        users:
          url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password: root

# mybatis 配置内容
mybatis:
  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
  type-aliases-package: com.yyds.springboot.domain # 配置数据库实体包路径

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <!-- 使用驼峰命名法转换字段。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer"/>
        <typeAlias alias="Long" type="java.lang.Long"/>
        <typeAlias alias="HashMap" type="java.util.HashMap"/>
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
        <typeAlias alias="ArrayList" type="java.util.ArrayList"/>
        <typeAlias alias="LinkedList" type="java.util.LinkedList"/>
    </typeAliases>

</configuration>

1.2.3 主启动类

package com.yyds.springboot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@MapperScan(basePackages = "com.yyds.springboot.mapper")
@EnableAspectJAutoProxy(exposeProxy = true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("***********Application***************");
    }
}

1.2.4 业务类

1.2.4.1 mapper层

OrderMapper

package com.yyds.springboot.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.yyds.springboot.commons.DBConstants;
import com.yyds.springboot.domain.OrderDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;


@Repository
@DS(DBConstants.DATASOURCE_ORDERS)
public interface OrderMapper {
    OrderDO selectById(@Param("id") Integer id);
}

UserMapper

package com.yyds.springboot.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.yyds.springboot.commons.DBConstants;
import com.yyds.springboot.domain.UserDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
@DS(DBConstants.DATASOURCE_USERS)
public interface UserMapper {
    UserDO selectById(@Param("id") Integer id);
}
  • @DS注解,是dynamic-datasource-spring-boot-starter提供,可添加在 Service 或 Mapper 的类/接口上,或者方法上。在其

    value属性,填写数据源的名字。

    • OrderMapper 接口上,我们添加了 @DS(DBConstants.DATASOURCE_ORDERS) 注解,访问 orders 数据源。
    • UserMapper 接口上,我们添加了 @DS(DBConstants.DATASOURCE_USERS) 注解,访问 users 数据源。
    package com.yyds.springboot.commons;
    
    public class DBConstants {
        public static final String DATASOURCE_ORDERS = "orders";
        public static final String DATASOURCE_USERS = "users";
    }
    

1.2.4.2 mapper.xml配置

OrderMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyds.springboot.mapper.OrderMapper">

    <sql id="FIELDS">
        id, user_id
    </sql>

    <select id="selectById" parameterType="Integer" resultType="OrderDO">
        SELECT
        <include refid="FIELDS" />
        FROM orders
        WHERE id = #{id}
    </select>

</mapper>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyds.springboot.mapper.UserMapper">

    <sql id="FIELDS">
        id, username
    </sql>

    <select id="selectById" parameterType="Integer" resultType="UserDO">
        SELECT
        <include refid="FIELDS" />
        FROM users
        WHERE id = #{id}
    </select>

</mapper>

1.2.5 实体类

-- 在 `test_orders` 库中。
CREATE TABLE `orders` (
  `id` int(11) DEFAULT NULL COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_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`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

OrderDO

package com.yyds.springboot.domain;

import lombok.Data;

/**
 * 订单 DO
 */
@Data
public class OrderDO {
    /**
     * 订单编号
     */
    private Integer id;
    /**
     * 用户编号
     */
    private Integer userId;

}

UserDO

package com.yyds.springboot.domain;

import lombok.Data;

/**
 * 用户 DO
 */
@Data
public class UserDO {
    /**
     * 用户编号
     */
    private Integer id;
    /**
     * 账号
     */
    private String username;
}

项目结构如下:

在这里插入图片描述

1.2.6 简单测试

package com.yyds;

import com.yyds.springboot.Application;
import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.domain.UserDO;
import com.yyds.springboot.mapper.OrderMapper;
import com.yyds.springboot.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(classes = Application.class)
class ApplicationTests {

    @Autowired
    private OrderMapper orderMapper;

    @Test
    public void testSelectOrderById() {
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
    }

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectUserById() {
        UserDO user = userMapper.selectById(1);
        System.out.println(user);
    }
}

如果跑通,说明配置就算成功了。

1.2.7 详细测试

1.2.7.1 场景一(method01)

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private UserMapper userMapper;

    private OrderService self() {
        return (OrderService) AopContext.currentProxy();
    }

    public void method01() {
        // 查询订单
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
        // 查询用户
        UserDO user = userMapper.selectById(1);
        System.out.println(user);
    }

}
package com.yyds;

import com.yyds.springboot.Application;
import com.yyds.springboot.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(classes = Application.class)
class ApplicationServiceTests {

    @Autowired
    private OrderService orderService;

    @Test
    public void testSelectOrderById() {
       orderService.method01();
    }
}
  • 方法未使用 @Transactional 注解,不会开启事务。
  • 对于 OrderMapper 和 UserMapper 的查询操作,分别使用其接口上的 @DS 注解,找到对应的数据源,执行操作。
  • 在未开启事务的情况下,我们已经能够自由的使用多数据源落。

1.2.7.2 场景二(method02)

	@Transactional
    public void method02() {
        // 查询订单
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
        // 查询用户
        UserDO user = userMapper.selectById(1);
        System.out.println(user);
    }

报错如下:

### SQL: SELECT                   id, user_id               FROM orders         WHERE id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test_users.orders' doesn't exist
; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test_users.orders' doesn't exist

	at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:239)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70)

原因分析:

  • 因为方法添加了 @Transactional 注解,Spring 事务就会生效。此时,Spring TransactionInterceptor 会通过 AOP 拦截该方法,创建事务。而创建事务,势必就会获得数据源。那么,TransactionInterceptor 会使用 Spring DataSourceTransactionManager 创建事务,并将事务信息通过 ThreadLocal 绑定在当前线程。
  • 而事务信息,就包括事务对应的 Connection 连接。那也就意味着,还没走到 OrderMapper 的查询操作,Connection 就已经被创建出来了。并且,因为事务信息会和当前线程绑定在一起,在 OrderMapper 在查询操作需要获得 Connection 时,就直接拿到当前线程绑定的 Connection ,而不是 OrderMapper 添加 @DS 注解所对应的 DataSource 所对应的 Connection 。
  • 对于每个 DataSourceTransactionManager 数据库事务管理器,创建时都会传入其需要管理的 DataSource 数据源。在使用 dynamic-datasource-spring-boot-starter 时,它创建了一个 DynamicRoutingDataSource ,传入到 DataSourceTransactionManager 中。
  • 而 DynamicRoutingDataSource 负责管理我们配置的多个数据源。例如说,本示例中就管理了 ordersusers 两个数据源,并且默认使用 users 数据源。那么在当前场景下,DynamicRoutingDataSource 需要基于 @DS 获得数据源名,从而获得对应的 DataSource ,结果因为我们在 Service 方法上,并没有添加 @DS 注解,所以它只好返回默认数据源,也就是 users 。故此,就发生了 Table 'test_users.orders' doesn't exist 的异常。

1.2.7.3 场景三(method03)

 public 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);
    }

场景三和场景二是等价的。

#self() 代码替换成 this 之后,诶,结果就正常执行。这又是为什么呢?胖友在思考一波。

其实,这样调整后,因为 this 不是代理对象,所以 #method031()#method032() 方法上的 @Transactional 直接没有作用,Spring 事务根本没有生效。所以,最终结果和场景一是等价的。

1.2.7.4 场景四(method04)

    public 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);
    }

执行方法,正常结束,未抛出异常。

  • 在执行 #method041() 方法前,因为有 @Transactional 注解,所以 Spring 事务机制触发。DynamicRoutingDataSource 根据 @DS 注解,获得对应的 orders 的 DataSource ,从而获得 Connection 。所以后续 OrderMapper 执行查询操作时,即使使用的是线程绑定的 Connection ,也可能不会报错。实际上,此时 OrderMapper 上的 @DS 注解,也没有作用。
  • 对于 #method042() ,也是同理。在 Spring 事务机制中,在一个事务执行完成后,会将事务信息和当前线程解绑。所以,在执行 #method042() 方法前,又可以执行一轮事务的逻辑。
  • 【重要】总的来说,对于声明了 @Transactional 的 Service 方法上,也同时通过 @DS 声明对应的数据源。

1.2.7.5 场景五(method05)

 	@Transactional
    @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() 方法。
  • 执行方法,正常结束,未抛出异常。
  • 添加的 @Transactionl 注解,使用的事务传播级别是 Propagation.REQUIRES_NEW 。此时,在执行 #method052() 方法之前,TransactionInterceptor 会将原事务挂起,暂时性的将原事务信息和当前线程解绑。
    • 所以,在执行 #method052() 方法前,又可以执行一轮事务的逻辑。
    • 之后,在执行 #method052() 方法完成后,会将原事务恢复,重新将原事务信息和当前线程绑定。

总结:【基于 Spring AbstractRoutingDataSource 做拓展,切换数据源,可能产生多个事务,就会碰到多个事务一致性的问题,也就是分布式事务。】

1.3 baomidou读写分离案例详解

1.3.1 pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>boot-start</artifactId>
        <groupId>com.yyds</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>baomidou-rw</artifactId>

    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!-- 实现对 MyBatis 的自动化配置 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!-- 实现对 dynamic-datasource 的自动化配置 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>2.5.7</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--小辣椒-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

</project>

1.3.2 yml配置

server:
  port: 6789


spring:
  datasource:
    # dynamic-datasource-spring-boot-starter 动态数据源的配置内容
    dynamic:
      primary: master # 设置默认的数据源或者数据源组,默认值即为 master
      datasource:
        # 订单 orders 主库的数据源配置
        master:
          url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password: root
        # 订单 orders 从库数据源配置(模拟)
        slave:
          url: jdbc:mysql://127.0.0.1:3306/test_orders_01?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password: root

# mybatis 配置内容
mybatis:
  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
  type-aliases-package: com.yyds.springboot.domain # 配置数据库实体包路径
  • 我们配置了订单库的多个数据源:

    • master :订单库的主库。
    • slave:订单库的1个从库。
  • 在dynamic-datasource-spring-boot-starter中,多个相同角色的数据源可以形成一个数据源组。

    例如说,slave_1和slave_2形成了slave组。

    • 我们可以使用 @DS("slave_1")@DS("slave_2") 注解,明确访问数据源组的指定数据源。
    • 也可以使用 @DS("slave") 注解,此时会负载均衡,选择分组中的某个数据源进行访问。负载均衡默认采用轮询的方式。
  • 本地并未搭建 MySQL 一主多从的环境,所以是通过创建了 test_orders_01库,手动模拟作为 test_orders 的从库。

1.3.3 主启动类

package com.yyds.springboot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@MapperScan(basePackages = "com.yyds.springboot.mapper")
@EnableAspectJAutoProxy(exposeProxy = true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("***********Application***************");
    }
}

1.3.4 业务类

package com.yyds.springboot.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.yyds.springboot.commons.DBConstants;
import com.yyds.springboot.domain.OrderDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;


@Repository
@DS(DBConstants.DATASOURCE_ORDERS)
public interface OrderMapper {
    @DS(DBConstants.DATASOURCE_SLAVE)
    OrderDO selectById(@Param("id") Integer id);

    @DS(DBConstants.DATASOURCE_MASTER)
    int insert(OrderDO entity);
}
  • #selectById(Integer id) 读操作,我们配置了 @DS(DBConstants.DATASOURCE_SLAVE) ,访问从库。
  • #insert(OrderDO entity) 写操作,我们配置了 @DS(DBConstants.DATASOURCE_MASTER) ,访问主库。

OrderMapper.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyds.springboot.mapper.OrderMapper">

    <sql id="FIELDS">
        id, user_id
    </sql>

    <select id="selectById" parameterType="Integer" resultType="OrderDO">
        SELECT
        <include refid="FIELDS" />
        FROM orders
        WHERE id = #{id}
    </select>


    <insert id="insert" parameterType="OrderDO" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO orders (
            user_id
        ) VALUES (
                     #{userId}
                 )
    </insert>

</mapper>

mybatis-config.xml配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <!-- 使用驼峰命名法转换字段。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer"/>
        <typeAlias alias="Long" type="java.lang.Long"/>
        <typeAlias alias="HashMap" type="java.util.HashMap"/>
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
        <typeAlias alias="ArrayList" type="java.util.ArrayList"/>
        <typeAlias alias="LinkedList" type="java.util.LinkedList"/>
    </typeAliases>

</configuration>

1.3.5 实体类

package com.yyds.springboot.domain;


import lombok.Data;


/**
 * 订单 DO
 */
@Data
public class OrderDO {

    /**
     * 订单编号
     */
    private Integer id;
    /**
     * 用户编号
     */
    private Integer userId;

}



package com.yyds.springboot.commons;

public class DBConstants {
    public static final String DATASOURCE_MASTER = "master";
    public static final String DATASOURCE_SLAVE = "slave";
    public static final String DATASOURCE_ORDERS = "orders";
}

1.3.6 简单测试

package com.yyds.springboot;

import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(classes = Application.class)
public class OrderMapperTest {
    @Autowired
    private OrderMapper orderMapper;

    @Test
    public void testSelectById() {
        for (int i = 0; i < 10; i++) {
            OrderDO order = orderMapper.selectById(1);
            System.out.println(order);
        }
    }

    @Test
    public void testInsert() {
        OrderDO order = new OrderDO();
        order.setUserId(10);
        orderMapper.insert(order);
    }
}

如果跑通,说明配置就算成功了。

1.3.7 详细测试

service层

package com.yyds.springboot.service;


import com.baomidou.dynamic.datasource.annotation.DS;
import com.yyds.springboot.commons.DBConstants;
import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;


    @Transactional
    @DS(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 数据源即可。

测试

package com.yyds.springboot;

import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest(classes = Application.class)
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    public void testAdd() {
        OrderDO order = new OrderDO();
        order.setUserId(20);
        orderService.add(order);
    }

    @Test
    public void testFindById() {
        OrderDO order = orderService.findById(1);
        System.out.println(order);
    }
}

如果跑通,说明配置就算成功了。

二、不同操作类,固定数据源

以 MyBatis 举例子,假设有 ordersusers 两个数据源。 那么我们可以创建两个 SqlSessionTemplate ordersSqlSessionTemplateusersSqlSessionTemplate ,分别使用这两个数据源。然后,配置不同的 Mapper 使用不同的 SqlSessionTemplate 。

如此,整个过程就变成,执行数据操作时,通过 Mapper 可以对应到其 SqlSessionTemplate ,使用 SqlSessionTemplate 获得对应的实际的 DataSource 。之后,在通过该 DataSource 获得 Connection 连接,最后发起数据库操作。

这种方式在结合 Spring 事务的时候,也会存在无法切换数据源的问题。

2.1 mybatis多数据源案例详解

2.1.1 pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>boot-start</artifactId>
        <groupId>com.yyds</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mybatis-multiple-db</artifactId>


    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

        <!-- MyBatis 相关依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!-- 保证 Spring AOP 相关的依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>


</project>

2.1.2 yml配置

spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root
    # 用户数据源配置
    users:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root


# 注意此时并不使用它实现对 MyBatis 的自动化配置。这么引入,只是单纯方便,实际只要引入 mybatis 和 mybatis-spring 即可。

2.1.3 主启动类

package com.yyds.springboot;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("***********Application***************");
    }

}

2.1.4 业务类

package com.yyds.springboot.mapper.orders;

import com.yyds.springboot.domain.OrderDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;


@Repository
public interface OrderMapper {
    OrderDO selectById(@Param("id") Integer id);
}

package com.yyds.springboot.mapper.users;


import com.yyds.springboot.domain.UserDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper {

    UserDO selectById(@Param("id") Integer id);
}

2.1.5 实体类及配置类

实体类和之前一样。

MyBatisOrdersConfig

package com.yyds.springboot.config;

import com.yyds.springboot.commons.DBConstants;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.yyds.springboot.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("com.yyds.springboot.domain");
        // <2.3> 设置 config 路径
        bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
        // <2.4> 设置 mapper 路径
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.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());
    }

}

MyBatisUsersConfig和MyBatisOrdersConfig基本一致。

在这里插入图片描述

2.1.6 简单测试

package com.yyds.springboot;

import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.domain.UserDO;
import com.yyds.springboot.mapper.orders.OrderMapper;
import com.yyds.springboot.mapper.users.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(classes = Application.class)
public class MapperTest {


    @Autowired
    private OrderMapper orderMapper;

    @Test
    public void testSelectById() {
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
    }



    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectByUserId() {
        UserDO user = userMapper.selectById(1);
        System.out.println(user);
    }
}

如果跑通,说明配置就算成功了。

2.1.7 详细测试

2.1.7.1 场景一(method01)

package com.yyds.springboot.service;

import com.yyds.springboot.commons.DBConstants;
import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.domain.UserDO;
import com.yyds.springboot.mapper.orders.OrderMapper;
import com.yyds.springboot.mapper.users.UserMapper;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private UserMapper userMapper;

    private OrderService self() {
        return (OrderService) AopContext.currentProxy();
    }

    public void method01() {
        // 查询订单
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
        // 查询用户
        UserDO user = userMapper.selectById(1);
        System.out.println(user);
    }
}

  • 方法未使用 @Transactional 注解,不会开启事务。
  • 对于 OrderMapper 和 UserMapper 的查询操作,分别使用其接口对应的 SqlSessionTemplate ,找到对应的数据源,执行操作。

2.1.7.2 场景二(method02)

    @Transactional // 报错,找不到事务管理器
    public void method02() {
        // 查询订单
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
        // 查询用户
        UserDO user = userMapper.selectById(1);
        System.out.println(user);
    }

执行方法,抛出异常:

  • @Transactional 注解上,如果未设置使用的事务管理器,它会去选择一个事务管理器。但是,我们这里创建了 ordersTransactionManagerusersTransactionManager 两个事务管理器,它就不知道怎么选了。此时,它只好抛出 NoUniqueBeanDefinitionException 异常。

2.1.7.3 场景三(method03)

   public 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 数据源,从而保证不报错。

2.1.7.4 场景四(method04)

@Transactional(transactionManager = DBConstants.TX_MANAGER_ORDERS)
    public void method04() {
        // 查询订单
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
        // 查询用户
        self().method041();
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_USERS,
            propagation = Propagation.REQUIRES_NEW)
    public void method041() {
        UserDO user = userMapper.selectById(1);
        System.out.println(user);
    }
  • 执行方法,正常结束,未抛出异常。
    • 所以,在执行 #method041() 方法前,又可以执行一轮事务的逻辑。
    • 之后,在执行 #method041() 方法完成后,会将原事务恢复,重新将原事务信息和当前线程绑定。

2.2 Spring Data JPA 多数据源案例详解

2.2.1 pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>boot-start</artifactId>
        <groupId>com.yyds</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jpa-multiple-db</artifactId>

    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

        <!-- JPA 相关依赖 -->
        <!--对于 spring-boot-starter-data-jpa 依赖,这里并不使用它实现对 JPA 的自动化配置。
        这么引入,只是单纯方便,不然需要引入 spring-data-jpa 和 hibernate-core 等依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--小辣椒-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

</project>

2.2.2 yml配置

spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root
    # 用户数据源配置
    users:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root
  jpa:
    show-sql: true # 打印 SQL
    # Hibernate 配置内容,对应 HibernateProperties 类
    hibernate:
      ddl-auto: none

2.2.3 主启动类

package com.yyds.springboot;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("***********Application***************");
    }

}

2.2.4 业务类

OrderRepository

package com.yyds.springboot.repository.orders;


import com.yyds.springboot.domain.OrderDO;
import org.springframework.data.repository.CrudRepository;

public interface OrderRepository extends CrudRepository<OrderDO, Integer> {

}

UserRepository

package com.yyds.springboot.repository.users;



import com.yyds.springboot.domain.UserDO;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<UserDO, Integer> {

}

2.2.5 实体类及配置类

OrderDO

package com.yyds.springboot.domain;


import lombok.Data;

/**
 * 订单 DO
 */
import javax.persistence.*;

@Entity
@Table(name = "orders")
@Data
public class OrderDO {

    /**
     * 订单编号
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY,  // strategy 设置使用数据库主键自增策略;
            generator = "JDBC") // generator 设置插入完成后,查询最后生成的 ID 填充到该属性中。
    private Integer id;
    /**
     * 用户编号
     */
    @Column(name = "user_id")
    private Integer userId;

}

UserDO

package com.yyds.springboot.domain;

import lombok.Data;

import javax.persistence.*;

/**
 * 用户 DO
 */
@Data
@Entity
@Table(name = "users")
public class UserDO {

    /**
     * 用户编号
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY,  // strategy 设置使用数据库主键自增策略;
            generator = "JDBC") // generator 设置插入完成后,查询最后生成的 ID 填充到该属性中。
    private Integer id;
    /**
     * 账号
     */
    private String username;
}

DBConstants

package com.yyds.springboot.common;

public class DBConstants {
    /**
     * 事务管理器 - 订单库
     */
    public static final String TX_MANAGER_ORDERS = "ordersTransactionManager";
    /**
     * 事务管理器 - 用户库
     */
    public static final String TX_MANAGER_USERS = "usersTransactionManager";

    /**
     * 实体管理器工厂 - 订单库
     */
    public static final String ENTITY_MANAGER_FACTORY_ORDERS = "ordersEntityManagerFactory";
    /**
     * 实体管理器工厂 - 用户库
     */
    public static final String ENTITY_MANAGER_FACTORY_USERS = "usersEntityManagerFactory";
}

HibernateConfig

package com.yyds.springboot.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.Map;

@Configuration
public class HibernateConfig {

    @Resource
    private JpaProperties jpaProperties;

    @Resource
    private HibernateProperties hibernateProperties;

    /**
     * 获取 Hibernate Vendor 相关配置
     */
    @Bean(name = "hibernateVendorProperties")
    public Map<String, Object> hibernateVendorProperties() {
        return hibernateProperties.determineHibernateProperties(
                jpaProperties.getProperties(), new HibernateSettings()
        );
    }

}

JpaOrdersConfig

  • JpaOrdersConfig 配置类,配置使用 orders 数据源的 Spring Data JPA 配置。
  • JpaUsersConfig 配置类,配置使用 users 数据源的 Spring Data JPA 配置。
package com.yyds.springboot.config;


import com.yyds.springboot.common.DBConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = DBConstants.ENTITY_MANAGER_FACTORY_ORDERS,
        transactionManagerRef = DBConstants.TX_MANAGER_ORDERS,
        basePackages = {"com.yyds.springboot.repository.orders"}) // 设置接口所在包
public class JpaOrdersConfig {

    @Resource(name = "hibernateVendorProperties")
    private Map<String, 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("com.yyds.springboot.domain") // 数据库实体 domain 所在包
                .persistenceUnit("ordersPersistenceUnit") // 设置持久单元的名字,需要唯一
                .build();
    }

    /**
     * 创建 PlatformTransactionManager
     */
    @Bean(name = DBConstants.TX_MANAGER_ORDERS)
    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactory(builder).getObject());
    }

}

2.2.6 简单测试

package com.yyds.springboot;

import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.domain.UserDO;
import com.yyds.springboot.repository.orders.OrderRepository;
import com.yyds.springboot.repository.users.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

@SpringBootTest(classes = Application.class)
public class MapperTest {


    @Autowired
    private OrderRepository orderRepository;

    @Test
    public void testSelectById() {
        Optional<OrderDO> orderDO = orderRepository.findById(1);
        System.out.println(orderDO.get());
    }



    @Autowired
    private UserRepository userRepository;

    @Test
    public void testSelectByUserId() {
        Optional<UserDO> userDO = userRepository.findById(1);
        System.out.println(userDO.get());
    }


}

2.2.7 详细测试

package com.yyds.springboot.service;

import com.yyds.springboot.common.DBConstants;
import com.yyds.springboot.domain.OrderDO;
import com.yyds.springboot.domain.UserDO;
import com.yyds.springboot.repository.orders.OrderRepository;
import com.yyds.springboot.repository.users.UserRepository;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private UserRepository userRepository;

    private OrderService self() {
        return (OrderService) AopContext.currentProxy();
    }

    public void method01() {
        // 查询订单
        OrderDO order = orderRepository.findById(1).orElse(null);
        System.out.println(order);
        // 查询用户
        UserDO user = userRepository.findById(1).orElse(null);
        System.out.println(user);
    }

    @Transactional // 报错,找不到事务管理器
    public void method02() {
        // 查询订单
        OrderDO order = orderRepository.findById(1).orElse(null);
        System.out.println(order);
        // 查询用户
        UserDO user = userRepository.findById(1).orElse(null);
        System.out.println(user);
    }

    public void method03() {
        // 查询订单
        self().method031();
        // 查询用户
        self().method032();
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_ORDERS)
    public void method031() {
        OrderDO order = orderRepository.findById(1).orElse(null);
        System.out.println(order);
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_USERS)
    public void method032() {
        UserDO user = userRepository.findById(1).orElse(null);
        System.out.println(user);
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_ORDERS)
    public void method04() {
        // 查询订单
        OrderDO order = orderRepository.findById(1).orElse(null);
        System.out.println(order);
        // 查询用户
        self().method041();
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_USERS,
            propagation = Propagation.REQUIRES_NEW)
    public void method041() {
        UserDO user = userRepository.findById(1).orElse(null);
        System.out.println(user);
    }
}

2.3 JdbcTemplate 多数据源案例详解

2.3.1 pom文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>boot-start</artifactId>
        <groupId>com.yyds</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jdbctemplate-multiple-db</artifactId>


    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

        <!-- 保证 Spring AOP 相关的依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

2.3.2 yml配置

spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root
    # 用户数据源配置
    users:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root

2.3.3 主启动类

package com.yyds.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("***********Application***************");
    }
}

2.3.4 业务类

dao层

package com.yyds.springboot.dao;


import com.yyds.springboot.constant.DBConstants;
import com.yyds.springboot.dataobject.OrderDO;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository
public class OrderDao {

    @Resource(name = DBConstants.JDBC_TEMPLATE_ORDERS)
    private JdbcTemplate template;

    public OrderDO selectById(Integer id) {
        return template.queryForObject("SELECT id, user_id FROM orders WHERE id = ?",
                new BeanPropertyRowMapper<>(OrderDO.class), // 结果转换成对应的对象
                id);
    }

}

package com.yyds.springboot.dao;


import com.yyds.springboot.constant.DBConstants;
import com.yyds.springboot.dataobject.UserDO;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository
public class UserDao {

    @Resource(name = DBConstants.JDBC_TEMPLATE_USERS)
    private JdbcTemplate template;

    public UserDO selectById(Integer id) {
        return template.queryForObject("SELECT id, username FROM users WHERE id = ?",
                new BeanPropertyRowMapper<>(UserDO.class), // 结果转换成对应的对象
                id);
    }

}

2.3.5 实体类及配置类

package com.yyds.springboot.constant;

/**
 * 数据库枚举类
 */
public class DBConstants {

    /**
     * 事务管理器 - 订单库
     */
    public static final String TX_MANAGER_ORDERS = "ordersTransactionManager";
    /**
     * 事务管理器 - 用户库
     */
    public static final String TX_MANAGER_USERS = "usersTransactionManager";

    /**
     * JdbcTemplate - 订单库
     */
    public static final String JDBC_TEMPLATE_ORDERS = "ordersJdbcTemplate";
    /**
     * JdbcTemplate - 用户库
     */
    public static final String JDBC_TEMPLATE_USERS = "usersJdbcTemplate";

}

  • JdbcTemplateOrdersConfig 配置类,配置使用 orders 数据源的 MyBatis 配置。
  • JdbcTemplateUsersConfig配置类,配置使用 users 数据源的 MyBatis 配置。
package com.yyds.springboot.config;


import com.yyds.springboot.constant.DBConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
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());
    }

}
package com.yyds.springboot.config;


import com.yyds.springboot.constant.DBConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
public class JdbcTemplateUsersConfig {

    /**
     * 创建 users 数据源
     */
    @Bean(name = "usersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.users")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建 users JdbcTemplate
     */
    @Bean(name = DBConstants.JDBC_TEMPLATE_USERS)
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(this.dataSource());
    }

    /**
     * 创建 users 数据源的 TransactionManager 事务管理器
     */
    @Bean(name = DBConstants.TX_MANAGER_USERS)
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(this.dataSource());
    }

}

2.3.6 简单测试

package com.yyds.springboot;


import com.yyds.springboot.dao.OrderDao;
import com.yyds.springboot.dao.UserDao;
import com.yyds.springboot.dataobject.OrderDO;
import com.yyds.springboot.dataobject.UserDO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;



@SpringBootTest(classes = Application.class)
public class DaoTest {

    @Autowired
    private OrderDao orderDao;

    @Test
    public void testSelectById() {
        OrderDO order = orderDao.selectById(1);
        System.out.println(order);
    }

    @Autowired
    private UserDao userDao;

    @Test
    public void testSelectByUserId() {
        UserDO user = userDao.selectById(1);
        System.out.println(user);
    }

}

2.3.7 详细测试

package com.yyds.springboot.service;

import com.yyds.springboot.constant.DBConstants;
import com.yyds.springboot.dao.OrderDao;
import com.yyds.springboot.dao.UserDao;
import com.yyds.springboot.dataobject.OrderDO;
import com.yyds.springboot.dataobject.UserDO;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private OrderDao orderDao;
    @Autowired
    private UserDao userDao;

    private OrderService self() {
        return (OrderService) AopContext.currentProxy();
    }

    public void method01() {
        // 查询订单
        OrderDO order = orderDao.selectById(1);
        System.out.println(order);
        // 查询用户
        UserDO user = userDao.selectById(1);
        System.out.println(user);
    }

    @Transactional // 报错,找不到事务管理器
    public void method02() {
        // 查询订单
        OrderDO order = orderDao.selectById(1);
        System.out.println(order);
        // 查询用户
        UserDO user = userDao.selectById(1);
        System.out.println(user);
    }

    public void method03() {
        // 查询订单
        self().method031();
        // 查询用户
        self().method032();
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_ORDERS)
    public void method031() {
        OrderDO order = orderDao.selectById(1);
        System.out.println(order);
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_USERS)
    public void method032() {
        UserDO user = userDao.selectById(1);
        System.out.println(user);
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_ORDERS)
    public void method04() {
        // 查询订单
        OrderDO order = orderDao.selectById(1);
        System.out.println(order);
        // 查询用户
        self().method041();
    }

    @Transactional(transactionManager = DBConstants.TX_MANAGER_USERS,
            propagation = Propagation.REQUIRES_NEW)
    public void method041() {
        UserDO user = userDao.selectById(1);
        System.out.println(user);
    }

}

三、分库分表中间件

对于分库分表的中间件,会解析我们编写的 SQL ,路由操作到对应的数据源。那么,它们天然就支持多数据源。如此,我们仅需配置好每个表对应的数据源,中间件就可以透明的实现多数据源或者读写分离。

目前,Java 最好用的分库分表中间件之一,就是 Apache ShardingSphere 。

那么,这种方式在结合 Spring 事务的时候,会不会存在无法切换数据源的问题呢?答案是不会。在上述的方案一和方案二中,在 Spring 事务中,会获得对应的 DataSource ,再获得 Connection 进行数据库操作。而获得的 Connection 以及其上的事务,会通过 ThreadLocal 的方式和当前线程进行绑定。这样,就导致我们无法切换数据源。

难道分库分表中间件不也是需要 Connection 进行这些事情么?答案是的,但是不同的是分库分表中间件返回的 Connection 返回的实际是动态的 DynamicRoutingConnection ,它管理了整个请求(逻辑)过程中,使用的所有的 Connection ,而最终执行 SQL 的时候,DynamicRoutingConnection 会解析 SQL ,获得表对应的真正的 Connection 执行 SQL 操作。

后面会介绍分库分表中间件的案例详解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/376283.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PPP简介,PPP分层体系架构,PPP链路建立过程及PPP的帧格式

PPP&#xff08;Point-to-Point Protocol&#xff09;是一种用于在两个网络节点之间传输数据的通信协议。它最初是为在拨号网络上进行拨号连接而开发的&#xff0c;现在已经被广泛应用于各种网络环境中&#xff0c;例如在宽带接入、虚拟专用网&#xff08;VPN&#xff09;等场景…

在linux中使用lftp和sftp下载文件(夹)

一、首先确保你的系统中已经下载了lftp和sftp。 1.安装lftp sudo apt install lftp sudo apt install screen 2.安装sftp 在Linux系统中&#xff0c;一般RedHat系统默认已经安装了openssh-client和openssh-server&#xff0c;即默认已经集成了sftp服务&#xff0c;不需要重…

LVGL8.3 集成 ST7789V 显示驱动和 CST816T 触摸屏驱动

LVGL8.3 集成 ST7789V 显示驱动和 CTS816S 触摸屏驱动起因效果&#xff08;正常显示&#xff0c;触摸屏可调换X&#xff0c;Y轴&#xff09;使用方式前提操作步骤最后参考起因 LVGL的ESP32 Drivers库中已经包含了大多数显示和触摸芯片的驱动&#xff0c;基本上只需要在MenuCon…

高级前端面试题汇总

iframe 有那些优点和缺点&#xff1f; iframe 元素会创建包含另外一个文档的内联框架&#xff08;即行内框架&#xff09;。 优点&#xff1a; 用来加载速度较慢的内容&#xff08;如广告&#xff09;可以使脚本可以并行下载可以实现跨子域通信 缺点&#xff1a; iframe 会…

【ssm框架】从0开始搭建ssm框架(idea版本)

SSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架集由Spring、MyBatis两个开源框架整合而&#xff08;SpringMVC是Spring中的部分内容&#xff09;&#xff0c;常作为数据源较简单的web项目的框架。 一、 环境介绍 先创建一个web工程。这里使用SSM最终完成一个员工信息的…

驾驭云端之风1——Spring Cloud微服务架构实践指南

本博客纯属个人总结&#xff0c;非原创。喜欢技术交流的&#xff0c;可关注博主&#xff0c;武汉有后端开发群&#xff0c;可支持内推&#xff0c;了解武汉行情等。 前沿 优惠卷平台项目的整体功能和模块&#xff0c;以及每个功能点的技术选型和背后的依据。 搭建一个简化版的…

窃密恶意软件Raccoon最新样本Stealer v2分析

Raccoon 是一个恶意软件家族&#xff0c;2019 年来一直在地下犯罪论坛中以恶意软件即服务的身份进行售卖。2022 年 7 月&#xff0c;该恶意软件家族发布了 C 语言编写的新版本 Raccoon Stealer v2&#xff0c;打破了以往使用 C 开发的传统。 Raccoon 是一个信息窃密恶意软件&a…

JavaEE高阶---Redis

一:缓存简介 1.1 缓存定义 缓存是一个高速数据交换的存储器&#xff0c;使用它可以快速的访问和操作数据 . 1.2 程序中的缓存 当没有使用缓存时 : 但随着业务的发展&#xff0c;公司的框架慢慢变成了多个程序调用一个数据库的情况了&#xff1a; 这是大部分公司的普遍的架构…

设备加密狗

场景描述 随着科技的飞速发展&#xff0c;越来越多的智能设备走进生产加工车间。例如智能雕刻机、钣金机、榫槽机、钻孔机、磨刀机等等。 这些智能设备存在很大共性&#xff0c;就是都内嵌完整的操作系统。有的是windows&#xff0c;有的是linux。设备制造商提供的专业软件运…

pycharm的下载讲解以及安装步骤

目录 Python安装 这时&#xff0c;有人要问了&#xff0c;下载pycharm一定要下载Python么&#xff1f; Python官网 Pycharm安装 完成 推荐书籍 写在最后 Python安装 首先我们进入Python的官方下载网站 这时&#xff0c;有人要问了&#xff0c;下载pycharm一定要下载Py…

算法设计与分析期末考试复习(五)

回溯法 回溯法是一种试探法&#xff0c;将n元问题P的状态空间E表示成为一棵高为n的带权有序数T&#xff0c;把在E中求问题P的解转换为在T中搜索问题P的解。 解题方法&#xff1a;按选优条件对T进行深度优先搜索&#xff0c;以达到目标。 从根节点出发深度优先搜索解空间树。当…

初学网络安全不可不知的:10款开源安全工具

随着互联网的不断发展&#xff0c;安全问题也越来越受到企业的重视。但安全问题往往需要大量资金的投入&#xff0c;例如聘请安全工程师&#xff0c;产品研发&#xff0c;测试等流程。这对于那些原本就资金紧缺的企业而言&#xff0c;是绝对无法接受的。因此&#xff0c;为了减…

Python3+Selenium3自动化测试-(准备)

最近在学习selenium自动化测试相关的内容&#xff0c;所以将实际准备情况做一记录&#xff0c; # 系统&#xff1a;win10(64位) # 浏览器&#xff1a;Chrome(67.0)、Firefox(61.0)、IE # python版本&#xff1a;3.6.5 # Selenium&#xff1a;3.13.0Selenium简介 Selenium是一…

JUC并发编程——线程安全问题

目录一、共享问题1.1 共享带来的问题1.2 临界区与竞态条件二、解决方案2.1 上下文切换——synchronized-解决2.2 上下文切换——synchronized-理解2.3 上下文切换——synchronized-思考2.4 锁面向对象改进2.5 方法上的 synchronized三、synchronized习题3.1 synchronized-加在方…

记住这些快捷键,让你轻松玩转mac(macOS 常用快捷键分享)

适当运用键盘快捷键可以帮助用户提高效率&#xff0c;这里小编为大家带来了一些macOS 常用快捷键&#xff0c;掌握这些快捷键可以帮你解放鼠标手&#xff0c;成为键盘侠&#xff0c;一起来看看吧&#xff01; 快捷键标识简介 当你在应用菜单中发现部分操作项后边有一些特殊标…

搜广推 AutoRec与 Deep Crossing - 推荐系统深度学习模型开篇

😄 AutoRec:2015年由澳大利亚国立大学提出。【后文简称AR】 😄 DeepCrossing:2016年,微软基于ResNet的经典DNN结构。【后文简称DC】 文章目录 1、AutoRec1.1、原理1.2、优点1.3、缺点2、Deep Crossing2.1、原理2.2、优点:2.3、缺点:Reference1、AutoRec 1.1、原理 -…

IEEE SLT 2022论文丨如何利用x-vectors提升语音鉴伪系统性能?

分享一篇IEEE SLT 2022收录的声纹识别方向的论文&#xff0c;《HOW TO BOOST ANTI-SPOOFING WITH X-VECTORS》由AuroraLab&#xff08;极光实验室&#xff09;发表。 来源丨AuroraLab AuroraLab源自清华大学电子工程系与新疆大学信息科学与工程学院&#xff0c;以说话人识别和…

Qt 第9课、计算器中缀转后缀算法

计算器核心算法&#xff1a; 1、将中缀表达式进行数字和运算符的分离 2、将中缀表达式转换成后缀表达式 3、通过后缀表达式计算最后的结果 二、计算器中缀转后缀算法 计算器中缀转后缀算法的意义在于把中缀表达式转换成后缀表达式&#xff0c;能够更好地计算 算法的基本思路…

【RabbitMQ笔记08】消息队列RabbitMQ之防止消息丢失的三种方式(生产者消息确认、消费者消息确认、消息持久化)

这篇文章&#xff0c;主要介绍消息队列RabbitMQ之防止消息丢失的三种方式&#xff08;生产者消息确认、消费者消息确认、消息持久化&#xff09;。 目录 一、防止消息丢失 1.1、消息确认机制&#xff08;生产者&#xff09; &#xff08;1&#xff09;生产者丢失消息 &…

字节跳动软件测试岗4轮面经(已拿34K+ offer)...

没有绝对的天才&#xff0c;只有持续不断的付出。对于我们每一个平凡人来说&#xff0c;改变命运只能依靠努力幸运&#xff0c;但如果你不够幸运&#xff0c;那就只能拉高努力的占比。 2021年10月&#xff0c;我有幸成为了字节跳动的一名测试工程师&#xff0c;从外包辞职了历…