赞
踩
在实际SpringBoot项目中,通常都离不开与数据库的交互,更多的选择是使用持久层框架MyBatis或SpringData等,而不是原生的spring-jdbc。
但学习SpringBoot整合JDBC场景下的组件装配,以及注解声明式事务的生效原理、控制流程、事务传播行为等,依旧是必要的,对后续学习SpringBoot整合持久层框架具有很大帮助。
选择MySQL作为本项目的数据源,创建一个新的数据库springboot_demo和一个新表t_user:
CREATE DATABASE springboot_demo CHARACTER SET 'utf8mb4';
CREATE TABLE t_user(
id INT(11) NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
tel VARCHAR(20) NULL,
PRIMARY KEY (id)
);
<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.47</version>
</dependency>
</dependencies>
在resources目录下新建application.properties文件,并配置数据源:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_demo?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
public class User {
private Integer id;
private String name;
private String tel;
// getter setter toString ...
}
@Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public void save(User user) { jdbcTemplate.update("insert into t_user (name, tel) values (?, ?)", user.getName(), user.getTel()); } public List<User> findAll() { return jdbcTemplate.query("select * from t_user", BeanPropertyRowMapper.newInstance(User.class)); } }
@Service public class UserService { @Autowired private UserDao userDao; @Transactional(rollbackFor = Exception.class) public void test() { User user = new User(); user.setName("齐天大圣"); user.setTel("12306"); userDao.save(user); List<User> userList = userDao.findAll(); userList.forEach(System.out::println); } }
主启动类注意两点:第一是要获取IOC容器,并提取出UserService类调用其test
方法;第二是要标注@EnableTransactionManagement注解开启注解式声明式事务。
@SpringBootApplication
@EnableTransactionManagement
public class JDBCApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(JDBCApp.class, args);
UserService userService = context.getBean(UserService.class);
userService.test();
}
}
运行主启动类,控制台可以正确打印出一条用户信息,说明SpringBoot整合JDBC场景顺利完成。
User{id=1, name='齐天大圣', tel='12306'}
由 10.1 节可知,对于原生的JDBC整合后,主启动类中并没有声明与之相关的注解,因此有关JDBC的组件装配都是以自动配置类的方式实现的。
借助IDEA通过spring-boot-autoconfigure依赖的spring.factories文件可以找到有关JDBC的自动配置类:
源码1:spring-boot-autoconfigure-2.3.11.RELEASE.jar!/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
可以发现,SpringBoot默认支持的自动配置包含数据源、JdbcTemplate、事务管理器、JNDI、XA协议等。
在SpringBoot的官方文档中,介绍了SpringBoot支持的数据源连接池:
1.1.4. Supported Connection Pools
Spring Boot uses the following algorithm for choosing a specific implementation:
We prefer HikariCP for its performance and concurrency. If HikariCP is available, we always choose it.
Otherwise, if the Tomcat pooling DataSource is available, we use it.
Otherwise, if Commons DBCP2 is available, we use it.
If none of HikariCP, Tomcat, and DBCP2 are available and if Oracle UCP is available, we use it.
可见,SpringBoot支持的数据源连接池包括HikariCP、Tomcat、DBCP2、Oracle UCP等,默认使用HikariCP。
因此,自动配置类DataSourceAutoConfiguration的静态内部类PooledDataSourceConfiguration会生效。因为它通过@Import注解导入了DataSourceConfiguration.Hikari类。
源码2:DataSourceAutoConfiguration.java @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory") @EnableConfigurationProperties(DataSourceProperties.class) @Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class }) public class DataSourceAutoConfiguration { @Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration { } }
源码3:DataSourceConfiguration.java @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HikariDataSource.class) @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true) static class Hikari { @Bean @ConfigurationProperties(prefix = "spring.datasource.hikari") HikariDataSource dataSource(DataSourceProperties properties) { HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } }
由 源码2、3 可知,SpringBoot默认会创建一个HikariDataSource。
此外,DataSourceAutoConfiguration还使用@Import注解导入了一个DataSourceInitializationConfiguration配置类。
源码4:DataSourceInitializationConfiguration.java @Configuration(proxyBeanMethods = false) @Import({DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class}) class DataSourceInitializationConfiguration { static class Registrar implements ImportBeanDefinitionRegistrar { private static final String BEAN_NAME = "dataSourceInitializerPostProcessor"; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!registry.containsBeanDefinition(BEAN_NAME)) { // 注册DataSourceInitializerPostProcessor GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); beanDefinition.setSynthetic(true); registry.registerBeanDefinition(BEAN_NAME, beanDefinition); } } } }
由 源码4 可知,这个配置类又使用@Import注解导入了一个DataSourceInitializerInvoker和一个Registrar注册器。注册器又向BeanDefinition中注册了一个DataSourceInitializerPostProcessor。
由类名理解,这是一个数据源初始化的执行器。
源码5:DataSourceInitializerInvoker.java class DataSourceInitializerInvoker implements ApplicationListener<DataSourceSchemaCreatedEvent>, InitializingBean { @Override public void afterPropertiesSet() { DataSourceInitializer initializer = getDataSourceInitializer(); if (initializer != null) { boolean schemaCreated = this.dataSourceInitializer.createSchema(); if (schemaCreated) { initialize(initializer); } } } }
由 源码5 可知,DataSourceInitializerInvoker实现了ApplicationListener接口,因此是一个监听器,监听的事件是DataSourceSchemaCreatedEvent;此外,它还实现了InitializingBean接口,会在对象创建后回调afterPropertiesSet
方法执行初始化逻辑。
在afterPropertiesSet
方法中,首先会调用getDataSourceInitializer
方法获取DataSourceInitializer实例,随后执行DataSourceInitializer的createSchema
方法,如果执行成功则继续执行initialize
方法。
该方法名直译为“创建约束”,即执行DDL语句。也就在项目开发中,可以先不创建数据库的表结构,而是在应用程序启动时,自动读取自定义的SQL文件执行DDL语句进行创建。
源码6:DataSourceInitializer.java boolean createSchema() { // 加载Schema资源 List<Resource> scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema"); // 解析Schema资源 if (!scripts.isEmpty()) { if (!isEnabled()) { logger.debug("Initialization disabled (not running DDL scripts)"); return false; } String username = this.properties.getSchemaUsername(); String password = this.properties.getSchemaPassword(); runScripts(scripts, username, password); } return !scripts.isEmpty(); } private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) { if (resources != null) { // 如果全局配置文件中配置了spring.datasource.schema属性 // 则直接根据该spring.datasource.schema属性加载资源文件 return getResources(propertyName, resources, true); } // 默认返回字符串"all" String platform = this.properties.getPlatform(); List<String> fallbackResources = new ArrayList<>(); // 拼接文件名:schema-all.sql和schema.sql fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql"); fallbackResources.add("classpath*:" + fallback + ".sql"); // 加载文件schema-all.sql和schema.sql return getResources(propertyName, fallbackResources, false); }
由 源码6 可知,createSchema
方法先读取全局配置文件中的spring.datasource.schema属性,根据该属性配置的路径加载资源文件,再对资源文件进行解析。在调用getScripts
方法读取资源文件时,分为两种情况:
源码7:DataSourceInitializerInvoker.java private void initialize(DataSourceInitializer initializer) { try { // 广播DataSourceSchemaCreatedEvent事件 this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(initializer.getDataSource())); if (!this.initialized) { // 解析资源文件 this.dataSourceInitializer.initSchema(); this.initialized = true; } } // catch ...... } @Override public void onApplicationEvent(DataSourceSchemaCreatedEvent event) { DataSourceInitializer initializer = getDataSourceInitializer(); if (!this.initialized && initializer != null) { initializer.initSchema(); this.initialized = true; } }
源码8:DataSourceInitializer.java void initSchema() { // 加载资源文件 List<Resource> scripts = getScripts("spring.datasource.data", this.properties.getData(), "data"); // 解析资源文件 if (!scripts.isEmpty()) { if (!isEnabled()) { logger.debug("Initialization disabled (not running data scripts)"); return; } String username = this.properties.getDataUsername(); String password = this.properties.getDataPassword(); runScripts(scripts, username, password); } }
由 源码7 可知,initialize
方法会广播一个DataSourceSchemaCreatedEvent事件,回调onApplicationEvent
方法,最终执行DataSourceInitializer类的initSchema
方法。
由 源码8 可知,initSchema
方法和createSchema
方法大同小异,不同的是initSchema
方法寻找资源文件的依据是全局配置文件中的spring.datasource.data属性,如果没有配置该属性,则加载名为data-all.sql和data.sql的资源文件。
总结一下,有了DataSourceInitializerInvoker的设计,使得项目开发中,可以自定义DDL语句和DML语句并保存在SQL文件中,放置在resources目录下,在项目启动时自动初始化数据库表结构和数据。
由类名理解,这是一个专门为DataSourceInitializer定制的后置处理器。
源码9:DataSourceInitializerPostProcessor.java
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSource) {
this.beanFactory.getBean(DataSourceInitializerInvoker.class);
}
return bean;
}
由 源码9 可知,当DataSourceInitializerPostProcessor检测到当前正在创建的bean对象的类型是DataSource,主动调用BeanFactory的getBean
方法创建一个DataSourceInitializerInvoker对象。这样做的目的是使预定定义好的SQL脚本立即执行,以确保DataSource与数据库表结构、数据的同步初始化。
源码10:JdbcTemplateAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {
}
由 源码10 可知,自动配置类JdbcTemplateAutoConfiguration会使用@Import注解注册一个JdbcTemplateConfiguration配置类和一个NamedParameterJdbcTemplateConfiguration配置类。
源码11:JdbcTemplateConfiguration.java @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(JdbcOperations.class) class JdbcTemplateConfiguration { @Bean @Primary JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); JdbcProperties.Template template = properties.getTemplate(); jdbcTemplate.setFetchSize(template.getFetchSize()); jdbcTemplate.setMaxRows(template.getMaxRows()); if (template.getQueryTimeout() != null) { jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds()); } return jdbcTemplate; } }
由 源码11 可知,JdbcTemplateConfiguration配置类会注册一个JdbcTemplate对象,用于与数据库的简单交互。
源码12:NamedParameterJdbcTemplateConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(JdbcTemplate.class)
@ConditionalOnMissingBean(NamedParameterJdbcOperations.class)
class NamedParameterJdbcTemplateConfiguration {
@Bean
@Primary
NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
}
由 源码12 可知,NamedParameterJdbcTemplateConfiguration配置类会注册一个NamedParameterJdbcTemplate对象,用于支持参数命名化的JdbcTemplate增强。
源码13:DataSourceTransactionManagerAutoConfiguration.java @Configuration(proxyBeanMethods = false) @ConditionalOnClass({JdbcTemplate.class, PlatformTransactionManager.class}) @AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) @EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceTransactionManagerAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(DataSource.class) static class DataSourceTransactionManagerConfiguration { @Bean @ConditionalOnMissingBean(PlatformTransactionManager.class) DataSourceTransactionManager transactionManager(DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager)); return transactionManager; } } }
由 源码13 可知,自动配置类DataSourceTransactionManagerAutoConfiguration会注册一个DataSourceTransactionManager,用于支持基于数据源的事务控制。
······
本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。