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

前言

写这个之前我一直在想,阅读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框架的居多,但是偶尔我们还是需要返璞归真一下,去探究底层的实现原理,加深自己的理解,提高自己的水平。












  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring JdbcTemplateSpring框架提供的一个对JDBC进行封装的工具类,它可以简化JDBC编程的复杂性,提供了一种更简便的方式来访问数据库JdbcTemplate原理是将JDBC的访问进行封装,提供了一些常用的操作方法,例如查询、更新、批量操作等。它通过DataSource来获取JDBC的连接,并通过Connection对象进行JDBC的操作,最后释放连接资源。 使用JdbcTemplate进行数据库操作的步骤如下: 1. 配置数据源,配置JdbcTemplate。 2. 在代码中通过JdbcTemplate对象进行数据库操作。 下面是一个简单的例子: 配置数据源和JdbcTemplate: ```xml <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <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="root"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> ``` 在代码中通过JdbcTemplate对象进行数据库操作: ```java @Autowired private JdbcTemplate jdbcTemplate; public void insertUser(User user) { String sql = "INSERT INTO user(name, age) VALUES(?, ?)"; jdbcTemplate.update(sql, user.getName(), user.getAge()); } public List<User> getUsers() { String sql = "SELECT * FROM user"; List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); return users; } ``` 在上面的代码中,我们通过@Autowired注解注入了JdbcTemplate对象,并使用它来执行插入和查询操作。 通过JdbcTemplate,我们可以使用占位符来传递参数,也可以使用BeanPropertyRowMapper将查询结果映射为Java对象。 总的来说,Spring JdbcTemplate原理封装了JDBC的访问,提供了一种更便捷的方式来操作数据库

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值