從jdbc到spring-boot-starter-jdbchtml
JDBC是一種用於執行SQL語句的API,能夠爲多種關係數據庫提供統一訪問,它是由一組用Java語言編寫的類和接口。是Java訪問數據庫的標準規範。java
JDBC是Java提供的一種標準規範,具體的實現由各個數據庫廠商去實現。對開發者來講屏蔽了不一樣數據庫之間的區別,可使用相同的方式(Java API)去操做不一樣的數據庫。兩個設備之間要進行通訊須要驅動,不一樣數據庫廠商對JDBC的實現類就是去鏈接數據庫的驅動。如mysql-connector-java
鏈接mysql
數據庫的驅動。mysql
// mysql 數據庫:「com.mysql.jdbc.Driver」 Class.forName(driver);
Connection conn=DriverManager.getConnection(url,userName,password);
Statement statement =conn.createStatement();
ResultSet rs =statement.executeQuery(sql);
在使用JDBC進行數據庫操做過程當中,每次使用就要建立鏈接,同時使用完畢還必須得關閉鏈接,操做繁瑣容易出錯,而且Connection的取得和釋放是代價比較高的操做。解決這個問題的方法就是鏈接池。鏈接池就是事先取得必定數量的Connection,程序執行處理的時候不是新建Connection,而是取得預先準備好的Connection。web
提供鏈接池機能的技術叫作DataSource。DataSource是JDK提供一個標準接口在javax.sql.DataSource
包下。常見的DBCP、C3P0、druid等。spring
spring-boot-starter-jdbc
主要提供了三個功能,第一個就是對數據源的裝配,第二個就是提供一個JdbcTemplate簡化使用,第三個就是事務sql
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()); } }
代碼邏輯以下:數據庫
打印結果以下apache
>>>>>>>>>>>>>>>>>服務啓動執行 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
能夠看到api
當前的pom文件中僅僅配置了spring-boot-starter-jdbc和mysql數據庫驅動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> <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>
在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、Dbcp二、Generic、DataSourceJmxConfiguration這五種數據源。從上面的數據源和鏈接信息的打印能夠知道默認狀況下Springboot裝配的是Hikari數據源。
在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>
打印信息以下:
>>>>>>>>>>>>>>>>>服務啓動執行 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
打印結果以下:
>>>>>>>>>>>>>>>>>服務啓動執行 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]]
對於Springboot默認支持的五種數據源,能夠經過上面兩種方式(1、排除默認數據源,添加使用的數據源;2、添加使用的數據源,使用配置文件指定使用的數據源) 進行選擇使用數據源。若是是其餘開源的數據源呢?好比阿里的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
打印結果以下:
>>>>>>>>>>>>>>>>>服務啓動執行 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()); } }
打印結果以下:
>>>>>>>>>>>>>>>>>服務啓動執行 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
Spring對數據庫的操做在jdbc上面作了深層次的封裝。使用Spring的注入功能,能夠把DataSource註冊到JdbcTemplate之中。
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; }