JDBC是Java语言操作数据库的一套接口,也就是规范,具体的实现需要各个数据库厂商去实现。比如我们在使用JDBC去连接mySql数据库的时候,我们必须要依赖一个叫做mysql-connector-java的jar包,这里面封装的就是mySql对于JDBC的实现。
首先引入mysql-connector-java依赖,比如maven项目的pom.xml文件中添加:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.27version>
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框架对JDBC的支持,暴露在用户层面,最常见的是一个叫做JdbcTemplate的类,封装了各种对数据库的操作。
JdbcTemplate的使用:
1、确保pom.xml中有以下两个依赖(这里由于添加了spring-boot-starter-parent这种标签,可以不写版本号)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
点开这个spring-boot-starter-jdbc,可以看到主要是添加了HikariCP这个数据源,以及spring-jdbc的代码库。
2、数据源属性的配置,application.yml配置文件中添加如下配置:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/book
username: root
password: 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;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 查询全部图书
* @return
*/
@Override
public List<Book> getAllBooks() {
List<Book> books = jdbcTemplate.query("select * from book", new RowMapper<Book>() {
@Override
public 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
*/
@Override
public 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能用@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
@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;
}
}
条件注解@ConditionalOnMissingBean(JdbcOperations.class):在没有JdbcOperations类型的bean时才加载当前类。
@Bean方法,返回值类型为JdbcTemplate,而JdbcTemplate恰好就实现了JdbcOperations接口,所以这里@ConditionalOnMissingBean注解的作用,就是当用户自己配置了JdbcOperations接口实现类的bean,并且已经注册时,spring就不会再自动注册jdbcTemplate了。
配置类解析完,会进行集中处理,注册bean definition。当处理到JdbcTemplateConfiguration这个配置类时,会解析@Bean方法(spring将其视为生成bean对象的工厂方法),检测到@Bean注解没有name属性,则使用方法名"jdbcTemplate"注册一个bean definition。后续在推断jdbcTemplate的类型时,会使用其工厂方法的返回类型JdbcTemplate作为目标类型。
4、现在jdbcTemplate这个bean已经注册上了,并且目标类型为JdbcTemplate。接下来在使用@Autowired注解注入jdbcTemplate实例时,就会使用到bean factory的getBeanNamesForType方法,根据类型找到jdbcTemplate这个bean,然后使用工厂方法,也就是带@Bean注解的jdbcTemplate(DataSource dataSource, JdbcProperties properties)方法进行实例化。
5、这时候只要保证 DataSource 和 JdbcProperties 这两个bean实例化完成,就能完成jdbcTemplate的实例化,我们就能用了。
实际上这个时候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.Driver
url: jdbc:mysql://localhost:3306/book
username: root
password: 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 {
@Bean
SqlDataSourceScriptDatabaseInitializer 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;
}
@Override
public 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类源码:
@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。