前言
写这个之前我一直在想,阅读spring对数据库操作的封装的操作原理还有意义么,hibernate和mybatis不是比JdbcTemplate封装的更好使用更加简单么,为什么我们还要阅读JdbcTemplate相关的操作原理呢,要读去读mybatis或者hibernate的源代码岂不是更好。其实,如果能去读mybatis或者hibernate的源代码也好,但是对于spring那么完美的框架,我不愿意放过任意一个作者精心设计的功能,我们虽然不常用到JdbcTemplate,难道我们就因此不去发现另一个美妙的设计?我认为阅读研究这一部分的源代码还是蛮有必要的,最起码我可以从中体会到作者的设计思想,或许以后,我可以从他的设计中得到启发,亦或者升华自己的设计理念。
so,让我们一块来开始揭开spring对数据库操作封装的神秘面纱把。
数据库操作一般流程
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方法
时序图
重要步骤说明
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());
}
//-------------------------------------------------------------------------
// 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());
}
}
具体的核心逻辑,上面注释也写比较清晰,我就不重复诉说了。