您的位置:首页 > 编程语言 > Java开发

spring源码剖析(七)JdbcTemplate数据库封装原理解读

2016-05-12 00:36 453 查看

前言

写这个之前我一直在想,阅读spring对数据库操作的封装的操作原理还有意义么,hibernate和mybatis不是比JdbcTemplate封装的更好使用更加简单么,为什么我们还要阅读JdbcTemplate相关的操作原理呢,要读去读mybatis或者hibernate的源代码岂不是更好。其实,如果能去读mybatis或者hibernate的源代码也好,但是对于spring那么完美的框架,我不愿意放过任意一个作者精心设计的功能,我们虽然不常用到JdbcTemplate,难道我们就因此不去发现另一个美妙的设计?我认为阅读研究这一部分的源代码还是蛮有必要的,最起码我可以从中体会到作者的设计思想,或许以后,我可以从他的设计中得到启发,亦或者升华自己的设计理念。

so,让我们一块来开始揭开spring对数据库操作封装的神秘面纱把。

数据库操作一般流程

下面以连接操作mysql数据库为例,简单说明不使用框架的情况下,操作的数据库的一般流程,相信大家也都敲过千百遍了,我就简单描述下流程吧
1)java程序中加载驱动程序。一般使用Class.forName这种反射的方法加载数据库的驱动。

2)创建数据连接对象。通过DriverManager的getConnection方法,输入数据库连接的URL,用户名,密码等信息,连接数据库,获取连接对象Connection 的实例

3)创建Statement(PreparedStatement)。通过数据库的连接对象可以创建Statement(PreparedStatement)对象实例,Statement(PreparedStatement)对象实例可以执行静态SQL并返回生成结果对象。

4)调用Statement(PreparedStatemen)t实例相关方法执行相应的SQL语句。Statement(PreparedStatement)的实例有很多数据库的操作方法,如通过executeQuery方法可以执行数据的查询,并可以得到返回的结果集ResultSet

5)关闭数据库连接。在使用完数据库之后,或者不需要再使用数据库的时候,可以通过Connection的close方法,关闭数据库的连接。

spring操作数据库流程

spring中的jdbc连接与直接使用jdbc去连接还是有所差别的,Spring对jdbc做了大量的封装,消除了冗余的代码,大大减少了开发量,下面先上一个简单的例子让大家回顾下spring的jdbc操作流程

spring的Jdbc操作示例

创建数据库表(person)



创建数据库表对应的po类

/**
*
* @author Fighter168
*/
public class Person {

private String personId;
private String name;
private String sex;
private int age;
private String comeFrom;
//省略set get方法
}

创建表与实体之间的映射

/**
*
* @author Fighter168
*/
public class PersonRowMapper implements RowMapper<Person>{

/* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
Person person=new Person();
person.setAge(rs.getInt("age"));
person.setComeFrom(rs.getString("come_from"));
person.setName(rs.getString("name"));
person.setPersonId(rs.getString("person_id"));
person.setSex(rs.getString("sex"));
return person;
}
}

创建数据库操作接口

/**
*
* @author Fighter168
*/
public interface PersonService {

public void save(Person person);

public List<Person> getPersons();
}

创建数据库操作接口的实现类

/**
*
* @author Fighter168
*/
public class PersonServiceImpl implements PersonService {

private JdbcTemplate jdbcTemplate;

public void setJdbcTemplate(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public void save(Person p) {
jdbcTemplate.update("insert in to person(person_id,name,age,sex,come_from) values(?,?,?,?,?)",
new Object[]{p.getPersonId(),p.getName(),p.getAge(),p.getSex(),p.getComeFrom()},
new int[]{Types.VARCHAR,Types.VARCHAR,Types.INTEGER,Types.VARCHAR,Types.VARCHAR});

}

public List<Person> getPersons(){
List<Person> list=jdbcTemplate.query("select * from person", new PersonRowMapper());
return list;
}
}

创建spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123abc"/>
<!-- 连接池启动时候的初始连接数 -->
<property name="initialSize" value="10"/>
<!-- 最小空闲值 -->
<property name="minIdle" value="5"/>
<!-- 最大空闲值 -->
<property name="maxIdle" value="20"/>
<property name="maxWait" value="2000"/>
<!-- 连接池最大值 -->
<property name="maxActive" value="50"/>
<property name="logAbandoned" value="true"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="180"/>
</bean>
<!-- 配置业务bean -->
<bean id="personService" class="net.itaem.service.PersonServiceImpl">
<!-- 向dataSource注入数据源 -->
<property name="jdbcTemplate" ref="dataSource"/>
</bean>
</beans>

测试

/**
*
* @author Fighter168
*/
public class JdbcTest {

public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("net/itaem/source/bean.xml");
PersonService service=(PersonService) context.getBean("personService");
System.out.println(service.getPersons().size());
}
}


jdbctemp的其他用法就不多说了,比较简单,大家都了解,那么我们就借着这个示例看看里面执行的源码原理吧。

JdbcTemplate的query方法

时序图

首先我们来看看query方法执行的时序图



重要步骤说明

首先是从PersonServiceImpl方法进去,调用JdbcTemplate的query方法,然后执行一连串错中复杂的调用,而且里面有很多函数都是以回调形式处理,由此可以知道,代理模式在spring是多么的常用。接下来让我来看一些重要的细节的逻辑把。
1)JdbcTemplate接受到query请求,由于query没有带参数,所以选择不带sql参数的重载方法query执行。
2)query方法面会创建一个内部类(QueryStatementCallback),然后实例化,传给execute方法,等待execute回调,这里使用了代理设计模式,让我们看看这个query的详细实现
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
//先进入RowMapperResultSetExtractor的回调函数,然后再回调PersonRowMapper的 mapRow方法
return rse.extractData(rsToUse);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}

3)很明显,上面是传了一个callback对象的实例进入execute,其实execute也是JdbcTemplate的核心方法,虽然execute有很多重载方法,但是他们的核心逻辑其实没什么特别大的差别,那么就让我先看看执行query方法所调用的execute方法的源码把.
//-------------------------------------------------------------------------
// Methods dealing with static SQL (java.sql.Statement)
//-------------------------------------------------------------------------

public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
//创建数据库连接
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
//应用用户输入的参数
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
//回调QueryStatementCallback的doInStatement方法,然后doStatement方法里面又回调PersonRowMapper类的rowMap方法,
//rowMap取得每一行的信息封装成Person对象返回,一直返回,知道返回到这里的result
T result = action.doInStatement(stmtToUse);
//警告处理
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// 发生异常时候释放资源
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
//释放Statement
JdbcUtils.closeStatement(stmt);
//释放Connection
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
具体的核心逻辑,上面注释也写比较清晰,我就不重复诉说了。
4)获取到List对象后,就直接退出execute方法,逐步返回result,知道返回给最初的调用者(ok,query方法实现完毕!)

JdbcTemplate的update方法

时序图

接下来我们看看update/save方法的具体实现吧,首先,我们还是先上时序图,清晰一点



            此时我们会发现,咦,这调用的时序怎么好像和query的有点类似。其实,他们的核心逻辑还是差不多的,都是先获取数据源,创建PreparedStatement或者Statement,然后调用各种的回调函数,给各种的CallBack去处理,处理完成之后便释放资源,返回结果。

             所以,JdbcTemplate核心操作数据库的逻辑其实都是差不多的,但是针对于不同的方法,例如query,或者update他们分别有不同的CallBack传给execute方法去执行,然后execute方法回调各自的回调方法,执行完毕后再传给execute方法,等待execute方法释放完资源后便逐步返回结果给调用者。
         
              由于其他方法,类似queryForObject,还有batchUpdate等等这些方法的处理逻辑类似,所以在此便不再说明了。

小结

   spring的JdbcTemplate用起来其实还是比较好用的,起码对原始的jdbc操作已经封装的很好了,尤其是设计里面代理模式的完美应用,各种callback。虽说我们现在做项目都是使用mybatis或者hibernate这种ORM框架的居多,但是偶尔我们还是需要返璞归真一下,去探究底层的实现原理,加深自己的理解,提高自己的水平。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: