JDBC是Java語言操作數(shù)據(jù)庫的一套接口,也就是規(guī)范,具體的實現(xiàn)需要各個數(shù)據(jù)庫廠商去實現(xiàn)。比如我們在使用JDBC去連接mySql數(shù)據(jù)庫的時候,我們必須要依賴一個叫做mysql-connector-java的jar包,這里面封裝的就是mySql對于JDBC的實現(xiàn)。
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類加載,執(zhí)行其靜態(tài)代碼塊,完成驅動注冊
Class.forName("com.mysql.cj.jdbc.Driver");
//準備用戶名、密碼、數(shù)據(jù)庫地址
String name = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/book";
//創(chuàng)建數(shù)據(jù)庫連接
Connection connection = DriverManager.getConnection(url, name, password);
//創(chuàng)建一個PreparedStatement,字面意思"準備好的語句",用來執(zhí)行具體的sql字符串
String sql = "select * from book";
PreparedStatement statement = connection.prepareStatement(sql);
//執(zhí)行sql,返回結果集
ResultSet resultSet = statement.executeQuery();
//處理結果集,逐行打印某一列數(shù)據(jù)
//ResultSet的光標每次只能指向一條記錄,初始定位在在第一行之前,需要調(diào)用其next()方法移動光標才能訪問記錄
while (resultSet.next()) {
System.out.println(resultSet.getString("book_name"));
}
//釋放資源
resultSet.close();
statement.close();
connection.close();
}
}
Spring boot對JDBC的封裝
Spring框架對JDBC的支持,暴露在用戶層面,最常見的是一個叫做JdbcTemplate的類,封裝了各種對數(shù)據(jù)庫的操作。
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這個數(shù)據(jù)源,以及spring-jdbc的代碼庫。
2、數(shù)據(jù)源屬性的配置,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依賴,業(yè)務代碼中使用JdbcTemplate執(zhí)行相關操作,示例如下:
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;
/**
* 查詢?nèi)繄D書
* @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的實例化過程
既然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恰好就實現(xiàn)了JdbcOperations接口,所以這里@ConditionalOnMissingBean注解的作用,就是當用戶自己配置了JdbcOperations接口實現(xiàn)類的bean,并且已經(jīng)注冊時,spring就不會再自動注冊jdbcTemplate了。
bean definition注冊階段
配置類解析完,會進行集中處理,注冊bean definition。當處理到JdbcTemplateConfiguration這個配置類時,會解析@Bean方法(spring將其視為生成bean對象的工廠方法),檢測到@Bean注解沒有name屬性,則使用方法名"jdbcTemplate"注冊一個bean definition。后續(xù)在推斷jdbcTemplate的類型時,會使用其工廠方法的返回類型JdbcTemplate作為目標類型。
bean實例化階段
4、現(xiàn)在jdbcTemplate這個bean已經(jīng)注冊上了,并且目標類型為JdbcTemplate。接下來在使用@Autowired注解注入jdbcTemplate實例時,就會使用到bean factory的getBeanNamesForType方法,根據(jù)類型找到jdbcTemplate這個bean,然后使用工廠方法,也就是帶@Bean注解的jdbcTemplate(DataSource dataSource, JdbcProperties properties)方法進行實例化。
5、這時候只要保證 DataSource 和 JdbcProperties 這兩個bean實例化完成,就能完成jdbcTemplate的實例化,我們就能用了。
實際上這個時候DataSource這個參數(shù)已經(jīng)實例化完成了。
DataSource參數(shù)
1)DataSource一個數(shù)據(jù)源接口,那么應該有一個實現(xiàn)類注冊了這個bean;或者類似 jdbcTemplate 那樣有個配置類通過@Bean方法生成這個bean。
經(jīng)過debug發(fā)現(xiàn),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能夠加載到這個配置類,并根據(jù)@Bean方法注冊了一個名為"dataSource"的bean definition。
2)實例化dataSource時,要先確保DataSourceProperties參數(shù)的實例化。
先看看DataSourceProperties的代碼:
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
這個類不像常見的配置類那樣使用@Configuration注解,而是使用了@ConfigurationProperties注解。
@ConfigurationProperties是用來綁定外部配置屬性的注解,我們在applicaition.yml文件中配置的數(shù)據(jù)源屬性,如下圖:
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類的成員變量一一對應的,后續(xù)在DataSourceProperties實例初始化的時候會進行解析和綁定。
DataSourceProperties這個bean是如何注冊上的呢?
在DataSource的頂層自動配置類DataSourceAutoConfiguration中,使用了一個注解@EnableConfigurationProperties(DataSourceProperties.class),表示啟用了DataSourceProperties這個類作為一個配置類,來支持外部配置的屬性綁定。
在處理DataSourceAutoConfiguration并注冊bean definition的過程中,調(diào)用了loadBeanDefinitionsFromRegistrars方法。這里使用了EnableConfigurationPropertiesRegistrar,顧名思義,它解析了@EnableConfigurationProperties的屬性,然后注冊了對應的bean definition。
DataSourceProperties這個bean又是如何綁定上我們定義的數(shù)據(jù)源配置的呢?
在DataSourceProperties實例化之后,在初始化過程中調(diào)用了initializeBean方法,允許一些BeanPostProcessor執(zhí)行其postProcessBeforeInitialization方法,做特定的初始化工作;其中就有一個ConfigurationPropertiesBindingPostProcessor,通過bind方法對DataSourceProperties這個bean和配置文件中的屬性進行了綁定。
還有一個問題,為什么在我們需要的jdbcTemplate實例化之前,DataSource就已經(jīng)實例化了呢?
實際上在解析配置類的過程中,還有兩個相關的bean被注冊上了。
1)dataSourceScriptDatabaseInitializer,
有一個配置類叫做DataSourceInitializationConfiguration,它有一個@Bean方法,用來生成一個SqlDataSourceScriptDatabaseInitializer對象,這是一個數(shù)據(jù)庫初始化器,用來獲取數(shù)據(jù)庫初始化相關的配置(比如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的靜態(tài)內(nèi)部類,實現(xiàn)了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配置類中已經(jīng)使用@Import注解引入了。
由于它實現(xiàn)了ImportBeanDefinitionRegistrar接口,重寫了registerBeanDefinitions方法,所以在解析的時候會實例化一個對象作為注冊器registrar,歸屬到配置類JdbcTemplateAutoConfiguration中。
在處理配置類JdbcTemplateAutoConfiguration的時候,最終也會調(diào)用loadBeanDefinitionsFromRegistrars方法,然后走到DatabaseInitializationDependencyConfigurer重寫的registerBeanDefinitions方法,注冊了一個DependsOnDatabaseInitializationPostProcessor。
3)配置類的解析和處理,包括注冊bean definition的過程,其實是spring容器啟動過程中,調(diào)用BeanDefinitionRegistryPostProcessor的過程;在此之后,還有一個調(diào)用BeanFactoryPostProcessor(前者的父接口)的過程,其中就會實例化DependsOnDatabaseInitializationPostProcessor并調(diào)用其postProcessBeanFactory方法。
DependsOnDatabaseInitializationPostProcessor會使用相關的探測器detector去探測到dataSourceScriptDatabaseInitializer這個bean,
然后再使用相關探測器探測到依賴數(shù)據(jù)庫初始化的bean(其中一個探測器的探測方法是尋找實現(xiàn) JdbcOperations 或者 NamedParameterJdbcOperations 接口的bean),最終找到了我們的 jdbcTemplate,
然后將dataSourceScriptDatabaseInitializer設置成jdbcTemplate的依賴。所以在jdbcTemplate實例化前,已經(jīng)先實例化了dataSourceScriptDatabaseInitializer以及他們依賴的DataSource。
JdbcProperties參數(shù)
JdbcProperties類源碼:文章來源:http://www.zghlxwxcb.cn/news/detail-824636.html
@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。文章來源地址http://www.zghlxwxcb.cn/news/detail-824636.html
到了這里,關于Java基礎 - JDBC操作數(shù)據(jù)庫(MySql)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!