JDBC是Java语言操作数据库的一套接口,也就是规范,具体的实现需要各个数据库厂商去实现。比如我们在使用JDBC去连接mySql数据库的时候,我们必须要依赖一个叫做mysql-connector-java的jar包,这里面封装的就是mySql对于JDBC的实现。
Java中使用JDBC
首先引入mysql-connector-java依赖,比如maven项目的pom.xml文件中添加:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency>
然后添加代码(一个简单的例子):
package org.example;import java.sql.*;public class JDBCTest {public static void main(String[] args) throws ClassNotFoundException, SQLException {//Driver类加载,执行其静态代码块,完成驱动注册Class.forName("com.mysql.cj.jdbc.Driver");//准备用户名、密码、数据库地址String name = "root";String password = "root";String url = "jdbc:mysql://localhost:3306/book";//创建数据库连接Connection connection = DriverManager.getConnection(url, name, password);//创建一个PreparedStatement,字面意思"准备好的语句",用来执行具体的sql字符串String sql = "select * from book";PreparedStatement statement = connection.prepareStatement(sql);//执行sql,返回结果集ResultSet resultSet = statement.executeQuery();//处理结果集,逐行打印某一列数据//ResultSet的光标每次只能指向一条记录,初始定位在在第一行之前,需要调用其next()方法移动光标才能访问记录while (resultSet.next()) {System.out.println(resultSet.getString("book_name"));}//释放资源resultSet.close();statement.close();connection.close();}}
Spring boot对JDBC的封装
Spring框架对JDBC的支持,暴露在用户层面,最常见的是一个叫做JdbcTemplate的类,封装了各种对数据库的操作。
JdbcTemplate的使用:
1、确保pom.xml中有以下两个依赖(这里由于添加了spring-boot-starter-parent这种标签,可以不写版本号)
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
点开这个spring-boot-starter-jdbc,可以看到主要是添加了HikariCP这个数据源,以及spring-jdbc的代码库。
2、数据源属性的配置,application.yml配置文件中添加如下配置:
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/bookusername: rootpassword: root
3、在bean中引入JdbcTemplate依赖,业务代码中使用JdbcTemplate执行相关操作,示例如下:
package org.example.service.impl;import org.example.domain.Book;import org.example.service.BookService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import org.springframework.stereotype.Service;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;@Servicepublic class BookServiceImpl implements BookService {@Autowiredprivate JdbcTemplate jdbcTemplate;/** * 查询全部图书 * @return */@Overridepublic List<Book> getAllBooks() {List<Book> books = jdbcTemplate.query("select * from book", new RowMapper<Book>() {@Overridepublic Book mapRow(ResultSet rs, int rowNum) throws SQLException {Book book = new Book();book.setBookId(rs.getInt("book_id"));book.setBookName(rs.getString("book_name"));book.setBookDesc(rs.getString("book_desc"));book.setPublishTime(rs.getDate("publish_time"));return book;}});return books;}/** * 更新某个图书 * @param book * @return */@Overridepublic Boolean updateBookMessage(Book book) {String sql = "update book set book_desc = ? where book_id = ?";Object[] params = new Object[]{book.getBookDesc(), book.getBookId()};jdbcTemplate.update(sql, params);return true;}}
到这里简单的JdbcTemplate的使用就完成了。
JdbcTemplate的实例化过程
既然JdbcTemplate能用@Autowired自动注入,但我们又没有手动配置这个bean,那么它是如何自动注册成为bean,然后实例化的呢?
配置类解析阶段
1、spring boot在启动过程中,在bean的实例化之前,会对配置类进行解析和注册bean definition。
首先在解析带有@SpringBootApplication的启动类时,会处理里面的@Import(AutoConfigurationImportSelector.class)这个注解(由于是DeferredImportSelector,暂存起来等启动类解析完后再处理);
处理过程中会读取META-INF目录下的spring.factories文件,其中包含了很多启用自动配置(EnableAutoConfiguration)的目标类,这里面就有和JdbcTemplate相关的JdbcTemplateAutoConfiguration类,于是我们找到了线索。
2、接下来我们找到JdbcTemplateAutoConfiguration类的源码:
@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })@ConditionalOnSingleCandidate(DataSource.class)@AutoConfigureAfter(DataSourceAutoConfiguration.class)@EnableConfigurationProperties(JdbcProperties.class)@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,NamedParameterJdbcTemplateConfiguration.class })public class JdbcTemplateAutoConfiguration {}
这个类没有做什么实际操作,只是约束了一些自动配置的依赖关系,然后使用@Import注解引入了JdbcTemplateConfiguration这个类。
解析当前类时就会解析JdbcTemplateConfiguration类。
3、接下来看看JdbcTemplateConfiguration类:
@Configuration(proxyBeanMethods = false)@ConditionalOnMissingBean(JdbcOperations.class)class JdbcTemplateConfiguration {@Bean@PrimaryJdbcTemplate 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;}}
条件注解@ConditionalOnMissingBean(JdbcOperations.class):在没有JdbcOperations类型的bean时才加载当前类。
@Bean方法,返回值类型为JdbcTemplate,而JdbcTemplate恰好就实现了JdbcOperations接口,所以这里@ConditionalOnMissingBean注解的作用,就是当用户自己配置了JdbcOperations接口实现类的bean,并且已经注册时,spring就不会再自动注册jdbcTemplate了。
bean definition注册阶段
配置类解析完,会进行集中处理,注册bean definition。当处理到JdbcTemplateConfiguration这个配置类时,会解析@Bean方法(spring将其视为生成bean对象的工厂方法),检测到@Bean注解没有name属性,则使用方法名”jdbcTemplate”注册一个bean definition。后续在推断jdbcTemplate的类型时,会使用其工厂方法的返回类型JdbcTemplate作为目标类型。
bean实例化阶段
4、现在jdbcTemplate这个bean已经注册上了,并且目标类型为JdbcTemplate。接下来在使用@Autowired注解注入jdbcTemplate实例时,就会使用到bean factory的getBeanNamesForType方法,根据类型找到jdbcTemplate这个bean,然后使用工厂方法,也就是带@Bean注解的jdbcTemplate(DataSource dataSource, JdbcProperties properties)方法进行实例化。
5、这时候只要保证 DataSource 和 JdbcProperties 这两个bean实例化完成,就能完成jdbcTemplate的实例化,我们就能用了。
实际上这个时候DataSource这个参数已经实例化完成了。
DataSource参数
1)DataSource一个数据源接口,那么应该有一个实现类注册了这个bean;或者类似 jdbcTemplate 那样有个配置类通过@Bean方法生成这个bean。
经过debug发现,datasource这个bean,是由org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari类的dataSource方法生成的:
@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;}}
由于spring-boot-starter-jdbc默认引入了Hikari的jar包,所以spring能够加载到这个配置类,并根据@Bean方法注册了一个名为”dataSource”的bean definition。
2)实例化dataSource时,要先确保DataSourceProperties参数的实例化。
先看看DataSourceProperties的代码:
@ConfigurationProperties(prefix = "spring.datasource")public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {private ClassLoader classLoader;
这个类不像常见的配置类那样使用@Configuration注解,而是使用了@ConfigurationProperties注解。
@ConfigurationProperties是用来绑定外部配置属性的注解,我们在applicaition.yml文件中配置的数据源属性,如下图:
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/bookusername: rootpassword: root
这里前缀正是对应了prefix = “spring.datasource”这个属性,包括后面的driver-class-name、url等属性,都是和DataSourceProperties类的成员变量一一对应的,后续在DataSourceProperties实例初始化的时候会进行解析和绑定。
DataSourceProperties这个bean是如何注册上的呢?
在DataSource的顶层自动配置类DataSourceAutoConfiguration中,使用了一个注解@EnableConfigurationProperties(DataSourceProperties.class),表示启用了DataSourceProperties这个类作为一个配置类,来支持外部配置的属性绑定。
在处理DataSourceAutoConfiguration并注册bean definition的过程中,调用了loadBeanDefinitionsFromRegistrars方法。这里使用了EnableConfigurationPropertiesRegistrar,顾名思义,它解析了@EnableConfigurationProperties的属性,然后注册了对应的bean definition。
DataSourceProperties这个bean又是如何绑定上我们定义的数据源配置的呢?
在DataSourceProperties实例化之后,在初始化过程中调用了initializeBean方法,允许一些BeanPostProcessor执行其postProcessBeforeInitialization方法,做特定的初始化工作;其中就有一个ConfigurationPropertiesBindingPostProcessor,通过bind方法对DataSourceProperties这个bean和配置文件中的属性进行了绑定。
还有一个问题,为什么在我们需要的jdbcTemplate实例化之前,DataSource就已经实例化了呢?
实际上在解析配置类的过程中,还有两个相关的bean被注册上了。
1)dataSourceScriptDatabaseInitializer,
有一个配置类叫做DataSourceInitializationConfiguration,它有一个@Bean方法,用来生成一个SqlDataSourceScriptDatabaseInitializer对象,这是一个数据库初始化器,用来获取数据库初始化相关的配置(比如sql脚本位置、用户名、密码等)。源码如下:
@Configuration(proxyBeanMethods = false)@ConditionalOnMissingBean({ SqlDataSourceScriptDatabaseInitializer.class, SqlR2dbcScriptDatabaseInitializer.class })@ConditionalOnSingleCandidate(DataSource.class)@ConditionalOnClass(DatabasePopulator.class)class DataSourceInitializationConfiguration {@BeanSqlDataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,SqlInitializationProperties properties) {return new SqlDataSourceScriptDatabaseInitializer(determineDataSource(dataSource, properties.getUsername(), properties.getPassword()), properties);}private static DataSource determineDataSource(DataSource dataSource, String username, String password) {if (StringUtils.hasText(username) && StringUtils.hasText(password)) {return DataSourceBuilder.derivedFrom(dataSource).username(username).password(password).type(SimpleDriverDataSource.class).build();}return dataSource;}}
显然dataSourceScriptDatabaseInitializer 和我们的 jdbcTemplate 一样,也依赖 dataSource 这个 bean。
2)DependsOnDatabaseInitializationPostProcessor
它是org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer的静态内部类,实现了BeanFactoryPostProcessor接口,部分源码如下:
public class DatabaseInitializationDependencyConfigurer implements ImportBeanDefinitionRegistrar {private final Environment environment;DatabaseInitializationDependencyConfigurer(Environment environment) {this.environment = environment;}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {String name = DependsOnDatabaseInitializationPostProcessor.class.getName();if (!registry.containsBeanDefinition(name)) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DependsOnDatabaseInitializationPostProcessor.class,this::createDependsOnDatabaseInitializationPostProcessor);registry.registerBeanDefinition(name, builder.getBeanDefinition());}}private DependsOnDatabaseInitializationPostProcessor createDependsOnDatabaseInitializationPostProcessor() {return new DependsOnDatabaseInitializationPostProcessor(this.environment);}/** * {@link BeanFactoryPostProcessor} used to configure database initialization * dependency relationships. */static class DependsOnDatabaseInitializationPostProcessor implements BeanFactoryPostProcessor, Ordered {private final Environment environment;DependsOnDatabaseInitializationPostProcessor(Environment environment) {this.environment = environment;}
其实DatabaseInitializationDependencyConfigurer这个类在JdbcTemplateAutoConfiguration配置类中已经使用@Import注解引入了。
由于它实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法,所以在解析的时候会实例化一个对象作为注册器registrar,归属到配置类JdbcTemplateAutoConfiguration中。
在处理配置类JdbcTemplateAutoConfiguration的时候,最终也会调用loadBeanDefinitionsFromRegistrars方法,然后走到DatabaseInitializationDependencyConfigurer重写的registerBeanDefinitions方法,注册了一个DependsOnDatabaseInitializationPostProcessor。
3)配置类的解析和处理,包括注册bean definition的过程,其实是spring容器启动过程中,调用BeanDefinitionRegistryPostProcessor的过程;在此之后,还有一个调用BeanFactoryPostProcessor(前者的父接口)的过程,其中就会实例化DependsOnDatabaseInitializationPostProcessor并调用其postProcessBeanFactory方法。
DependsOnDatabaseInitializationPostProcessor会使用相关的探测器detector去探测到dataSourceScriptDatabaseInitializer这个bean,
然后再使用相关探测器探测到依赖数据库初始化的bean(其中一个探测器的探测方法是寻找实现 JdbcOperations 或者 NamedParameterJdbcOperations 接口的bean),最终找到了我们的 jdbcTemplate,
然后将dataSourceScriptDatabaseInitializer设置成jdbcTemplate的依赖。所以在jdbcTemplate实例化前,已经先实例化了dataSourceScriptDatabaseInitializer以及他们依赖的DataSource。
JdbcProperties参数
JdbcProperties类源码:
@ConfigurationProperties(prefix = "spring.jdbc")public class JdbcProperties {private final Template template = new Template();public Template getTemplate() {return this.template;}
JdbcProperties 和 刚才分析的 DataSourceProperties非常相似,都使用@ConfigurationProperties注解,bean注册和实例化的方式也和DataSourceProperties一样,这里不再分析。
该bean绑定的是以 spring.jdbc 为前缀的,提供给jdbcTemplate使用的配置,比如spring.jdbc.template.max-rows。