本篇文章将回答如下几个问题html
首先但愿你能带着这些问题来看这篇文章,也但愿这篇文章能让你很好的解答这些问题。固然,这篇文章的终极目标是但愿你可以借鉴spring-jdbc 的思想来解决咱们在工做过程当中所面临的问题。
若是你想了解,如何使用spring-jdbc,请绕道......java
为了实现数据和业务的分离,有人提出了Dao模式。Dao模式是数据处理的一种理想模式,(我认为)它带来了两个方面的好处:一、屏蔽数据访问的差别性;二、业务与数据分离。spring-jdbc 在本质上是一种Dao模式的具体实现。(Dao模式的详细介绍)
接下下咱们用一个简单的例子(未具体实现)来简单介绍一下Dao模式(以下图所示)spring
public class User {
private int id;
private String name;
private String email;
private String phone;
public User() {
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPhone() {
return phone;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setEmail(String email) {
this.email = email;
}
public void setPhone(String phone) {
this.phone = phone;
}
}复制代码
public interface UserDaoInterface {
public User getUserInfoByName(String name);
public void putUserInfo(User user);
public void updateUserInfo(User user);
}复制代码
public class UserDaoJdbcAccessImpl implements UserDaoInterface {
// Jdbc链接数据库等操做,未完成具体实现
private DataSource dataSource;
public User getUserInfoByName(String name) {
dataSource.getC
return new User();
}
public void putUserInfo(User user) {
}
public void updateUserInfo(User user) {
}
}复制代码
public class UserDaoBatisAccessImpl implements UserDaoInterface {
// Batis链接数据库等操做,未完成具体实现
public User getUserInfoByName(String name) {
return new User();
}
public void putUserInfo(User user) {
}
public void updateUserInfo(User user) {
}
}复制代码
public class UserDaoFacotry {
public static UserDaoInterface getUserDao(int which) {
switch(which) {
case 1:
return new UserDaoJdbcAccessImpl();
case 2:
return new UserDaoBatisAccessImpl();
default:
return null;
}
}
}复制代码
public class UserService {
public UserDaoInterface getUserDaoOperation() {
return UserDaoFacotry.getUserDao(1);
}
public void getUserInfo() {
User user = this.getUserDaoOperation().getUserInfoByName("xiaoming");
}
}复制代码
但在具体实现DaoImpl时遇到了一个问题,数据库的链接访问会抛出异常,且属于checked exceptionsql
public User getUserInfoByName(String name) {
try {
Connection connection = dataSource.getConnection();
User user = ....
return user;
} catch (SQLException e) {
} finally {
connection.close();
}
}复制代码
这是很尴尬的,由于此时咱们不知道是要抛给上层业务仍是catch以后进行处理。catch以后进行处理,因为屏蔽异常会让客户端难以排查问题,若是直接抛出去也带来更严重的问题(必须更改接口且不一样数据库所抛出的异常不同),以下所示数据库
public User getUserInfoByName(String name) throw SQLException, NamingException ... {
try {
Connection connection = dataSource.getConnection();
User user = ....
return user;
} finally {
connection.close();
}
}复制代码
jdbc 为了解决不一样数据库带来的异常差别化,则对异常进行统一转换,并抛出unchecked异常。具体抛出的异常能够在org.springframework.dao中查看缓存
这是很尴尬的,由于此时咱们不知道是要抛给上层业务仍是catch以后进行处理。catch以后进行处理,因为屏蔽异常会让客户端难以排查问题,若是直接抛出去也带来更严重的问题(必须更改接口且不一样数据库所抛出的异常不同),以下所示bash
具体异常所表明的含义:
Spring的DAO异常层次 oracle
异常 | 什么时候抛出 |
---|---|
CleanupFailureDataAccessException | 一项操做成功地执行,但在释放数据库资源时发生异常(例如,关闭一个Connection |
DataAccessResourceFailureException | 数据访问资源完全失败,例如不能链接数据库 |
iMac | 10000 元 |
DataIntegrityViolationException | Insert或Update数据时违反了完整性,例如违反了唯一性限制 |
DataRetrievalFailureException | 某些数据不能被检测到,例如不能经过关键字找到一条记录 |
DeadlockLoserDataAccessException | 当前的操做由于死锁而失败 |
IncorrectUpdateSemanticsDataAccessException | Update时发生某些没有预料到的状况,例如更改超过预期的记录数。当这个异常被抛出时,执行着的事务不会被回滚 |
InvalidDataAccessApiusageException 一个数据访问的JAVA | API没有正确使用,例如必须在执行前编译好的查询编译失败了 |
invalidDataAccessResourceUsageException | 错误使用数据访问资源,例如用错误的SQL语法访问关系型数据库 |
OptimisticLockingFailureException | 乐观锁的失败。这将由ORM工具或用户的DAO实现抛出 |
TypemismatchDataAccessException | Java类型和数据类型不匹配,例如试图把String类型插入到数据库的数值型字段中 |
UncategorizedDataAccessException | 有错误发生,但没法归类到某一更为具体的异常中 |
咱们能够将spring-jdbc 看做Dao 模式的一个最佳实践,它只是使用了template模式,实现了最大化的封装,以减小用户使用的复杂性。spring-jdbc 提供了两种模式的封装,一种是Template,一种是操做对象的模式。操做对象的模式只是提供了面向对象的视觉(template 更像面向过程),其底层的实现仍然是采用Template。
接下来咱们将会了解Template 的封装过程。app
仍是延用上述例子,若是这里咱们须要根据用户名查询用户的完整信息,将采用下面的方式实现查询框架
public class UserDaoJdbcAccessImpl implements UserDaoInterface {
// Jdbc链接数据库等操做,未完成具体实现
private DataSource dataSource;
public User getUserInfoByName(String name) {
String sql = "....." + name;
Connection connection = null;
try {
connection = DataSourceUtils.getConnection(dataSource);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
List<User> userList = Lists.newArrayList();
while(resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt(1));
user.setName(name);
user.setEmail(resultSet.getString(3));
user.setPhone(resultSet.getString(4));
userList.add(user);
}
connection.close();
connection = null;
statement.close();
return userList;
} catch (Exception e) {
throw new DaoException(e);
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
log.error(".....");
}
}
}
}复制代码
当咱们只须要完成一个操做的项目时,这种方式还能够接受,但当项目中有大量的DAO须要操做时,不免过程当中会出现各类问题,如忘记关闭链接等。
其实咱们能够发现整个的数据库的操做实现能够分为四个部分:资源管理(数据库的链接关闭等操做)、sql执行(查询、更新等)、结果集的处理(将sql查询结果转化)、异常处理。
那是否是能够将公共部分抽象成一个模板进行使用呢?如今咱们来定义一个Jdbc的一个模板
public class JdbcTemplate {
public final Object execute(StatementCallback callback) {
Connection connection = null;
Statement statement = null;
try {
connection = getConnetion();
statement = con.createStatement();
Object ret = callback.doWithStatement(callback);
return retValue;
} catch (SQLException e) {
DateAccessException ex = translateSqlException(e);
throw ex;
} finally {
closeStatement(statement);
releaseConnection(connection);
}
}
}复制代码
Template 定义了关注了操做的全部过程,只须要传递一个callback,就能够帮咱们处理各类细节化操做,这些细节化操做包括:获取数据库链接;执行操做;处理异常;资源释放。那咱们在使用时就能够简化为
private JdbcTemplate jdbcTemplate;
// Jdbc链接数据库等操做,未完成具体实现
private DataSource dataSource;
public User getUserInfoByName(String name) {
StatementCallback statementCallback = new StatementCallback() {
@Override
public Object doInStatement(Statement stmt) throws SQLException, DataAccessException {
return null;
}
}
return jdbcTemplate.execute(statementCallback);
}复制代码
实际上,Template 在封装时远比这个复杂,接下来咱们就看一下spring-jdbc 是如何对jdbc进行封装的
JdbcTemplate 实现了JdbcOperations接口和继承了JdbcAccessor。
JdbcOperations 定义了数据库的操做,excute、 query、update 等,它是对行为的一种封装。
JdbcAccessor 封装了对资源的操做以及异常的处理,能够看一下源码,比较短。
public abstract class JdbcAccessor implements InitializingBean {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private DataSource dataSource;
private SQLExceptionTranslator exceptionTranslator;
private boolean lazyInit = true;
/**
* Set the JDBC DataSource to obtain connections from.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Return the DataSource used by this template.
*/
public DataSource getDataSource() {
return this.dataSource;
}
/**
* Specify the database product name for the DataSource that this accessor uses.
* This allows to initialize a SQLErrorCodeSQLExceptionTranslator without
* obtaining a Connection from the DataSource to get the metadata.
* @param dbName the database product name that identifies the error codes entry
* @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
*/
public void setDatabaseProductName(String dbName) {
this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);
}
/**
* Set the exception translator for this instance.
* <p>If no custom translator is provided, a default
* {@link SQLErrorCodeSQLExceptionTranslator} is used
* which examines the SQLException's vendor-specific error code. * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator */ public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) { this.exceptionTranslator = exceptionTranslator; } /** * Return the exception translator for this instance. * <p>Creates a default {@link SQLErrorCodeSQLExceptionTranslator} * for the specified DataSource if none set, or a * {@link SQLStateSQLExceptionTranslator} in case of no DataSource. * @see #getDataSource() */ 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; } /** * Set whether to lazily initialize the SQLExceptionTranslator for this accessor, * on first encounter of a SQLException. Default is "true"; can be switched to * "false" for initialization on startup. * <p>Early initialization just applies if {@code afterPropertiesSet()} is called. * @see #getExceptionTranslator() * @see #afterPropertiesSet() */ public void setLazyInit(boolean lazyInit) { this.lazyInit = lazyInit; } /** * Return whether to lazily initialize the SQLExceptionTranslator for this accessor. * @see #getExceptionTranslator() */ public boolean isLazyInit() { return this.lazyInit; } /** * Eagerly initialize the exception translator, if demanded, * creating a default one for the specified DataSource if none set. */ @Override public void afterPropertiesSet() { if (getDataSource() == null) { throw new IllegalArgumentException("Property 'dataSource' is required"); } if (!isLazyInit()) { getExceptionTranslator(); } } }复制代码
源码有三个参数:datasource、exceptionTranslator(转换各类数据库方案商的不一样的数据库异常)、lazyInit(延时加载:是否在applicationContext 初始化时就进行实例化)
在使用的过程当中咱们能够看到,只须要提供一个statementCallback,就能够实现对Dao 的各类操做。spring-jdbc 为了知足各类场景的须要,为咱们提供了四组不一样权限的callback
在使用的过程当中咱们能够看到,只须要提供一个statementCallback,就能够实现对Dao 的各类操做。spring-jdbc 为了知足各类场景的须要,为咱们提供了四组不一样权限的callback
callback | 说明 |
---|---|
CallableStatementCallback | 面向存储过程 |
ConnectionCallback | 面向链接的call,权限最大(但通常状况应该避免使用,形成操做不当) |
PreparedStatementCallback | 包含查询询参数的的callback,能够防止sql 注入 |
StatementCallback | 缩小了ConnectionCallback的权限范围,不容许操做数据库的链接 |
咱们再看一下JdbcTemplate 的封装
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet. DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex); } finally { DataSourceUtils.releaseConnection(con, getDataSource()); } }复制代码
有两个须要注意的地方
Connection con = DataSourceUtils.getConnection(getDataSource());复制代码
这里建立链接使用的是DataSourceUtils,而不是datasource.getConnection,这是因为考虑到了事务处理的因素。
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}复制代码
这里并不必定使用的是jdbc的connection,由于jdbc是一种统一化封装,而忽略了各个sql供应商的差别性。有时间咱们须要使用某一数据库的某种特性(好比Oracle sql)时,就能够经过对nativeJdbcExtractor来达到目的。
JdbcTemplate 还有几个演生的template,这里都再也不详细介绍。
Ok,关于template 的介绍就到此为止(这里更倾向于介绍各类技术的实现原理,而非如何使用)。
对象模式其实只是把Template 中的操做封装成各个对象,而其本质的实现方式仍然是Template
spring-jdbc的封装方式获得了普遍承认,但并不表明它是一个友好的的操做数据库的工具。 从上面的介绍过程当中,咱们能够感觉到jdbc 的封装是面向底层的,因此它对于上层的使用方并不那么友好。jdbc 并未能真正的实现业务和数据的彻底分离,对callback的定义仍然会穿插在业务当中,因此在实际的业务应用中,已经不多直接使用jdbc。所以spring 也对不少其它的ORM框架进行了支持,如ibatis,hibernate,JDO等等,这些更高级对用户更加友好。接下我会用一系列文章,对这些框架进行介绍
咱们再来回顾一下最前面提出的三个问题:
我的能力有限,有错误之处还请指证.....