从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; }