内容
数据源DataSource
事务Transation
JDBC4.0(JSR-221)
问题及回答
数据源
数据源是数据库连接的来源,通过DataSource接口获取。
类型
通用型数据源
分布式数据源
嵌入式数据源
org.springframework.jdbc.datasource.embedded.EmbeddedDatasource
Spring Boot实际使用场景
在Spring Boot 2.0.0
如果采用Spring WebMVC作为Web服务,默认情况下,使用嵌入式Tomcat。
如果采用Spring WebFlux,默认情况下,使用嵌入式Netty Web Server。
传统的Servlet采用HttpServletRequest、HttpServletResponse
WebFlux采用:ServletRequest、ServletResponse
不再限制于Servlet容器,可以选择自定义实现,比如Netty Web Server
单数据源的场景 数据库连接池技术
Apache Commons DBCP
commons-dbcp2
commons-dbcp
Tomcat DBCP
代码示例
创建项目
启动项目
会报错:Cannot determine embedded database driver class for database type NONE
如果没有配置数据源,就将pom文件下jdbc
依赖注释掉。
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency >
报错源码
org.springframework.boot.autoconfigure.jdbc
包下
DataSourceConfiguration.Hikari#dataSource
-> DataSourceConfiguration#createDataSource
-> DataSourceProperties#initializeDataSourceBuilder
->
DataSourceProperties#determineDriverClassName
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public String determineDriverClassName () { if (StringUtils.hasText(this .driverClassName)) { Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this .driverClassName); return this .driverClassName; } String driverClassName = null ; if (StringUtils.hasText(this .url)) { driverClassName = DatabaseDriver.fromJdbcUrl(this .url).getDriverClassName(); } if (!StringUtils.hasText(driverClassName)) { driverClassName = this .embeddedDatabaseConnection.getDriverClassName(); } if (!StringUtils.hasText(driverClassName)) { throw new DataSourceBeanCreationException(this .embeddedDatabaseConnection, this .environment, "driver class" ); } return driverClassName; }
配置数据源
1 2 3 4 @ConfigurationProperties (prefix = "spring.datasource" )public class DataSourceProperties
多数据源的场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.bai.springbootjdbc.config;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import javax.sql.DataSource;@Configuration public class MultipleDataSourceConfiguration { @Bean @Primary public DataSource masterDataSource () { DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); DataSource dataSource = dataSourceBuilder.driverClassName("com.mysql.jdbc.Driver" ) .url("jdbc://localhost:3306/test" ) .username("root" ) .password("root" ) .build(); return dataSource; } @Bean public DataSource slaveDataSource () { DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); DataSource dataSource = dataSourceBuilder.driverClassName("com.mysql.jdbc.Driver" ) .url("jdbc://localhost:3306/test2" ) .username("root" ) .password("root" ) .build(); return dataSource; } }
考虑到容灾、负载均衡的情况,更多的是通过MySQL的代理来做。
配置多数据源不能通过application.properties
,是因为无法区分Bean的名称。
事务Transaction
事务用于提供数据完整性,并在并发访问下确保数据视图的一致性。
概念
自动提交模式Auto-commitmode
事务隔离级别Transaction isolation levels
保护点Savepoints
自动提交模式和手动提交 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public boolean save (User user) { boolean result = false ; Connection connection = null ; try { connection = this .masterDataSource.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO users(name) VALUES (?);" ); preparedStatement.setString(1 , user.getName()); result = preparedStatement.executeUpdate() > 0 ? true : false ; preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } finally { if (connection != null ) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } }; } return result; }
Annotation 驱动 1 2 3 4 5 6 7 8 9 10 11 12 13 @Transactional public boolean save (User user) { boolean result = false ; result=jdbcTemplate.execute("INSERT INTO users(name) VALUES (?);" , new PreparedStatementCallback<Boolean>() { @Nullable @Override public Boolean doInPreparedStatement (PreparedStatement preparedStatement) throws SQLException, DataAccessException { preparedStatement.setString(1 , user.getName()); return preparedStatement.executeUpdate() > 0 ; } }); return result; }
事务隔离级别Transaction isolation levels
脏读 :脏读又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。
不可重复读 :不可重复读的重点是修改 。不可重复读是指在同一个事务内,两个相同的查询返回了不同的结果。
幻读 :幻读的重点在于新增或者删除 。统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样。这就叫幻读。
java.sql.Connection
中四个属性
TRANSACTION_READ_UNCOMMITTED
TRANSACTION_READ_COMMITTED
TRANSACTION_REPEATABLE_READ
TRANSACTION_SERIALIZABLE
脏读、不可重复读、幻读的级别高低是:脏读 < 不可重复读 < 幻读。
级别越高性能越差
Spring JDBC Transaction实现重写了JDBC API:
org.springframework.transaction.annotation.Isolation
->org.springframework.transaction.TransactionDefinition
->
java.sql.Connection
通过AOP的方式代理了Connection,自动提交模式关闭,经过一系列操作后,方法执行完毕,再提交事务。
@Transaction执行代理-TransactionInterceptor
org.springframework.transaction.annotation.Transactional
可以控制rollback的异常粒度
rollbackFor
方法以及noRollbackFor
方法
可以执行事务管理器
API驱动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Transactional public boolean save (User user) { boolean result = false ; DefaultTransactionAttribute defaultTransactionAttribute=new DefaultTransactionAttribute(); TransactionStatus transactionStatus=platformTransactionManager.getTransaction(defaultTransactionAttribute); result=jdbcTemplate.execute("INSERT INTO users(name) VALUES (?);" , new PreparedStatementCallback<Boolean>() { @Nullable @Override public Boolean doInPreparedStatement (PreparedStatement preparedStatement) throws SQLException, DataAccessException { preparedStatement.setString(1 , user.getName()); return preparedStatement.executeUpdate() > 0 ; } }); platformTransactionManager.commit(transactionStatus); return result; }
场景选择
是否事务嵌套,这就涉及到了事务传播
打了@Transactional
标签的save
方法调用没有打@Transactional
的save2
方法,单独调用save2
是没有事务的
org.springframework.transaction.annotation.Propagation
REQUIRED
SUPPORTS
MANDATORY
REQUIRES_NEW
NOT_SUPPORTED
NEVER
NESTED
保护点Savepoints 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public boolean jdbcSave (User user) { boolean result = false ; Connection connection = null ; try { connection = this .masterDataSource.getConnection(); connection.setAutoCommit(false ); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO users(name) VALUES (?);" ); preparedStatement.setString(1 , user.getName()); result = preparedStatement.executeUpdate() > 0 ? true : false ; Savepoint savepoint = connection.setSavepoint("T1" ); try { transactionalSave(user); } catch (Exception ex) { connection.rollback(savepoint); } connection.commit(); connection.releaseSavepoint(savepoint); preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } finally { if (connection != null ) { try { connection.commit(); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } return result; } @Transactional public boolean transactionalSave (User user) { boolean result = false ; result = jdbcTemplate.execute("INSERT INTO logs(name1) VALUES (?);" , new PreparedStatementCallback<Boolean>() { @Nullable @Override public Boolean doInPreparedStatement (PreparedStatement preparedStatement) throws SQLException, DataAccessException { preparedStatement.setString(1 , "rollback" ); return preparedStatement.executeUpdate() > 0 ; } }); return result; }
jdbcSave
调用transactionalSave
,在transactionalSave
内部故意写错列名导致发生错误,最后结果是jdbcSave
可以正常提交保存成功,transactionalSave
被回滚,数据不会插入到数据库中。
问题&解答 Q:用reactive web,原来MVC的好多东西都不能用了?
A:不是,Reactive Web还是能够兼容Spring Web Mvc
Q:SP1调用SP2,如果SP2是注解新开一个事务的话,那么和嵌套事务有什么区别?
A:NESTED
是根据JDBC驱动来实现的,不是Spring实现的,不一定所有数据库都支持。REQUIRES_NEW
有独立的事务环境,NESTED
是共享的(commit和rollback)
Q:开个线程池事务控制API方式?比如写的Excutor.fixExcutor(5)
A:TransactionSynchronizationManager
使用大量的ThreadLocal
来实现的。
Q:假设一个service方法打了@Transation
,在这个方法中还有其它service的某个方法,这个方法没有加@Transation
,那么如果内部方法报错,会回滚吗?
A:会。当前可以过滤掉一些无关紧要的异常noRollbackfor()。
Q:Spring 分布式事务生产环境实现方式有哪些?
A:Distributed Transactions with JTA