#依赖

  1. # 使用jdbc依赖
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-jdbc</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. <scope>runtime</scope>
  10. </dependency>
  11. # 使用mybatis依赖
  12. <dependency>
  13. <groupId>mysql</groupId>
  14. <artifactId>mysql-connector-java</artifactId>
  15. <scope>runtime</scope>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.mybatis.spring.boot</groupId>
  19. <artifactId>mybatis-spring-boot-starter</artifactId>
  20. <version>2.1.1</version>
  21. </dependency>

数据库配置

  • url地址local...后是库名称 ```yaml

spring: datasource: username: root password: root

# ?serverTimezone=UTC  解决时区的报错,后面2个配置处理字符集
#写在url里面的无法加时区数字,如UTC+8是错误的
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver   #驱动名称

也可以把时区写最上面,表示对以下所有数据库都失效

spring: time-zone: GMT+8 datasource:

注意jackson也有time-zone这个配置,通过jackson配时区时为这样:

spring: jackson: time-zone: GMT+8 datasource:

<a name="wlAJN"></a>
## 使用连接池时的配置

- springboot-jdbc的依赖集成了hikari,配置下配置文件即可直接使用
```yaml
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    initialization-mode: always
    continue-on-error: true
    schema:
    - "classpath:db/schema.sql"
    data:
    - "classpath:db/data.sql"
    hikari:
      minimum-idle: 5
      connection-test-query: SELECT 1 FROM DUAL   #测试连接通不通的语句
      maximum-pool-size: 20
      auto-commit: true
      idle-timeout: 30000
      pool-name: SpringBootDemoHikariCP
      max-lifetime: 60000
      connection-timeout: 30000
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver   #驱动名称
    username: root
    password: root
    druid:
      # 配置初始化大小、最小、最大
      initial-size: 5
      minIdle: 10
      max-active: 20
      # 配置获取连接等待超时的时间(单位:毫秒)
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 2000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 600000
      max-evictable-idle-time-millis: 900000
      # 用来测试连接是否可用的SQL语句,默认值每种数据库都不相同,这是mysql
      validationQuery: select 1
      # 应用向连接池申请连接,并且testOnBorrow为false时,连接池将会判断连接是否处于空闲状态,如果是,则验证这条连接是否可用
      testWhileIdle: true
      # 如果为true,默认是false,应用向连接池申请连接时,连接池会判断这条连接是否是可用的
      testOnBorrow: false
      # 如果为true(默认false),当应用使用完连接,连接池回收连接的时候会判断该连接是否还可用
      testOnReturn: false
      # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle
      poolPreparedStatements: true
      # 要启用PSCache,必须配置大于0,当大于0时, poolPreparedStatements自动触发修改为true,
      # 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,
      # 可以把这个数值配置大一些,比如说100
      maxOpenPreparedStatements: 20
      # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作
      keepAlive: true
      # Spring 监控,利用aop 对指定接口的执行时间,jdbc数进行记录
      aop-patterns: "com.springboot.template.dao.*"
      ########### 启用内置过滤器(第一个 stat必须,否则监控不到SQL)##########
      filters: stat,log4j2
      # 自己配置监控统计拦截的filter,这里配置了wall则filters就不要有wall了,否则还是不允许批量操作
      filter:
        wall:
          config:
            multiStatementAllow: true #true开启批量更新
        # 开启druiddatasource的状态监控
        stat:
          enabled: true
          db-type: mysql
          # 开启慢sql监控,超过2s 就认为是慢sql,记录到日志中
          log-slow-sql: true
          slow-sql-millis: 2000
        # 日志监控,使用slf4j 进行日志输出
        slf4j:
          enabled: true
          statement-log-error-enabled: true
          statement-create-after-log-enabled: false
          statement-close-after-log-enabled: false
          result-set-open-after-log-enabled: false
          result-set-close-after-log-enabled: false
      ########## 配置WebStatFilter,用于采集web关联监控的数据 ##########
      web-stat-filter:
        enabled: true                   # 启动 StatFilter
        url-pattern: /*                 # 过滤所有url
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除一些不必要的url
        session-stat-enable: true       # 开启session统计功能
        session-stat-max-count: 1000    # session的最大个数,默认100
      ########## 配置StatViewServlet(监控页面),用于展示Druid的统计信息 ##########
      stat-view-servlet:
        enabled: true                   # 启用StatViewServlet
        url-pattern: /druid/*           # 访问内置监控页面的路径,内置监控页面的首页是/druid/index.html
        reset-enable: false              # 不允许清空统计数据,重新计算
        login-username: root            # 配置监控页面访问密码
        login-password: 123
        allow: 127.0.0.1           # 允许访问的地址,如果allow没有配置或者为空,则允许所有访问
        deny:                                        # 拒绝访问的地址,deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝

JDBC

  • 以往我们要使用连接池获取连接,需要创建配置信息类,通过配置信息类得到连接池对象。再通过连接池对象得到DataSource对象
  • 现在我们把配置信息写在配置文件中,自动注入会根据配置文件自动注入得到DataSource对象

  • dataSource.getClass()得到的信息是class com.zaxxer.hikari.HikariDataSource
    • Spring Boot 2.2.5 默认使用HikariDataSource 数据源,所以我们也应该使用hikari来获取jdbc ```java

@SpringBootTest class SpringbootDataJdbcApplicationTests {

//DI注入数据源
@Autowired
DataSource dataSource;

@Test
public void contextLoads() throws SQLException {
    //查看ds对象的类,得到ds对象连接池类型
    System.out.println(dataSource.getClass());
    //获得连接
    Connection connection =   dataSource.getConnection();
    //关闭连接
    connection.close();
}

}

<a name="W2cAy"></a>
# JdbcTemplate

- 在 Spring 中操作数据库,可以使用 Spring 提供的 JdbcTemplate 对象,JdbcTemplate 类提供了很多便利的方法,比如把数据库数据转变成基本数据类型或对象,执行自定义的 SQL 语句,提供了自定义的数据错误处理等,JdbcTemplate 使用示例如下:
- Spring 的 JdbcTemplate 是对 JDBC API 的封装
   - JdbcTemplate 是线程安全的;
   - 自动完成资源的创建和释放工作;
```java
@Autowired
private JdbcTemplate jdbcTemplate;

// 新增
@GetMapping("save")
public String save() {
    String sql = "INSERT INTO USER (USER_NAME,PASS_WORD) VALUES ('laowang','123')";
    int rows = jdbcTemplate.update(sql);
    return "执行成功,影响" + rows + "行";
}

// 删除
@GetMapping("del")
public String del(int id) {
    int rows= jdbcTemplate.update("DELETE FROM  USER  WHERE ID = ?",id);
    return "执行成功,影响" + rows + "行";
}

// 查询
@GetMapping("getMapById")
public Map getMapById(Integer id) {
    String sql = "SELECT * FROM USER WHERE ID = ?";
    Map map= jdbcTemplate.queryForMap(sql,id);
    return map;
}

Mybatis

注解版

  • 直接在mapper接口上增加@Mapper即可注册为mapper接口 .
    • 或者使用@MapperScan(?)在boot主启动类上,参数为mapper接口所在的包,这样mapper接口无需再每一个都加@Mapper
  • 使用依赖注入获取mapper接口以使用sql方法
  • springboot中的mybatis注解版无需在配置文件中配置dao-接口文件位置,只要能被扫描到即可用,即上面的注册操作 ```java @Mapper public interface sourceMapper { // 获取所有部门信息 @Select(“select * from score”) List getDepartments(); }

class Test(){ @Autowired sourceMapper sourcexMapper;

main(){ 
   List<source> ss=  sourcexMapper.getDepartments();
     }

}

<a name="linVr"></a>
## xml版

- xml版跟注解版几乎一样
- **springboot里xml写在resource下时无需配置扫描,写在java下时需要配置**
   - 这就相当于`mybatis.xml`配置mapper-xml文件位置
   - 写在src/java下的xml**必须指定具体路径,无法使用**`****/*.xml**`最多就`*.xml`  路径为java下的包路径,如`com/rao/www/mapper.*.xml`
```properties
#  classpath即表示resource目录  
#这里xml是直接写在resource下所以路径为:  *.xml     现在表示当前路径的所以xml   如果想包括当前路径
#及其子路径,则为**/*.xml
#当然也可以直接写具体的路径,如 mapper/UserMapper.xml
mybatis.mapper-locations=classpath:*.xml


#yml写法:
mybatis:
      mapper-locations: classpath:*.xml

资源过滤

  • 跟ssm里差不多. 不过这里写了resources的资源配置(没啥用就是,因为spring和boot中对于resource下的xml可以自动识别)

    • 写在build中
      <resources>
      <!-- mapper.xml文件在java目录下 -->
      <resource>
         <directory>src/main/java</directory>
         <includes>
             <include>**/*.xml</include>
         </includes>
      </resource>
      <!-- mapper.xml文件在resources目录下-->
      <resource>
         <directory>src/main/resources</directory>
      </resource>
      </resources>
      

      日志

  • springboot默认集成了logging日志,通过如下配置可自动输出sql的执行记录

    • 试了下只能在propetes中配置有用,拿到yaml中无效
  • level后为mapper接口路径,一定得是包才有效,如果写在src/java下,则logging.level=debug是错的

    logging.level.com.boot.mapper=debug
    

    事务

  • 你的程序是否支持事务首先取决于数据库,数据库不支持使用了事务也不会生效

    spring事务使用

  • Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • **PlatformTransactionManager**: (平台)事务管理器,Spring 事务策略的核心。
    • 提供了3个方法,获取,提交,回滚事务。见下面代码块
  • **TransactionDefinition**:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。即事务属性
  • **TransactionStatus**: 事务运行状态。事务管理器获取事务返回的就是该类对象
    • **TransactionDefinition ****TransactionStatus** 这两个接口可以看作是事务的描述
    • **PlatformTransactionManager**会根据 **TransactionDefinition**的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 **TransactionStatus**接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等

编程式事务管理

  • 较少使用,需要手动管理事务比较麻烦,但是对于你理解 Spring 事务管理原理有帮助。

    • 借助 TransactionTemplate或者TransactionManager手动管理事务

      @Autowired
      private TransactionTemplate transactionTemplate;
      public void testTransaction() {
      
         transactionTemplate.execute(new TransactionCallbackWithoutResult() {
             @Override
             protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
      
                 try {
      
                     // ....  业务代码
                 } catch (Exception e){
                     //回滚
                     transactionStatus.setRollbackOnly();
                 }
      
             }
         });
      }
      

      ```java @Autowired private PlatformTransactionManager transactionManager;

public void testTransaction() { //获得事务 TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // …. transactionManager.commit(status); //提交事务 } catch (Exception e) { transactionManager.rollback(status); //回滚 } }

<a name="wRyZl"></a>
### 声明式事务管理

- **推荐使用(代码侵入性最小),实际是通过 AOP 实现**
- 在类或者方法上使用`@Transactional `。**不建议在类上使用事务**,事务层层调用很耗费资源
   - 当把@Transactional 注解放在类上时,表示所有该类的 `**public **`方法都配置相同的事务属性信息。
   - 如果放接口上:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
   -  作用于方法:当类配置了@Transactional,方法也配置了@Transactional,**方法的事务会覆盖类的事务配置信息。  如下是**`**Transactional**`失效的原因
<a name="ChUnY"></a>
#### 事务失效的情况
事务失效即事务未回滚成功,spring事务本质还是利用spring aop,因此会影响动态代理的因素也会影响spring事务, 非公开的方法,final方法,static方法无法通过动态代理重写,因此也无法进行spring事务

1. **方法非公开**:Transactional注解应用在非`public`,`static`,`final`修饰的方法上,Transactional将会失效.(原因见[链接](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486483&idx=2&sn=77be488e206186803531ea5d7164ec53&chksm=cea243d8f9d5cacecaa5c5daae4cde4c697b9b5b21f96dfc6cce428cfcb62b88b3970c26b9c2&token=816772476&lang=zh_CN#rd))。注意只是失效不会报错
1. **同一个类中未有事务方法调用有事务方法**:A再调如果用**本类**的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务将失效。
   - 如果普通方法需要调当前类的事务方法,1:除了把事务方法移动到其他服务,2:还可以选择在当前服务注入当前服务然后调用事务方法(spring ioc三级缓存避免了这种情况的循环依赖)3:或者使用如下方法获取代理对象然后调用
```java
@Servcie
public class ServiceA {

   public void save(User user) {
         queryData1();
         queryData2();
         ((ServiceA)AopContext.currentProxy()).doSave(user);
   }

   @Transactional(rollbackFor=Exception.class)
   public void doSave(User user) {
       addData1();
       updateData2();
    }
 }
  1. 方法嵌套异常捕获:事务中方法A捕获B方法的异常,事务不会正常回滚
  2. 错误的注解参数:
    1. 异常捕获的类型不正确:在业务方法中一般不需要手动catch异常,如果非要catch一定要抛出throw new RuntimeException(),让@Transactional能够捕获,或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class)。这样才能执行异常回滚
    2. 错误配置传播行为也会导致当前事务失效
  3. 抛出的异常catch处理了又未再手动抛出, 则事务失效。即开发者处理了这个异常,则spring捕获不到该异常
  4. 事务方法所在类未被spring管理
  5. 多线程时使用同一个事务方法,这些事务不是同一个事务,因此一个线程里的方法回滚不会影响其他他线程(spring的事务是通过数据库连接来实现,只有同一个连接的相同方法才是一个事务
  6. 未开启事务:这种情况一般出现在非springboot的项目中,因为springboot的DataSourceTransactionManagerAutoConfiguration默认配置时已经开启了事务
  7. 还有一种会导致失效,数据库不支持事务,这种一般不会发生,使用前就设置号了

事务属性

jdbc与mybatis - 图1


  • timeout:事务的超时时间,默认值为 -1,即无。如果超过该时间限制但事务还没有完成,则自动回滚事务。
  • **rollbackFor**回滚规则:在@Transactional注解中如果不配置rollbackFor属性,那么事务默认只会在遇到**RuntimeException**的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。也可以指定异常
  • **isolation**设置隔离级别
    • Isolation._DEFAULT/READ_UNCOMMITTED/READ_COMMITTED/SERIALIZABLE_ _DEFAULT_表示采用数据库默认的隔离级别,**isolation**的默认值为_**DEFAULT**_
  • **propagation**代表事务的传播行为,默认值为 **Propagation.REQUIRED**,如果错误的配置如下三种,可能也会导致失效
    • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • readOnly指定事务是否为只读事务,默认值为 false;只读事务不涉及数据的修改,数据库会提供一些针对多条查询命令的优化

    传播行为

  • 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

    • 如: 我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了
  • 默认值为 Propagation.REQUIRED,如
    //一个方法回滚,则另外一个方法也回滚
    Class A {
      @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
      public void aMethod {
          //do something
          B b = new B();
          b.bMethod();
      }
    }
    Class B {
      @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
      public void bMethod {
         //do something
      }
    }
    

  • Propagation.REQUIRED如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务2个事务互相影响( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
  • Propagation.REQUIRES_NEW存在不存在都重新创建一个新的事务,如果当前存在事务,暂停当前的事务。事务相互独立,互不干扰。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )。但是b出现异常回滚,a也会回滚
  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
  • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
  • Propagation.NESTED :当前无事务时,和Propagation.REQUIRED 效果一样。如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;嵌套事务不影响外部事务,但是外部事务影响嵌套事务
    • 如a调b,b出错b回滚a不滚。但是a出错a,b都回滚
  • 对于希望子方法事务不影响外部事务方法,也可以采用手动catch子方法事务的异常,这样异常就不会层层向上传递

    @Service
    public class UserService {
      @Autowired
      private UserMapper userMapper;
      @Autowired
      private RoleService roleService;
      @Transactional
      public void add(UserModel userModel) throws Exception {
          userMapper.insertUser(userModel);
          try {
              roleService.doOtherThing(); 
          } catch (Exception e) {
              log.error(e.getMessage(), e);
          }
      }
    }
    

    多数据源

  • 要实现多数据源其实很简单。最主要的类就是AbstractRoutingDataSource,该类内部实现了数据源的切换。我们要做的就是传入一组数据源与数据源对应的键,并重写**AbstractRoutingDataSource****determineCurrentLookupKey**(该方法是获取一个键,然后抽象路由数据源类会根据键去那组数据源里找对应数据源使用)

  • 多数据源常常用于读写分离

    禁用自动配置

    @SpringBootApplication
    // 启动自动配置,但排除指定的自动配置:
    @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
    public class Application {
      ...
    }
    

    多数据源的配置

  • 具体的配置完全不是固定的,因为我们只要能实现数据源的注入即可

    spring:
    datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      master:
        url: jdbc:mysql://127.0.0.1:3307/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
        username: root
        password:
        #也可以针对具体的如驱动,数据库类型具体再进行配置,不配置则采用默认配置
      slave:
        url: jdbc:mysql://127.0.0.1:3308/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
        username: root
        password:
    

    手动创建数据库操作相关的bean

  • 手动创建DataSouce,不使用自动创建(自动创建时只能有一个默认配置)

    • 下面是一主一从时的配置
  • 上面说过,多数据源切换最重要的类就是AbstractRoutingDataSource,主从动态配置就是将多个数据源以及对应键存储到map中传给AbstractRoutingDataSource

    @Configuration
    @MapperScan(basePackages = "com.xjt.proxy.mapper", sqlSessionTemplateRef = "sqlTemplate")  //这里不知道是不是将所有的mapper和sqlTemplate纳入管理的意思
    public class DataSourceConfig {
    
       // 主库,这里采用的是bean工厂创建bean的方法,也可以自己创建DataSourceProperties的bean,然后再将该配置对象作为参数传给datasource的bean进行创建
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.master")
        public DataSource masterDb() {
    return DruidDataSourceBuilder.create().build();
        }
    
      /**
       * 从库
       */
      //ConfigurationProperties 从库有该注解表名从库不是必须的,只有配置了从库时才会进行创建
      @Bean
      @ConditionalOnProperty(prefix = "spring.datasource", name = "slave", matchIfMissing = true)
      @ConfigurationProperties(prefix = "spring.datasource.slave")
      public DataSource slaveDb() {
          return DruidDataSourceBuilder.create().build();
      }
    
      /**
       * 主从动态配置
       */
      @Bean
      public DynamicDataSource dynamicDb(@Qualifier("masterDb") DataSource masterDataSource,
          @Autowired(required = false) @Qualifier("slaveDb") DataSource slaveDataSource) {
          DynamicDataSource dynamicDataSource = new DynamicDataSource();
          Map<Object, Object> targetDataSources = new HashMap<>();
          targetDataSources.put(DynamicDataSourceEnum.MASTER.getDataSourceName(), masterDataSource);
          if (slaveDataSource != null) {
              targetDataSources.put(DynamicDataSourceEnum.SLAVE.getDataSourceName(), slaveDataSource);
          }
          //设置数据源和对应的键
          dynamicDataSource.setTargetDataSources(targetDataSources);
          //设置默认的数据源
          dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
          return dynamicDataSource;
      }
      //Qualifier为动态数据源表示将sqlSession和DataSourceTransactionManager都使用我们实际使用的数据源进行创建
      @Bean
      public SqlSessionFactory sessionFactory(@Qualifier("dynamicDb") DataSource dynamicDataSource) throws Exception {
          SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
          bean.setMapperLocations(
              new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml"));
          bean.setDataSource(dynamicDataSource);
          return bean.getObject();
      }
      @Bean
      public SqlSessionTemplate sqlTemplate(@Qualifier("sessionFactory") SqlSessionFactory sqlSessionFactory) {
          return new SqlSessionTemplate(sqlSessionFactory);
      }
      @Bean(name = "dataSourceTx")
      public DataSourceTransactionManager dataSourceTx(@Qualifier("dynamicDb") DataSource dynamicDataSource) {
          DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
          dataSourceTransactionManager.setDataSource(dynamicDataSource);
          return dataSourceTransactionManager;
      }
    }
    

    AbstractRoutingDataSource

    ```java

public class DynamicDataSource extends AbstractRoutingDataSource { @Override //该方法是获取当前的键值以便拿到对应的数据源,配合我们定义的注解实时修改当前键值,如果我们不需要手动能指定数据源的功能,我们也可以这里直接读操作就将从库的键进行轮询然后返回,如果是写操作就返回主数据源对应的键 protected Object determineCurrentLookupKey() { return DataSourceContextHolder.get(); } }

```java
//ThreadLocal用于存储实时的键
public class DataSourceContextHolder {
    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_CONTEXT = new ThreadLocal<>();
    public static void set(String datasourceType) {
        DYNAMIC_DATASOURCE_CONTEXT.set(datasourceType);
    }
    public static String get() {
        return DYNAMIC_DATASOURCE_CONTEXT.get();
    }
    public static void clear() {
        DYNAMIC_DATASOURCE_CONTEXT.remove();
    }
}
@Getter
public enum DynamicDataSourceEnum {
    MASTER("master"),
    SLAVE("slave");
    private String dataSourceName;
    DynamicDataSourceEnum(String dataSourceName) {
        this.dataSourceName = dataSourceName;
    }
}

注解实现手动切换数据源

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSourceSelector {
       //指定数据源对应的键,指定是否需要清除数据源
    DynamicDataSourceEnum value() default DynamicDataSourceEnum.MASTER;
    boolean clear() default true;
}
@Slf4j
@Aspect
@Order(value = 1)
@Component
public class DataSourceContextAop {

    @Around("@annotation(com.xjt.proxy.dynamicdatasource.DataSourceSelector)")
    public Object setDynamicDataSource(ProceedingJoinPoint pjp) throws Throwable {
        boolean clear = true;
        try {
            Method method = this.getMethod(pjp);
            DataSourceSelector dataSourceImport = method.getAnnotation(DataSourceSelector.class);
            clear = dataSourceImport.clear();
            DataSourceContextHolder.set(dataSourceImport.value().getDataSourceName());
            log.info("========数据源切换至:{}", dataSourceImport.value().getDataSourceName());
            return pjp.proceed();    //执行被增强的原本方法
        } finally {
            if (clear) {
                DataSourceContextHolder.clear();
            }

        }
    }
    //通过切入点获取切入点方法对象,以拿到自定义注解
    private Method getMethod(JoinPoint pjp) {
        MethodSignature signature = (MethodSignature)pjp.getSignature();
        return signature.getMethod();
    }

}

测试

 @Autowired
    private UserMapper userMapper;

    @DataSourceSelector(value = DynamicDataSourceEnum.MASTER)
    public int update(Long userId) {
        User user = new User();
        user.setUserId(userId);
        user.setUserName("老薛");
        return userMapper.updateByPrimaryKeySelective(user);
    }