天天看点

Spring学习系列之JdbcTemplate

0x01 为什么会有JdbcTemplate

原因一:代码重复性

JDBC编程需要我们获取数据库连接,然后进行增删改查,最后关闭一些资源,然后还要捕获异常。那么这些代码都是重复的,我们可以通过

模板方法模式

去简化这些代码

原因二:异常体系

根据JDBC规范,不同实现厂商会抛出

SQLException

异常,这个异常通过

ErrorCode

来区分不同错误,但是不同厂商针对同一个错误提供的

ErrorCode

可能不同,那么需要我们统一处理这些差异

JdbcTemplate

就解决了上述两个问题。

0x02 相关类讲解

JdbcTemplate

继承了

JdbcAccessor

类,并实现了

JdbcOprations

接口。

JdbcOprations

该接口提供了所有JDBC相关需要实现的方法

JdbcAccessor

该类是一个抽象类。

实现了Spring的

InitializingBean

接口,在

afterPropertiesSet

方法中,根据

lazyInit

属性加载异常转换器(就是将不同

ErrorCode

SQLException

封装成Spring的

DataAccessException

),调用的方法是

getExceptionTranslator

方法

public synchronized SQLExceptionTranslator getExceptionTranslator() {
		if (this.exceptionTranslator == null) {
			DataSource dataSource = getDataSource();
			if (dataSource != null) {
				this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
			}
			else {
				this.exceptionTranslator = new SQLStateSQLExceptionTranslator();
			}
		}
		return this.exceptionTranslator;
	}
           

DataSource

是在JDBC2.0之后引入的,代替了

DriverManager

的数据库连接创建方式

JdbcTemplate

该类采用了

模板方法模式

,同时配合回调方法

// 该方法会被其他方法调用
// 比如query方法,query方法中会有实现StatementCallback接口的一个query内部类
@Override
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
	Assert.notNull(action, "Callback object must not be null");
	// 此处通过该工具类获取Connection会被绑定在当前线程中,以便Spring的统一事务使用
	Connection con = DataSourceUtils.getConnection(getDataSource());
	Statement stmt = null;
	try {
		Connection conToUse = con;
		// 当需要使用原始数据库厂商提供的API的时候
		if (this.nativeJdbcExtractor != null &&
				this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
			conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
		}
		stmt = conToUse.createStatement();
		// 控制fetchSize、maxRows、queryTimeout
		applyStatementSettings(stmt);
		Statement stmtToUse = stmt;
		if (this.nativeJdbcExtractor != null) {
			stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
		}
		T result = action.doInStatement(stmtToUse);
		handleWarnings(stmt);
		return result;
	}
	catch (SQLException ex) {
		// Release Connection early, to avoid potential connection pool deadlock
		// in the case when the exception translator hasn't been initialized yet.
		JdbcUtils.closeStatement(stmt);
		stmt = null;
		DataSourceUtils.releaseConnection(con, getDataSource());
		con = null;
		throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
	}
	finally {
		JdbcUtils.closeStatement(stmt);
		DataSourceUtils.releaseConnection(con, getDataSource());
	}
}
           

0x03 异常转换

顶级接口

SQLExceptionTranslator

,其子类有

SQLErrorCodeSQLExceptionTranslator

SQLStateSQLExceptionTranslator

SQLExceptionSubclassTranslator

ErrorCode

的分析要比

SQLState

要准确

这些子类还有一个统一抽象父类

AbstractFallbackSQLExceptionTranslator

,在该类中实现了

SQLExceptionTranslator

translate

方法

@Override
public DataAccessException translate(String task, String sql, SQLException ex) {
	Assert.notNull(ex, "Cannot translate a null SQLException");
	if (task == null) {
		task = "";
	}
	if (sql == null) {
		sql = "";
	}
	// 该类中提供了抽象方法doTranslate供三个子类实现
	DataAccessException dex = doTranslate(task, sql, ex);
	if (dex != null) {
		// Specific exception match found.
		return dex;
	}
	// 若无子类返回异常,则调用失败机制的异常转换器
	// 上述三个子类在构造器中都会调用setFallbackTranslator方法
	SQLExceptionTranslator fallback = getFallbackTranslator();
	if (fallback != null) {
		return fallback.translate(task, sql, ex);
	}
	// We couldn't identify it more precisely.
	return new UncategorizedSQLException(task, sql, ex);
}
           

SQLExceptionSubclassTranslator

该转换器简单重写了

doTranslate

方法,通过

instanceof

判断然后重新

new

一个包装异常

SQLStateSQLExceptionTranslator

也是重写了

doTranslate

方法,然后通过各种

Set

来判断属于哪个异常,然后抛出

SQLErrorCodeSQLExceptionTranslator

doTranslate

首先调用了

customTranslate

方法,可以继承该转换器然后重写

customTranslate

方法来实现自己的转译

该类中的

sqlErrorCodes

属性会在构造器中初始化

public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) {
	this();
	setDataSource(dataSource);
}
public void setDataSource(DataSource dataSource) {
	this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
}
           

SQLErrorCodesFactory

是单例模式(其实继承该类可以重写加载方式),重点在于其构造方法

protected SQLErrorCodesFactory() {
	Map<String, SQLErrorCodes> errorCodes;

	try {
		// 通过BeanFactory去加载配置
		DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
		lbf.setBeanClassLoader(getClass().getClassLoader());
		XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(lbf);

		// 加载默认配置org/springframework/jdbc/support/sql-error-codes.xml
		Resource resource = loadResource(SQL_ERROR_CODE_DEFAULT_PATH);
		if (resource != null && resource.exists()) {
			bdr.loadBeanDefinitions(resource);
		}
		else {
			logger.warn("Default sql-error-codes.xml not found (should be included in spring.jar)");
		}

		// 如果当前CLASSPATH下存在sql-error-codes.xml则覆盖
		resource = loadResource(SQL_ERROR_CODE_OVERRIDE_PATH);
		if (resource != null && resource.exists()) {
			bdr.loadBeanDefinitions(resource);
			logger.info("Found custom sql-error-codes.xml file at the root of the classpath");
		}

		// Check all beans of type SQLErrorCodes.
		errorCodes = lbf.getBeansOfType(SQLErrorCodes.class, true, false);
		if (logger.isInfoEnabled()) {
			logger.info("SQLErrorCodes loaded: " + errorCodes.keySet());
		}
	}
	catch (BeansException ex) {
		logger.warn("Error loading SQL error codes from config file", ex);
		errorCodes = Collections.emptyMap();
	}

	this.errorCodesMap = errorCodes;
}
           

如果

SQLErrorCode

无法转译,那么就只能求助于

SQLState

到此我们可以发现有两处扩展点,第一就是继承

SQLErrorCodeSQLExceptionTranslator

重写

customTranslate

,第二就是自定义

sql-error-codes.xml