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