Java基础 - JDBC操作数据库(MySql)

JDBC是Java语言操作数据库的一套接口,也就是规范,具体的实现需要各个数据库厂商去实现。比如我们在使用JDBC去连接mySql数据库的时候,我们必须要依赖一个叫做mysql-connector-java的jar包,这里面封装的就是mySql对于JDBC的实现。

Java中使用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 boot对JDBC的封装

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的实例化过程

既然JdbcTemplate能用@Autowired自动注入,但我们又没有手动配置这个bean,那么它是如何自动注册成为bean,然后实例化的呢?

配置类解析阶段

1、spring boot在启动过程中,在bean的实例化之前,会对配置类进行解析和注册bean definition。
首先在解析带有@SpringBootApplication的启动类时,会处理里面的@Import(AutoConfigurationImportSelector.class)这个注解(由于是DeferredImportSelector,暂存起来等启动类解析完后再处理);
处理过程中会读取META-INF目录下的spring.factories文件,其中包含了很多启用自动配置(EnableAutoConfiguration)的目标类,这里面就有和JdbcTemplate相关的JdbcTemplateAutoConfiguration类,于是我们找到了线索。
Java基础 - JDBC操作数据库(MySql)_第1张图片
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注册阶段

配置类解析完,会进行集中处理,注册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.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参数

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。

你可能感兴趣的:(java,数据库,mysql)