spring JDBC持久化设计
spring中大量运用到模板模式,简单回顾一下模板模式就是
抽象类中定义好产品的步骤,不同的子类去实现相同的步骤方法,客户端调用抽象类的方法,执行统一的产品步骤,从而返回结果有点类似于桥接模式的一半,将建造过程抽象出来,形成统一的模板方法。可以使用钩子方法改造模板方法流程,从而增加健壮性。
JdbcTemplate分析
使用代码
server:
port: 8080
spring:
profiles:
active: dev
datasource:
username: root
password: 111111
url: jdbc:mysql://127.0.0.1:3306/tables?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
- UserDO.java
@Data
public class UserDO {
private Integer id;
private String userName;
private String password;
}
- UserRowMapper.java
public class UserRowMapper implements RowMapper<UserDO> {
@Override
public UserDO mapRow(ResultSet rs, int rowNum) throws SQLException {
UserDO userDO = new UserDO();
userDO.setId(rs.getInt("id"));
userDO.setUserName(rs.getString("user_name"));
userDO.setPassword(rs.getString("pass_word"));
return userDO;
}
}
- UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public UserDO getById(int id) {
String sql = "select * from t_user where id = ? ";
List<UserDO> stu = jdbcTemplate.query(sql,new Object[]{id}, new UserRowMapper());
UserDO student = null;
if(!stu.isEmpty()){
student = stu.get(0);
}
return student;
}
@Override
public int add(UserDO userDO) {
String sql ="insert into student(id,user_name,pass_word) values(null,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
int resRow = jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(sql,new String[]{"id"});
ps.setString(1,userDO.getUserName());
ps.setString(2,userDO.getPassword());
return ps;
}
},keyHolder);
return Integer.parseInt(keyHolder.getKey().toString());
}
}
- Test.java
@Resource
private UserService service;
@Test
public void selectTest() {
UserDO user = service.getById(2);
System.out.println(user);
}
@Test
public void addTest() {
UserDO userDO = new UserDO();
userDO.setUserName("add");
userDO.setPassword("password");
Integer userId = service.add(userDO);
System.out.println(userId);
}
**private JdbcTemplate jdbcTemplate;**是由JdbcTemplateAutoConfiguration加载到容器中的;
-
JdbcTemplate实现了JdbcAccessor、JdbcOperations接口
-
query
@Nullable
public <T> T query(
PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
Assert.notNull(rse, "ResultSetExtractor must not be null");
logger.debug("Executing prepared SQL query");
return execute(psc, new PreparedStatementCallback<T>() {
@Override
@Nullable
public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);
}
rs = ps.executeQuery();
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
- execute
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
applyStatementSettings(ps);
T result = action.doInPreparedStatement(ps);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
}
finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
execute是一个模板方法,将connection的获取和关闭抽象出来作为执行,传入的action作为扩展实现的方法。
Connection
Connection是通过DataSourceUtils.getConnection(DataSource dataSource)来获取的
- doGetConnection
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
catch (RuntimeException ex) {
// Unexpected exception from external delegation call -> close Connection and rethrow.
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
- TransactionSynchronizationManager.resource
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
resource是一个线程变量,当同一个线程尝试获取jdbc Connection时返回的是同一个Connection
当Connection获取不到时就会从DataSource中获取新的jdbc连接,并放到resource中
事务
spring中事务传播级别分为
支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
spring中事务隔离级别分为
- TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务的远行原理
-
TransactionAspectSupport
TransactionAspectSupport implements BeanFactoryAware, InitializingBean
这里有两个比较重要的方法 -
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
createTransactionIfNecessary() -> AbstractPlatformTransactionManager.getTransaction(txAttr) -> doBegin(transaction, definition)
con = txObject.getConnectionHolder().getConnection();
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
在doBegin方法中获取到设置一个con.setAutoCommit(false);
- completeTransactionAfterThrowing(txInfo, ex);
completeTransactionAfterThrowing(txInfo, ex) -> txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()) ->processRollback(defStatus, false) ->doRollback
调用AbstractPlatformTransactionManager的doRollback方法执行的回滚,这里使用的也是模板模式不同的连接方式有不同的回滚逻辑,例如DataSourceTransactionManager的回滚方法就是调用con.rollback()(数据库驱动程序的rollback)
如何织入的?
spring容器在启动的时候会将 @Transactional注解标注的方法先进行一个配置解析,再进行一个aop的增强,当执行这个方法时候会先执行aop的方法开启事务,通过反射将目标方法执行完毕后在通过结过进行对应的一个处理
总结:
-
Spring基于ThreadLocal解决有状态的Connetion的并发问题,事务同步管理器org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal为不同事务线程提供独立的资源副本
-
Spring事务管理基于3个接口:TransactionDefinition,TransactionStatus和PlatformTransactionManager
TransactionDefinition定义了传播级别和隔离级别
TransactionStatus定义了事务的状态
PlatformTransactionManager实现了事务的执行方法
- 注意点
- 注解不能被继承,所以业务接口中的@Transactional注解不会被业务实现类继承;方法处的注解会覆盖类定义处的注解
- 对于基于接口动态代理的AOP事务,由于接口方法都是public的,实现类的实现方法必须是public的,同时不能使用static修饰符。因此,可以通过接口动态代理实施AOP增强、实现Spring事务的方法只能是public或public final的
- 基于CGLib动态代理实施AOP的时候,由于使用final、static、private的方法不能被子类覆盖,相应的,这些方法不能实施AOP增强,实现事务
- 不能被Spring进行AOP事务增强的方法不能启动事务,但是外层方法的事务上下文仍然可以传播到这些方法中