你好,我是郭屹。
恭喜你学完了MiniSpring的第三部分——JdbcTemplate了。JdbcTemplate在Spring 框架里,扮演着非常重要的角色。通过它,我们可以更加便捷地进行数据库操作,缩短了开发周期和开发成本,同时也降低了出错的风险。
它对Spring应用程序的稳定性和性能表现有着至关重要的影响,已经成为开发高效、高质量应用程序的不可或缺的一部分。
为了让你更好地掌握这部分内容,下面我们对这一整章做一个重点回顾。
JdbcTemplate是Spring框架中的一部分,是Spring对数据访问的一个实现,在Spring应用程序中被广泛采用。它这个实现特别好地体现了Rod Johnson对简洁实用的原则的把握。JdbcTemplate封装了JDBC的 API,并提供了更为便捷的访问方式,使得开发人员在不需要编写大量代码的情况下,能够高效、灵活地进行数据库操作。
我们知道,JDBC的程序都是类似的,所以这个部分我们提取出一个JDBC访问的模板,同时引入DataSource概念,屏蔽具体的数据库,就便利了上层应用业务程序员。然后,我们再进行SQL参数的处理,SQL请求带有参数,实现把数据转换成SQL语句所需要的参数格式,对SQL语句执行后的返回结果,又要自动绑定为业务对象。
之后,为了支持大量的数据访问,我们实现了数据库连接池提高性能,并且把连接池构造变成一个Bean注入到IoC容器里,还可以让用户自行配置连接池的参数。最后,进一步把程序里的SQL语句也抽取出来,配置到外部文件中,实现一个简单的MyBatis。
这就是这一章实现JdbcTemplate的过程,你可以再回顾一下。另外我们每一节课后面都给了一道思考题,让你在我们实现的这个极简框架上进行扩展,如果你认真学习了这一章的内容,相信你是可以举一反三的,自己提出解决方案。
方法可能不同,但目标是一样的。我把参考答案写在文稿中了,你可以看一下,如果你有更好的思路和想法,也欢迎和我分享。下节课我们马上要进入AOP的环节了,一起期待一下吧!
我们现在只实现了query,想一想如果想要实现update应该如何做呢?
我们现在JdbcTemplate类的结构,对于query()和update()是并列设计的,只要在类中对应的提供一个方法,形如:int update(String sql, Object[] args, int[] argTypes)。这个方法内部是一个PreparedStatement,SQL是要执行的SQL语句,args是SQL参数,argTypes是数据类型,返回值是受影响的行数。
你想一想我们应该怎么改造数据库连接池,保证多线程安全?
这个问题有不同的方案,下面是一种思路供参考。
提供两个队列,一个用于忙的连接,一个用于空闲连接:
private BlockingQueue<PooledConnection> busy;
private BlockingQueue<PooledConnection> idle;
获取数据库连接就从idle队列中获取,程序大体如下:
while (true) {
conn = idle.poll();
}
就是死等一个空闲连接。然后加入忙队列。
当然,进一步考虑,还应当判断连接数是否到了最大,如果没有,则要先创建一个新的连接。创建的时候要小心了,因为是多线程的,所以要再次校验是否超过最大连接数,如使用CAS技术:
if (size.get() < getPoolProperties().getMaxActive()) {
if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
size.decrementAndGet();
} else {
return createConnection(now, con, username, password);
}
}
而且还应当设置一个timeout,如果在规定的时间内还没有拿到一个连接,就要抛出一个异常。
if ((System.currentTimeMillis() - now) >= maxWait) {
throw new PoolExhaustedException(
"Timeout: Unable to fetch a connection in " + (maxWait / 1000) +
" seconds.");
} else {
continue;
}
关闭连接,也就是从busy队列移除,然后加入到idle队列中。
我们只是简单地实现了select语句的配置,如何扩展到update语句?进一步,如何实现读写分离?
我们可以在sql节点类MapperNode中增加一个属性sqltype,表示sql语句的类型,比如0表示select,1表示update,2表示insert,3表示delete。这样我们就知道了一个sql语句是read还是write。
然后datasource变成两个,一个是readDatasource,一个是writeDatasource,可以配置在外部文件中。JdbcTemplate也提供一个setDatasource()允许动态设置数据源。
DefaultSqlSession类中配置两个data source,形如:
private DataSource readDataSource;
private DataSource writeDataSource;
然后在selectOne()中这么判断:
public Object selectOne(String sqlid, Object[] args, PreparedStatementCallback pstmtcallback) {
int sqltype = this.sqlSessionFactory.getMapperNode(sqlid).getSqlType();
if (sqltype==0) {//read
jdbcTemplate.setDatasource(readDataSource);
}
return jdbcTemplate.query(sql, args, pstmtcallback);
}
也就是说,每一次用SqlSession执行SQL语句的时候,都判断一下SQL类型,如果是read,则设置readDatasource,否则设置writeDatasource.