纷杂的Spring-boot-starter: 3 数据访问与spring-boot-starter-jdbc

大部分的Java应用都需要访问数据库,尤其是服务层,所以,SpringBoot会为我们自动配置相应的数据访问设施

若想SpringBoot为我们自动配置数据访问的基础设施,那么,我们需要直接或间接地依赖spring-jdbc,一旦spring-jdbc位于我们springBoot应用的classpath,即会触发数据访问相关自动配置行为,最简单的做法就是把spring-boot-starter-jdbc加为应用的依赖

默认情况下,如果我们没有配置任何DataSource,那么,SpringBoot会为我们自动配置一个基于嵌入式数据库的DataSource,这种自动配置行为其实很适合于测试场景,但对实际的开发帮助不大,基本上我们会自己配置一个DataSource实例,或者通过自动配置模块提供的配置参数对DataSource实例进行自定义的配置

假设我们的SpringBoot应用只依赖一个数据库,那么,使用DataSource自动配置模块提供的配置参数是最方便的:

spring.datasource.url= jdbc: mysql://{ database host}: 3306/{ databaseName} 
spring.datasource.username={ database username} 
spring.datasource.password={ database password} 

当然,自己配置一个DataSource也是可以的,SpringBoot也会智能地选择我们自己配置的这个DataSource实例(只不过必要性不大)

除了DatraSource会自动配置,SpringBoot还会自动配置相应的JdbcTemplate、DataSourceTransactionManager等关联“设施”,可谓服务周到,我们只要在使用的地方注入就可以了:

class SomeDao { 
	@Autowired 
	JdbcTemplate jdbcTemplate; 

	public < T> List< T> queryForList( String sql){
	 	// ... 
	} 
	// ... 
} 

不过,spring-boot-starter-jdbc以及与其相关的自动配置也不总是带来便利,在某些场景下,我们可能会在一个应用中需要依赖和访问多个数据库,这个时候就会出问题了。

假设我们在ApplicationContext中设置了多个DataSource实例指向多个数据库:

@Bean 
public DataSource dataSource1() throws Throwable {
	DruidDataSource dataSource = new DruidDataSource(); 
	dataSource.setUrl(...); 
	dataSource.setUsername(...); 
	dataSource.setPassword(...); 
	// TODO other settings if necessary in the future. 
	return dataSource;
}
@Bean 
public DataSource dataSource2() throws Throwable {
	DruidDataSource dataSource = new DruidDataSource(); 
	dataSource.setUrl(...); 
	dataSource.setUsername(...); 
	dataSource.setPassword(...); 
	// TODO other settings if necessary in the future. 
	return dataSource;
} 

那么,不好意思,启动SpringBoot应用的时候会抛出类似如下的异常:

Exception): 
No qualifying bean of type [javax. sql. DataSource] is defined: expected single matching bean but found 2

为了避免这种情况发生,我们需要在SpringBoot的启动类上做点儿“手脚”:

@SpringBootApplication( exclude = { DataSourceAutoConfiguration. class, DataSourceTransactionManagerAutoConfiguration. class }) 
public class UnveilSpringChapter3Application { 
	public static void main( String[] args) { 
		SpringApplication.run( UnveilSpringChapter3Application. class, args); 
	} 
} 

也就是说,我们需要在这种场景下排除SpringBoot默认提供的对DataSource相关的自动配置。
但如果我们还是想要享受SpringBoot提供的自动配置DataSource的机能,也可通过为其中一个DataSource配置添加org.springframework.context.annotation.Primary这个Annotation的方式以实现两全其美:

@Bean 
@Primary
public DataSource dataSource1() throws Throwable {
	DruidDataSource dataSource = new DruidDataSource(); 
	dataSource.setUrl(...); 
	dataSource.setUsername(...); 
	dataSource.setPassword(...); 
	// TODO other settings if necessary in the future. 
	return dataSource;
}
@Bean 
public DataSource dataSource2() throws Throwable {
	DruidDataSource dataSource = new DruidDataSource(); 
	dataSource.setUrl(...); 
	dataSource.setUsername(...); 
	dataSource.setPassword(...); 
	// TODO other settings if necessary in the future. 
	return dataSource;
} 

另外,SpringBoot还提供了很多其他数据访问相关的自动配置模块,比如spring-boot-starter-data-jpa、spring-boot-starter-data-mongodb等,大家可以根据自己数据访问的具体场景选择使用这些自动配置模块。

警告: 如果选择了spring-boot-starter-data-jpa等关系数据库相关的数据访问自动配置模块,并且还需要同时依赖访问多个数据库,那么,也需要相应的在SpringBoot启动类中排除掉这些自动配置模块中的AutoConfiguration实现类(对应spring-boot-starter-data-jpa是JpaRepositoriesAutoConfiguration),或者标注某个DataSource为@Primary

SpringBoot应用的数据库版本化管理

关于如何针对数据库的变更进行版本化管理,从Ruby On Rails的migration支持,到Java的MyBatis Migrations,Flyway以及Liquibase,都给出了相应的最佳实践建议和方案,但是,从我看到的国内业界现状,数据库migrations的实践方式并没有在国内普遍应用起来,大部分都是靠人来解决,这或许可以用一句“成熟度不够”来解释,另外一个原因或许是职能明确分工后造成局面

如果仔细分析以上数据库migration方案就会发现,它们给出的应用场景和实践几乎都是单应用、单部署的,这在庞大单一部署单元(Monolith)的年代显然是很适合的,因为应用从开发到发布部署,再到启动,整个生命周期内,应用相关的所有“原材料”都集中在一起进行管理,而且国外开发者往往偏“特种作战”(Full-Stack Developer),一身多能,从而数据库migration这种实践自然可以成型并广泛应用。

但回到国内来看,我们往往是“集团军作战”,拼的是“大部队+明确分工”的模式,而且应用所面向的服务人数也往往更为庞大,所以,整个应用的交付链路上各个环节之间的衔接是不同的人,而应用最终部署的拓扑又往往是分布式部署居多,所以,在一个项目单元里维护数据库的migration脚本然后部署后启动前执行这些脚本就变得不合时宜了:

  1. 从职责上,这些Migration脚本虽然大部分情况下都是开发人员写,但写完之后要不要进行SQL审查,是否符合规范,这些又会涉及应用运维DBA,代码管理系统对开发来说很亲切,对DBA来说则不尽然,而且DBA往往还要一人服务多个团队多个项目,从DBA的角度来说,他更愿意将SQL集中到一处进行管理,而不是分散在各个项目中
  2. 应用分布式部署之后,就不单单是单一部署的应用在启动之前直接执行一次migration脚本那么简单了,你要执行多次,虽然migration方案都有版本控制,变更应该是最终状态都是一样的,但这个多个部署节点上都执行统一逻辑显然是多于的。更复杂一点儿,多个应用可能同时使用同一个数据库的情况(不要怀疑,遗留系统对大家来说都不陌生),一个项目的数据库migration操作跟另一个项目的数据库migration操作会不会在互补知晓的情况下产生冲突和破坏?

所以,数据库migration的思路和实践很好,但不能照搬(任何事情其实皆如此),不过,我们到时不用一棒子打死,结合现有的一些数据库migration方案,比如flyway和liquibase,我们可以对这些数据库migration的基础设施和支持外部化(Externalize),一个可能的架构如下图:
在这里插入图片描述

在这个架构中,数据库migration的版本化管理剥离到了单独的管理系统,单一项目中不再保存完整历史的migration记录,而只需要提供当次发布要牵扯的数据库变更SQL。在项目发布的时候,由DBA进行统一的审查并纳入单独的数据库migration管理系统,由单独的数据库migration管理系统来管理完整的数据库migration记录,可以根据数据库的粒度进行管理和状态同步,从而既可以在开发阶段让开发人员可以集中管理数据库SQL,又能在发布期间审查SQL并同步migration状态和完整的历史记录管理。当然,这一切可以实现的前提是有一套完整的软件交付链路支撑平台,能够从流程上,软件生命周期管理上进行统一的治理和规范。

不管怎样,SpringBoot还为大家提供了针对Flyway和Liquibase的自动配置功能(org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration和org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration),对于单一开发和部署的应用来说,还是可以考虑的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值