spring JDBC持久化设计


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接口
    8Yb6aR.png

  • 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实现了事务的执行方法

  • 注意点
  1. 注解不能被继承,所以业务接口中的@Transactional注解不会被业务实现类继承;方法处的注解会覆盖类定义处的注解
  2. 对于基于接口动态代理的AOP事务,由于接口方法都是public的,实现类的实现方法必须是public的,同时不能使用static修饰符。因此,可以通过接口动态代理实施AOP增强、实现Spring事务的方法只能是public或public final的
  3. 基于CGLib动态代理实施AOP的时候,由于使用final、static、private的方法不能被子类覆盖,相应的,这些方法不能实施AOP增强,实现事务
  4. 不能被Spring进行AOP事务增强的方法不能启动事务,但是外层方法的事务上下文仍然可以传播到这些方法中

  TOC