從jdbc到spring-boot-starter-jdbc
jdbc 是什么
JDBC是一種用於執行SQL語句的API,可以為多種關系數據庫提供統一訪問,它是由一組用Java語言編寫的類和接口。是Java訪問數據庫的標准規范。
JDBC是Java提供的一種標准規范,具體的實現由各個數據庫廠商去實現。對開發者來說屏蔽了不同數據庫之間的區別,可以使用相同的方式(Java API)去操作不同的數據庫。兩個設備之間要進行通信需要驅動,不同數據庫廠商對JDBC的實現類就是去連接數據庫的驅動。如mysql-connector-java 連接mysql數據庫的驅動。
使用JDBC連接數據庫的步驟
- 注冊驅動,這里的執行 就需要驅動jar包
// mysql 數據庫:“com.mysql.jdbc.Driver”
Class.forName(driver);
- 建立數據庫連接 Connection
Connection conn=DriverManager.getConnection(url,userName,password);
- 創建Statement對象 用來執行SQL語句
Statement statement =conn.createStatement();
- 執行SQL語句
ResultSet rs =statement.executeQuery(sql);
- 處理結果
- 釋放資源
數據庫連接池
在使用JDBC進行數據庫操作過程中,每次使用就要創建連接,同時使用完畢還必須得關閉連接,操作繁瑣容易出錯,並且Connection的取得和釋放是代價比較高的操作。解決這個問題的方法就是連接池。連接池就是事先取得一定數量的Connection,程序執行處理的時候不是新建Connection,而是取得預先准備好的Connection。
DataSource
提供連接池機能的技術叫做DataSource。DataSource是JDK提供一個標准接口在javax.sql.DataSource包下。常見的DBCP、C3P0、druid等。
spring-boot-starter-jdbc
spring-boot-starter-jdbc主要提供了三個功能,第一個就是對數據源的裝配,第二個就是提供一個JdbcTemplate簡化使用,第三個就是事務
數據源相關使用
查看數據源和連接信息
package com.lucky.spring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootApplication
public class Application implements CommandLineRunner {
Logger logger = LoggerFactory.getLogger(Application.class);
@Autowired
DataSource dataSource;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(">>>>>>>>>>>>>>>>>服務啟動執行");
showConnection();
}
private void showConnection() throws SQLException {
logger.info("dataSource:{}", dataSource.getClass().getName());
Connection connection = dataSource.getConnection();
logger.info("connection:{}", connection.toString());
}
}
代碼邏輯如下:
- 通過CommandLineRunner監聽服務的啟動,在啟動后調用showConnection方法
- 在showConnection方法里打印出當前的DataSource實現類,獲取一個連接並打印該連接的基本信息
打印結果如下
>>>>>>>>>>>>>>>>>服務啟動執行
2020-07-12 07:38:42.076 INFO 8144 --- [ main] com.lucky.spring.Application : dataSource:com.zaxxer.hikari.HikariDataSource
2020-07-12 07:38:42.077 INFO 8144 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-07-12 07:38:42.274 INFO 8144 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2020-07-12 07:38:42.277 INFO 8144 --- [ main] com.lucky.spring.Application : connection:HikariProxyConnection@1366499339 wrapping com.mysql.jdbc.JDBC4Connection@25c5e994
可以看到
- dataSource使用的是:com.zaxxer.hikari.HikariDataSource
- connection信息是:HikariProxyConnection@1366499339 wrapping com.mysql.jdbc.JDBC4Connection@25c5e994
當前的pom文件中僅僅配置了spring-boot-starter-jdbc和mysql數據庫驅動
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<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>
</dependencies>
Springboot支持的數據源
在DataSourceAutoConfiguration類的內部類PooledDataSourceConfiguration標識了默認支持的數據源。
@Configuration
@Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class})
protected static class PooledDataSourceConfiguration {
protected PooledDataSourceConfiguration() {
}
}
默認支持Hikari、Tomcat、Dbcp2、Generic、DataSourceJmxConfiguration這五種數據源。從上面的數據源和連接信息的打印可以知道默認情況下Springboot裝配的是Hikari數據源。
自動裝配Tomcat數據源
在DataSourceConfiguration中Tomcat數據源的實現如下
@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
)
static class Tomcat {
Tomcat() {
}
@Bean
@ConfigurationProperties(
prefix = "spring.datasource.tomcat"
)
public org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
org.apache.tomcat.jdbc.pool.DataSource dataSource = (org.apache.tomcat.jdbc.pool.DataSource)DataSourceConfiguration.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;
}
}
讓Springboot自動裝配選擇Tomcat的方式有兩種
第一種
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--默認配置 start-->
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-jdbc</artifactId>-->
<!--</dependency>-->
<!--默認配置 end-->
<!--使用tomcat數據源 方式 start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!--使用tomcat數據源 方式 end-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
- 加入tomcat-jdbc數據源的依賴
- 排除Hikari的數據源依賴
打印信息如下:
>>>>>>>>>>>>>>>>>服務啟動執行
2020-07-12 08:11:49.761 INFO 8469 --- [ main] com.lucky.spring.Application : dataSource:org.apache.tomcat.jdbc.pool.DataSource
2020-07-12 08:11:50.058 INFO 8469 --- [ main] com.lucky.spring.Application : connection:ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@4d6f197e]]
第二種
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--默認配置 start-->
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-jdbc</artifactId>-->
<!--</dependency>-->
<!--默認配置 end-->
<!--使用tomcat數據源 方式 start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!--使用tomcat數據源 方式 end-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
spring:
datasource:
url: jdbc:mysql://localhost:3306/readinglist?characterEncoding=utf8&useSSL=false
username: root
password: 12345678
type: org.apache.tomcat.jdbc.pool.DataSource
- pom文件中添加tomcat-jdbc依賴
- 在application.yml文件中指定數據源為org.apache.tomcat.jdbc.pool.DataSource
打印結果如下:
>>>>>>>>>>>>>>>>>服務啟動執行
2020-07-12 08:15:51.746 INFO 8525 --- [ main] com.lucky.spring.Application : dataSource:org.apache.tomcat.jdbc.pool.DataSource
2020-07-12 08:15:52.152 INFO 8525 --- [ main] com.lucky.spring.Application : connection:ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@5173200b]]
使用druid數據源
對於Springboot默認支持的五種數據源,可以通過上面兩種方式(一、排除默認數據源,添加使用的數據源;二、添加使用的數據源,使用配置文件指定使用的數據源) 進行選擇使用數據源。如果是其他開源的數據源呢?比如阿里的druid數據源。也是有兩種方式。
第一種
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
spring:
datasource:
url: jdbc:mysql://localhost:3306/readinglist?characterEncoding=utf8&useSSL=false
username: root
password: 12345678
type: com.alibaba.druid.pool.DruidDataSource
- 添加druid數據源依賴
- 在application.yml文件中指定數據源為com.alibaba.druid.pool.DruidDataSource
打印結果如下:
>>>>>>>>>>>>>>>>>服務啟動執行
2020-07-12 08:27:52.523 INFO 8813 --- [ main] com.lucky.spring.Application : dataSource:com.alibaba.druid.pool.DruidDataSource
2020-07-12 08:27:52.562 INFO 8813 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2020-07-12 08:27:52.883 INFO 8813 --- [ main] com.lucky.spring.Application : connection:com.mysql.jdbc.JDBC4Connection@3b0ca5e1
第二種
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
package com.lucky.spring;
import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootApplication
public class Application implements CommandLineRunner {
Logger logger = LoggerFactory.getLogger(Application.class);
@Autowired
DataSource dataSource;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(">>>>>>>>>>>>>>>>>服務啟動執行");
showConnection();
}
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/readinglist?characterEncoding=utf8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("12345678");
return dataSource;
}
private void showConnection() throws SQLException {
logger.info("dataSource:{}", dataSource.getClass().getName());
Connection connection = dataSource.getConnection();
logger.info("connection:{}", connection.toString());
}
}
- 添加druid依賴
- 創建DataSource的bean,進行相關配置后,返回DruidDataSource
打印結果如下:
>>>>>>>>>>>>>>>>>服務啟動執行
2020-07-12 08:37:09.898 INFO 9140 --- [ main] com.lucky.spring.Application : dataSource:com.alibaba.druid.pool.DruidDataSource
2020-07-12 08:37:09.951 INFO 9140 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2020-07-12 08:37:10.314 INFO 9140 --- [ main] com.lucky.spring.Application : connection:com.mysql.jdbc.JDBC4Connection@d400943
JdbcTemplate相關使用
JdbcTemplate 是什么
Spring對數據庫的操作在jdbc上面做了深層次的封裝。使用Spring的注入功能,可以把DataSource注冊到JdbcTemplate之中。
JdbcTemplate主要提供了一下五類方法
- execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句
- update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句
- query方法及queryForXXX方法:用於執行查詢相關語句
- call方法:用於執行存儲過程、函數相關語句
事務的相關使用
Springboot中在需要使用事務的方法上面添加@Transactional,需要注意的是,默認只會對運行時異常進行事務回滾,非運行時異常不會回滾事務。
Controller層定義了兩個接口
@RestController
public class DataOperationController {
@Autowired
DataOpeService service;
@GetMapping("/api/queryData")
public String queryData() {
return service.queryData();
}
@PostMapping("/api/addData")
public String addData() {
try {
service.addData();
return "add data success";
} catch (Exception e) {
e.printStackTrace();
return "add data fail";
}
}
}
Service通過JdbcTemplate執行sql
@Service
public class DataOpeServiceImpl implements DataOpeService {
private Logger logger = LoggerFactory.getLogger(DataOpeServiceImpl.class);
@Autowired
private JdbcTemplate template;
@Override
public String queryData() {
String sql = "select * from t where id=1";
RowMapper<T> data = new BeanPropertyRowMapper<>(T.class);
T t = template.queryForObject(sql, data);
return t.toString();
}
@Override
public String addData() {
List<T> data = new ArrayList<>();
for (int i = 0; i < 2; i++) {
T item = new T();
item.setA(i);
item.setB(i);
data.add(item);
}
for (int i = 0; i < data.size(); i++) {
String sql = "insert into t(a,b) values (" + data.get(i).getA() + "," + data.get(i).getB() + ")";
logger.info("sql:{}", sql);
template.execute(sql);
}
return null;
}
}
運行時異常
修改代碼,人為在添加第二條記錄時拋出異常。
@Transactional
@Override
public String addData() {
List<T> data = new ArrayList<>();
for (int i = 0; i < 2; i++) {
T item = new T();
item.setA(i);
item.setB(i);
data.add(item);
}
for (int i = 0; i < data.size(); i++) {
String sql = "insert into t(a,b) values (" + data.get(i).getA() + "," + data.get(i).getB() + ")";
logger.info("sql:{}", sql);
if (data.get(i).getA() == 1) {
throw new NullPointerException("人為拋出運行時異常異常");
}
template.execute(sql);
}
return null;
}
調用接口,發現事務生效,即發生運行時異常進行了代碼回滾。
非運行時異常
重新修改代碼,人為拋出非運行時異常。
@Transactional
@Override
public String addData() throws Exception{
List<T> data = new ArrayList<>();
for (int i = 0; i < 2; i++) {
T item = new T();
item.setA(i);
item.setB(i);
data.add(item);
}
for (int i = 0; i < data.size(); i++) {
String sql = "insert into t(a,b) values (" + data.get(i).getA() + "," + data.get(i).getB() + ")";
logger.info("sql:{}", sql);
if (data.get(i).getA() == 1) {
// throw new NullPointerException("人為拋出運行時異常異常");
throw new FileNotFoundException("人為拋出非運行時異常");
}
template.execute(sql);
}
return null;
}
調用接口,發現事務沒有生效,即第一條數據插入到了數據庫里。
指定回滾時的異常
如果要使得非運行時期異常也回滾,那么在使用@Transactional注解時,指定rollbackFor屬性。
@Transactional(rollbackFor = Exception.class)
@Override
public String addData() throws Exception{
List<T> data = new ArrayList<>();
for (int i = 0; i < 2; i++) {
T item = new T();
item.setA(i);
item.setB(i);
data.add(item);
}
for (int i = 0; i < data.size(); i++) {
String sql = "insert into t(a,b) values (" + data.get(i).getA() + "," + data.get(i).getB() + ")";
logger.info("sql:{}", sql);
if (data.get(i).getA() == 1) {
// throw new NullPointerException("人為拋出運行時異常異常");
throw new FileNotFoundException("人為拋出非運行時異常");
}
template.execute(sql);
}
return null;
}
