做網(wǎng)絡(luò)銷售都做什么網(wǎng)站饑餓營銷的十大案例
文章目錄
- 前言
- 第10章 SpringBoot整合JDBC
- 10.1 SpringBoot整合JDBC的項目搭建
- 10.1.1 初始化數(shù)據(jù)庫
- 10.1.2 整合項目
- 10.1.2.1 導(dǎo)入JDBC和MySQL驅(qū)動依賴
- 10.1.2.2 配置數(shù)據(jù)源
- 10.1.3 編寫業(yè)務(wù)代碼
- 10.1.3.1 編寫與t_user表對應(yīng)的實體類User
- 10.1.3.2 編寫Dao層代碼
- 10.1.3.3 編寫Service層代碼
- 10.1.4 編寫主啟動類
- 10.1.5 測試結(jié)果
- 10.2 整合JDBC后的自動裝配
- 10.2.1 配置數(shù)據(jù)源
- 10.2.1.1 DataSourceInitializerInvoker
- (1)createSchema
- (2)initialize
- 10.2.1.2 DataSourceInitializerPostProcessor
- 10.2.2 創(chuàng)建JdbcTemplate
- 10.2.3 配置事務(wù)管理器
前言
在實際SpringBoot項目中,通常都離不開與數(shù)據(jù)庫的交互,更多的選擇是使用持久層框架MyBatis或SpringData等,而不是原生的spring-jdbc。
但學(xué)習(xí)SpringBoot整合JDBC場景下的組件裝配,以及注解聲明式事務(wù)的生效原理、控制流程、事務(wù)傳播行為等,依舊是必要的,對后續(xù)學(xué)習(xí)SpringBoot整合持久層框架具有很大幫助。
第10章 SpringBoot整合JDBC
10.1 SpringBoot整合JDBC的項目搭建
10.1.1 初始化數(shù)據(jù)庫
選擇MySQL作為本項目的數(shù)據(jù)源,創(chuàng)建一個新的數(shù)據(jù)庫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)
);
10.1.2 整合項目
10.1.2.1 導(dǎo)入JDBC和MySQL驅(qū)動依賴
<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>
10.1.2.2 配置數(shù)據(jù)源
在resources目錄下新建application.properties文件,并配置數(shù)據(jù)源:
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
10.1.3 編寫業(yè)務(wù)代碼
10.1.3.1 編寫與t_user表對應(yīng)的實體類User
public class User {private Integer id;private String name;private String tel;// getter setter toString ...
}
10.1.3.2 編寫Dao層代碼
@Repository
public class UserDao {@Autowiredprivate 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));}}
10.1.3.3 編寫Service層代碼
@Service
public class UserService {@Autowiredprivate 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);}}
10.1.4 編寫主啟動類
主啟動類注意兩點:第一是要獲取IOC容器,并提取出UserService類調(diào)用其test
方法;第二是要標注@EnableTransactionManagement注解開啟注解式聲明式事務(wù)。
@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();}}
10.1.5 測試結(jié)果
運行主啟動類,控制臺可以正確打印出一條用戶信息,說明SpringBoot整合JDBC場景順利完成。
User{id=1, name='齊天大圣', tel='12306'}
10.2 整合JDBC后的自動裝配
由 10.1 節(jié)可知,對于原生的JDBC整合后,主啟動類中并沒有聲明與之相關(guān)的注解,因此有關(guān)JDBC的組件裝配都是以自動配置類的方式實現(xiàn)的。
借助IDEA通過spring-boot-autoconfigure依賴的spring.factories文件可以找到有關(guān)JDBC的自動配置類:
源碼1:spring-boot-autoconfigure-2.3.11.RELEASE.jar!/META-INF/spring.factoriesorg.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,\
可以發(fā)現(xiàn),SpringBoot默認支持的自動配置包含數(shù)據(jù)源、JdbcTemplate、事務(wù)管理器、JNDI、XA協(xié)議等。
10.2.1 配置數(shù)據(jù)源
在SpringBoot的官方文檔中,介紹了SpringBoot支持的數(shù)據(jù)源連接池:
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支持的數(shù)據(jù)源連接池包括HikariCP、Tomcat、DBCP2、Oracle UCP等,默認使用HikariCP。
因此,自動配置類DataSourceAutoConfiguration的靜態(tài)內(nèi)部類PooledDataSourceConfiguration會生效。因為它通過@Import注解導(dǎo)入了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默認會創(chuàng)建一個HikariDataSource。
此外,DataSourceAutoConfiguration還使用@Import注解導(dǎo)入了一個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";@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {if (!registry.containsBeanDefinition(BEAN_NAME)) {// 注冊DataSourceInitializerPostProcessorGenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);beanDefinition.setSynthetic(true);registry.registerBeanDefinition(BEAN_NAME, beanDefinition);}}}}
由 源碼4 可知,這個配置類又使用@Import注解導(dǎo)入了一個DataSourceInitializerInvoker和一個Registrar注冊器。注冊器又向BeanDefinition中注冊了一個DataSourceInitializerPostProcessor。
10.2.1.1 DataSourceInitializerInvoker
由類名理解,這是一個數(shù)據(jù)源初始化的執(zhí)行器。
源碼5:DataSourceInitializerInvoker.javaclass DataSourceInitializerInvoker implements ApplicationListener<DataSourceSchemaCreatedEvent>, InitializingBean {@Overridepublic void afterPropertiesSet() {DataSourceInitializer initializer = getDataSourceInitializer();if (initializer != null) {boolean schemaCreated = this.dataSourceInitializer.createSchema();if (schemaCreated) {initialize(initializer);}}}}
由 源碼5 可知,DataSourceInitializerInvoker實現(xiàn)了ApplicationListener接口,因此是一個監(jiān)聽器,監(jiān)聽的事件是DataSourceSchemaCreatedEvent;此外,它還實現(xiàn)了InitializingBean接口,會在對象創(chuàng)建后回調(diào)afterPropertiesSet
方法執(zhí)行初始化邏輯。
在afterPropertiesSet
方法中,首先會調(diào)用getDataSourceInitializer
方法獲取DataSourceInitializer實例,隨后執(zhí)行DataSourceInitializer的createSchema
方法,如果執(zhí)行成功則繼續(xù)執(zhí)行initialize
方法。
(1)createSchema
該方法名直譯為“創(chuàng)建約束”,即執(zhí)行DDL語句。也就在項目開發(fā)中,可以先不創(chuàng)建數(shù)據(jù)庫的表結(jié)構(gòu),而是在應(yīng)用程序啟動時,自動讀取自定義的SQL文件執(zhí)行DDL語句進行創(chuàng)建。
源碼6:DataSourceInitializer.javaboolean 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屬性// 則直接根據(jù)該spring.datasource.schema屬性加載資源文件return getResources(propertyName, resources, true);}// 默認返回字符串"all"String platform = this.properties.getPlatform();List<String> fallbackResources = new ArrayList<>();// 拼接文件名:schema-all.sql和schema.sqlfallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");fallbackResources.add("classpath*:" + fallback + ".sql");// 加載文件schema-all.sql和schema.sqlreturn getResources(propertyName, fallbackResources, false);
}
由 源碼6 可知,createSchema
方法先讀取全局配置文件中的spring.datasource.schema屬性,根據(jù)該屬性配置的路徑加載資源文件,再對資源文件進行解析。在調(diào)用getScripts
方法讀取資源文件時,分為兩種情況:
- 如果全局配置文件中配置了spring.datasource.schema屬性,則直接根據(jù)該屬性的值加載資源文件;
- 如果全局配置文件沒有配置該屬性,則加載名為schema-all.sql和schema.sql的資源文件。
(2)initialize
源碼7:DataSourceInitializerInvoker.javaprivate 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.javavoid 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事件,回調(diào)onApplicationEvent
方法,最終執(zhí)行DataSourceInitializer類的initSchema
方法。
由 源碼8 可知,initSchema
方法和createSchema
方法大同小異,不同的是initSchema
方法尋找資源文件的依據(jù)是全局配置文件中的spring.datasource.data屬性,如果沒有配置該屬性,則加載名為data-all.sql和data.sql的資源文件。
總結(jié)一下,有了DataSourceInitializerInvoker的設(shè)計,使得項目開發(fā)中,可以自定義DDL語句和DML語句并保存在SQL文件中,放置在resources目錄下,在項目啟動時自動初始化數(shù)據(jù)庫表結(jié)構(gòu)和數(shù)據(jù)。
10.2.1.2 DataSourceInitializerPostProcessor
由類名理解,這是一個專門為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檢測到當前正在創(chuàng)建的bean對象的類型是DataSource,主動調(diào)用BeanFactory的getBean
方法創(chuàng)建一個DataSourceInitializerInvoker對象。這樣做的目的是使預(yù)定定義好的SQL腳本立即執(zhí)行,以確保DataSource與數(shù)據(jù)庫表結(jié)構(gòu)、數(shù)據(jù)的同步初始化。
10.2.2 創(chuàng)建JdbcTemplate
源碼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@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;}}
由 源碼11 可知,JdbcTemplateConfiguration配置類會注冊一個JdbcTemplate對象,用于與數(shù)據(jù)庫的簡單交互。
源碼12:NamedParameterJdbcTemplateConfiguration.java@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(JdbcTemplate.class)
@ConditionalOnMissingBean(NamedParameterJdbcOperations.class)
class NamedParameterJdbcTemplateConfiguration {@Bean@PrimaryNamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {return new NamedParameterJdbcTemplate(jdbcTemplate);}}
由 源碼12 可知,NamedParameterJdbcTemplateConfiguration配置類會注冊一個NamedParameterJdbcTemplate對象,用于支持參數(shù)命名化的JdbcTemplate增強。
10.2.3 配置事務(wù)管理器
源碼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,用于支持基于數(shù)據(jù)源的事務(wù)控制。
······
本節(jié)完,更多內(nèi)容請查閱分類專欄:SpringBoot源碼解讀與原理分析