autobahn-java-master,禁用自动配置

Spring Boot大量使用自动配置和默认配置,极大地减少了代码,通常只需要加上几个注解,并按照默认规则设定一下必要的配置即可。例如,配置JDBC,默认情况下,只需要配置一个spring.datasource:

spring:

datasource:

url: jdbc:hsqldb:file:testdb

username: sa

password:

dirver-class-name: org.hsqldb.jdbc.JDBCDriver

Spring Boot就会自动创建出DataSource、JdbcTemplate、DataSourceTransactionManager,非常方便。

但是,有时候,我们又必须要禁用某些自动配置。例如,系统有主从两个数据库,而Spring Boot的自动配置只能配一个,怎么办?

这个时候,针对DataSource相关的自动配置,就必须关掉。我们需要用exclude指定需要关掉的自动配置:

@SpringBootApplication

// 启动自动配置,但排除指定的自动配置:

@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)

public class Application {

...

}

现在,Spring Boot不再给我们自动创建DataSource、JdbcTemplate和DataSourceTransactionManager了,要实现主从数据库支持,怎么办?

让我们一步一步开始编写支持主从数据库的功能。首先,我们需要把主从数据库配置写到application.yml中,仍然按照Spring Boot默认的格式写,但datasource改为datasource-master和datasource-slave:

spring:

datasource-master:

url: jdbc:hsqldb:file:testdb

username: sa

password:

dirver-class-name: org.hsqldb.jdbc.JDBCDriver

datasource-slave:

url: jdbc:hsqldb:file:testdb

username: sa

password:

dirver-class-name: org.hsqldb.jdbc.JDBCDriver

注意到两个数据库实际上是同一个库。如果使用MySQL,可以创建一个只读用户,作为datasource-slave的用户来模拟一个从库。

下一步,我们分别创建两个HikariCP的DataSource:

public class MasterDataSourceConfiguration {

@Bean("masterDataSourceProperties")

@ConfigurationProperties("spring.datasource-master")

DataSourceProperties dataSourceProperties() {

return new DataSourceProperties();

}

@Bean("masterDataSource")

DataSource dataSource(@Autowired @Qualifier("masterDataSourceProperties") DataSourceProperties props) {

return props.initializeDataSourceBuilder().build();

}

}

public class SlaveDataSourceConfiguration {

@Bean("slaveDataSourceProperties")

@ConfigurationProperties("spring.datasource-slave")

DataSourceProperties dataSourceProperties() {

return new DataSourceProperties();

}

@Bean("slaveDataSource")

DataSource dataSource(@Autowired @Qualifier("slaveDataSourceProperties") DataSourceProperties props) {

return props.initializeDataSourceBuilder().build();

}

}

注意到上述class并未添加@Configuration和@Component,要使之生效,可以使用@Import导入:

@SpringBootApplication

@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)

@Import({ MasterDataSourceConfiguration.class, SlaveDataSourceConfiguration.class})

public class Application {

...

}

此外,上述两个DataSource的Bean名称分别为masterDataSource和slaveDataSource,我们还需要一个最终的@Primary标注的DataSource,它采用Spring提供的AbstractRoutingDataSource,代码实现如下:

class RoutingDataSource extends AbstractRoutingDataSource {

@Override

protected Object determineCurrentLookupKey() {

// 从ThreadLocal中取出key:

return RoutingDataSourceContext.getDataSourceRoutingKey();

}

}

RoutingDataSource本身并不是真正的DataSource,它通过Map关联一组DataSource,下面的代码创建了包含两个DataSource的RoutingDataSource,关联的key分别为masterDataSource和slaveDataSource:

public class RoutingDataSourceConfiguration {

@Primary

@Bean

DataSource dataSource(

@Autowired @Qualifier("masterDataSource") DataSource masterDataSource,

@Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource) {

var ds = new RoutingDataSource();

// 关联两个DataSource:

ds.setTargetDataSources(Map.of(

"masterDataSource", masterDataSource,

"slaveDataSource", slaveDataSource));

// 默认使用masterDataSource:

ds.setDefaultTargetDataSource(masterDataSource);

return ds;

}

@Bean

JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {

return new JdbcTemplate(dataSource);

}

@Bean

DataSourceTransactionManager dataSourceTransactionManager(@Autowired DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

}

仍然需要自己创建JdbcTemplate和PlatformTransactionManager,注入的是标记为@Primary的RoutingDataSource。

这样,我们通过如下的代码就可以切换RoutingDataSource底层使用的真正的DataSource:

RoutingDataSourceContext.setDataSourceRoutingKey("slaveDataSource");

jdbcTemplate.query(...);

只不过写代码切换DataSource即麻烦又容易出错,更好的方式是通过注解配合AOP实现自动切换,这样,客户端代码实现如下:

@Controller

public class UserController {

@RoutingWithSlave //

@GetMapping("/profile")

public ModelAndView profile(HttpSession session) {

...

}

}

实现上述功能需要编写一个@RoutingWithSlave注解,一个AOP织入和一个ThreadLocal来保存key。由于代码比较简单,这里我们不再详述。

如果我们想要确认是否真的切换了DataSource,可以覆写determineTargetDataSource()方法并打印出DataSource的名称:

class RoutingDataSource extends AbstractRoutingDataSource {

...

@Override

protected DataSource determineTargetDataSource() {

DataSource ds = super.determineTargetDataSource();

logger.info("determin target datasource: {}", ds);

return ds;

}

}

访问不同的URL,可以在日志中看到两个DataSource,分别是HikariPool-1和hikariPool-2:

2020-06-14 17:55:21.676 INFO 91561 --- [nio-8080-exec-7] c.i.learnjava.config.RoutingDataSource : determin target datasource: HikariDataSource (HikariPool-1)

2020-06-14 17:57:08.992 INFO 91561 --- [io-8080-exec-10] c.i.learnjava.config.RoutingDataSource : determin target datasource: HikariDataSource (HikariPool-2)

我们用一个图来表示创建的DataSource以及相关Bean的关系:

┌────────────────────┐ ┌──────────────────┐

│@Primary │

│RoutingDataSource │ └──────────────────┘

│ ┌────────────────┐ │ ┌──────────────────┐

│ │MasterDataSource│ │

│ └────────────────┘ │ │TransactionManager│

│ ┌────────────────┐ │ └──────────────────┘

│ │SlaveDataSource │ │

│ └────────────────┘ │

└────────────────────┘

注意到DataSourceTransactionManager和JdbcTemplate引用的都是RoutingDataSource,所以,这种设计的一个限制就是:在一个请求中,一旦切换了内部数据源,在同一个事务中,不能再切到另一个,否则,DataSourceTransactionManager和JdbcTemplate操作的就不是同一个数据库连接。

练习

小结

可以通过@EnableAutoConfiguration(exclude = {...})指定禁用的自动配置;

可以通过@Import({...})导入自定义配置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值