一、SpringBoot的数据访问方式
对于数据访问层,无论是SQL还是NOSQL,SpringBoot默认采用整合SpringData的方式进行统一处理,SpringBoot中添加了大量的自动配置,屏蔽了很多设置。引入各种XxxTemplate,XxxRepository来简化我们对数据访问层的操作,对我们来说只需要进行简单的设置即可。
二、SpringBoot整合JDBC
1、创建项目,引入Jdbc的启动器和Mysql驱动
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、在application.properties或application.yml中配置数据库连接
spring:
datasource:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.2.107:3306/docker_jdbc
3、测试数据源配置
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootJdbcApplicationTests {
@Autowired
DataSource dataSource;
@Test
public void contextLoads() {
try {
System.out.print(">>>>>" + dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.print(">>>>>"+connection);
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
结果:
>>>>>class com.zaxxer.hikari.HikariDataSource
>>>>>>HikariProxyConnection@886386381
低版本的SpringBoot使用org.apache.tomcat.jdbc.pool.DataSource作为默认数据源
三、自动配置原理
数据源的相关属性配置都在DataSourceProperties类里面,数据源自动配置相关的类都在org.springframework.boot.autoconfigure.jdbc包下:
1、数据源的配置类DataSourceConfiguration,该类会根据我们的配置决定使用哪种数据源,默认使用org.apache.tomcat.jdbc.pool.DataSource作为数据源:可以使用spring.datasource.type来改变数据源类型,比如改为c3p0
abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked")
protected static <T> T createDataSource(DataSourceProperties properties,
Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
/**
* Tomcat Pool DataSource configuration.
*/
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)//没有该项配置时,默认使用org.apache.tomcat.jdbc.pool.DataSource
static class Tomcat {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
public org.apache.tomcat.jdbc.pool.DataSource dataSource(
DataSourceProperties properties) {
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(
properties, org.apache.tomcat.jdbc.pool.DataSource.class);
DatabaseDriver databaseDriver = DatabaseDriver
.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
}
return dataSource;
}
}
/**
* Hikari DataSource configuration.
*/
@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")
public HikariDataSource dataSource(DataSourceProperties properties) {
return createDataSource(properties, HikariDataSource.class);
}
}
/**
* DBCP DataSource configuration.
*
* @deprecated as of 1.5 in favor of DBCP2
*/
@ConditionalOnClass(org.apache.commons.dbcp.BasicDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp.BasicDataSource", matchIfMissing = true)
@Deprecated
static class Dbcp {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dbcp")
public org.apache.commons.dbcp.BasicDataSource dataSource(
DataSourceProperties properties) {
org.apache.commons.dbcp.BasicDataSource dataSource = createDataSource(
properties, org.apache.commons.dbcp.BasicDataSource.class);
DatabaseDriver databaseDriver = DatabaseDriver
.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
}
return dataSource;
}
}
/**
* DBCP DataSource configuration.
*/
@ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource", matchIfMissing = true)
static class Dbcp2 {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
public org.apache.commons.dbcp2.BasicDataSource dataSource(
DataSourceProperties properties) {
return createDataSource(properties,
org.apache.commons.dbcp2.BasicDataSource.class);
}
}
/**
* Generic DataSource configuration.
*/
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
public DataSource dataSource(DataSourceProperties properties){
return properties.initializeDataSourceBuilder().build();
}
}
}
SpringBoot默认支持的数据源类型有:
org.apache.tomcat.jdbc.pool.DataSource
com.zaxxer.hikari.HikariDataSource
org.apache.commons.dbcp.BasicDataSource//即dbcp
org.apache.commons.dbcp2.BasicDataSource//即dbcp2
SpringBoot也支持自定义数据源:使用DataSourceBuilder创建数据源,利用反射创建相应type的数据源,并且绑定相关属性
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
}
2、数据源的自动配置类DataSourceAutoConfiguration:在该类中有一个DataSourceInitializer,可用来初始化数据:
/**
* Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
* {@link PostConstruct} and {@literal data-*.sql} SQL scripts on a
* {@link DataSourceInitializedEvent}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @since 1.1.0
* @see DataSourceAutoConfiguration
*/
class DataSourceInitializer implements ApplicationListener<DataSourceInitializedEvent> {
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
private final DataSourceProperties properties;
private final ApplicationContext applicationContext;
private DataSource dataSource;
private boolean initialized = false;
DataSourceInitializer(DataSourceProperties properties,
ApplicationContext applicationContext) {
this.properties = properties;
this.applicationContext = applicationContext;
}
@PostConstruct
public void init() {
if (!this.properties.isInitialize()) {
logger.debug("Initialization disabled (not running DDL scripts)");
return;
}
if (this.applicationContext.getBeanNamesForType(DataSource.class, false,
false).length > 0) {
this.dataSource = this.applicationContext.getBean(DataSource.class);
}
if (this.dataSource == null) {
logger.debug("No DataSource found so not initializing");
return;
}
runSchemaScripts();
}
private void runSchemaScripts() {
List<Resource> scripts = getScripts("spring.datasource.schema",
this.properties.getSchema(), "schema");
if (!scripts.isEmpty()) {
String username = this.properties.getSchemaUsername();
String password = this.properties.getSchemaPassword();
runScripts(scripts, username, password);
try {
this.applicationContext
.publishEvent(new DataSourceInitializedEvent(this.dataSource));
// The listener might not be registered yet, so don't rely on it.
if (!this.initialized) {
runDataScripts();
this.initialized = true;
}
}
catch (IllegalStateException ex) {
logger.warn("Could not send event to complete DataSource initialization ("
+ ex.getMessage() + ")");
}
}
}
@Override
public void onApplicationEvent(DataSourceInitializedEvent event) {
if (!this.properties.isInitialize()) {
logger.debug("Initialization disabled (not running data scripts)");
return;
}
// NOTE the event can happen more than once and
// the event datasource is not used here
if (!this.initialized) {
runDataScripts();
this.initialized = true;
}
}
private void runDataScripts() {
List<Resource> scripts = getScripts("spring.datasource.data",
this.properties.getData(), "data");
String username = this.properties.getDataUsername();
String password = this.properties.getDataPassword();
runScripts(scripts, username, password);
}
private List<Resource> getScripts(String propertyName, List<String> resources,
String fallback) {
if (resources != null) {
return getResources(propertyName, resources, true);
}
String platform = this.properties.getPlatform();
List<String> fallbackResources = new ArrayList<String>();
fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
fallbackResources.add("classpath*:" + fallback + ".sql");
return getResources(propertyName, fallbackResources, false);
}
private List<Resource> getResources(String propertyName, List<String> locations,
boolean validate) {
List<Resource> resources = new ArrayList<Resource>();
for (String location : locations) {
for (Resource resource : doGetResources(location)) {
if (resource.exists()) {
resources.add(resource);
}
else if (validate) {
throw new ResourceNotFoundException(propertyName, resource);
}
}
}
return resources;
}
private Resource[] doGetResources(String location) {
try {
SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(
this.applicationContext, Collections.singletonList(location));
factory.afterPropertiesSet();
return factory.getObject();
}
catch (Exception ex) {
throw new IllegalStateException("Unable to load resources from " + location,
ex);
}
}
private void runScripts(List<Resource> resources, String username, String password) {
if (resources.isEmpty()) {
return;
}
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(this.properties.isContinueOnError());
populator.setSeparator(this.properties.getSeparator());
if (this.properties.getSqlScriptEncoding() != null) {
populator.setSqlScriptEncoding(this.properties.getSqlScriptEncoding().name());
}
for (Resource resource : resources) {
populator.addScript(resource);
}
DataSource dataSource = this.dataSource;
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
dataSource = DataSourceBuilder.create(this.properties.getClassLoader())
.driverClassName(this.properties.determineDriverClassName())
.url(this.properties.determineUrl()).username(username)
.password(password).build();
}
DatabasePopulatorUtils.execute(populator, dataSource);
}
}
该类中有两个方法:
①runSchemaScripts():先执行,可运行建表语句
②runDataScripts():后执行,可运行插入数据的等语句
根据该类的注释可看出我们只需要将文件命名为:
schema‐*.sql或data‐*.sql
默认情况下使用schema.sql或者schema-all.sql来命名,并把该sql文件放在类路径的根目录下,在项目启动的时候就会加载并执行sql文件中的sql进行建表等操作:
上面是使用的SpringBoot默认的命名方式,我们也可以通过设置spring.datasource.schema来设置这些sql文件的名称和位置:data也是一样
spring:
datasource:
schema:
- classpath:department.sql
- classpath:employee.sql
3、进行数据库操作的配置类JdbcTemplateAutoConfiguration:在该类中SpringBoot在容器中添加了JdbcTemplate和NamedParameterJdbcTemplate:我们在使用的时候直接使用@Autowired注入即可
@Configuration
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class JdbcTemplateAutoConfiguration {
private final DataSource dataSource;
public JdbcTemplateAutoConfiguration(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
@Primary
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
@Bean
@Primary
@ConditionalOnMissingBean(NamedParameterJdbcOperations.class)
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(this.dataSource);
}
}
举例如下:
@RestController
public class JdbcController {
@Autowired
JdbcTemplate jdbcTemplate;
@RequestMapping("/query")
public Map<String, Object> query() {
List<Map<String, Object>> list = (List<Map<String, Object>>) jdbcTemplate.queryForList("select * from department");
return list.get(0);
}
}