16|再回首:JdbcTemplate章节小结

你好,我是郭屹。

恭喜你学完了MiniSpring的第三部分——JdbcTemplate了。JdbcTemplate在Spring 框架里,扮演着非常重要的角色。通过它,我们可以更加便捷地进行数据库操作,缩短了开发周期和开发成本,同时也降低了出错的风险。

它对Spring应用程序的稳定性和性能表现有着至关重要的影响,已经成为开发高效、高质量应用程序的不可或缺的一部分。

为了让你更好地掌握这部分内容,下面我们对这一整章做一个重点回顾。

JdbcTemplate重点回顾

JdbcTemplate是Spring框架中的一部分,是Spring对数据访问的一个实现,在Spring应用程序中被广泛采用。它这个实现特别好地体现了Rod Johnson对简洁实用的原则的把握。JdbcTemplate封装了JDBC的 API,并提供了更为便捷的访问方式,使得开发人员在不需要编写大量代码的情况下,能够高效、灵活地进行数据库操作。

我们知道,JDBC的程序都是类似的,所以这个部分我们提取出一个JDBC访问的模板,同时引入DataSource概念,屏蔽具体的数据库,就便利了上层应用业务程序员。然后,我们再进行SQL参数的处理,SQL请求带有参数,实现把数据转换成SQL语句所需要的参数格式,对SQL语句执行后的返回结果,又要自动绑定为业务对象。

之后,为了支持大量的数据访问,我们实现了数据库连接池提高性能,并且把连接池构造变成一个Bean注入到IoC容器里,还可以让用户自行配置连接池的参数。最后,进一步把程序里的SQL语句也抽取出来,配置到外部文件中,实现一个简单的MyBatis。

这就是这一章实现JdbcTemplate的过程,你可以再回顾一下。另外我们每一节课后面都给了一道思考题,让你在我们实现的这个极简框架上进行扩展,如果你认真学习了这一章的内容,相信你是可以举一反三的,自己提出解决方案。

方法可能不同,但目标是一样的。我把参考答案写在文稿中了,你可以看一下,如果你有更好的思路和想法,也欢迎和我分享。下节课我们马上要进入AOP的环节了,一起期待一下吧!

13|JDBC访问框架:如何抽取JDBC模板并隔离数据库?

思考题

我们现在只实现了query,想一想如果想要实现update应该如何做呢?

参考答案

我们现在JdbcTemplate类的结构,对于query()和update()是并列设计的,只要在类中对应的提供一个方法,形如:int update(String sql, Object[] args, int[] argTypes)。这个方法内部是一个PreparedStatement,SQL是要执行的SQL语句,args是SQL参数,argTypes是数据类型,返回值是受影响的行数。

14|增强模板:如何抽取专门的部件完成专门的任务?

思考题

你想一想我们应该怎么改造数据库连接池,保证多线程安全?

参考答案

这个问题有不同的方案,下面是一种思路供参考。

提供两个队列,一个用于忙的连接,一个用于空闲连接:

1
2
    private BlockingQueue<PooledConnection> busy;
    private BlockingQueue<PooledConnection> idle;

获取数据库连接就从idle队列中获取,程序大体如下:

1
2
3
while (true) {
conn = idle.poll();
}

在处理数据库连接时,我们通常会遇到以下步骤:

  1. 等待空闲连接:首先,系统会等待一个空闲的数据库连接。这是必要的,因为数据库连接是有限的资源,我们需要确保在进行任何操作之前,有一个可用的连接。

  2. 加入忙队列:如果所有连接都在使用中,系统将请求加入忙队列。这意味着系统将等待直到有一个连接变为可用。

  3. 检查最大连接数:在创建新连接之前,我们需要检查当前的连接数是否已经达到了最大限制。这是为了避免超出数据库允许的最大连接数,可能会导致性能问题或错误。

  4. 创建新连接:如果没有达到最大连接数,系统将创建一个新的数据库连接。这一步需要特别小心,因为是在多线程环境下进行的。

  5. 使用CAS技术:在多线程环境中,为了避免创建过多的连接,我们可以使用比较并交换(CAS)技术来确保在创建新连接时,连接数不会超过最大限制。CAS是一种原子操作,可以保证在多线程环境下数据的一致性。 以上步骤确保了在多线程环境下,数据库连接的创建和使用是安全和有效的。

1
2
3
4
5
6
7
if (size.get() < getPoolProperties().getMaxActive()) {
            if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
                size.decrementAndGet();
            } else {
                return createConnection(now, con, username, password);
            }
        }

而且还应当设置一个timeout,如果在规定的时间内还没有拿到一个连接,就要抛出一个异常。

1
2
3
4
5
6
7
if ((System.currentTimeMillis() - now) >= maxWait) {
                throw new PoolExhaustedException(
                    "Timeout: Unable to fetch a connection in " + (maxWait / 1000) +
                    " seconds.");
        } else {
                continue;
        }

mBatis: SQL语句配置化与读写分离实现

扩展SQL语句类型至update语句

为了将简单的select语句配置扩展到支持update等其他类型的SQL语句,我们可以对现有的MapperNode类进行修改。具体来说,可以增加一个属性sqlType来区分不同的SQL操作类型。

  • 0 表示 SELECT 操作(读取数据)。
  • 1 表示 UPDATE 操作(更新数据)。
  • 2 表示 INSERT 操作(插入数据)。
  • 3 表示 DELETE 操作(删除数据)。 通过这种方式,框架能够识别出每个配置的SQL语句属于哪种类型,并据此执行相应的逻辑处理。

实现读写分离

在实现了对多种SQL语句的支持之后,接下来是实现数据库的读写分离。这通常涉及到设置多个数据源,其中一个用于读操作,另一个则专用于写操作。

数据源配置

  • readDataSource: 配置为仅负责处理SELECT查询的数据源。
  • writeDataSource: 专门用来处理INSERT, UPDATE, 和 DELETE操作的数据源。 这些数据源的具体连接信息可以通过外部配置文件进行设定,以便于根据实际情况灵活调整。

JdbcTemplate 支持动态数据源

为了让JdbcTemplate可以根据当前执行的SQL类型选择合适的数据源,我们需要让其实现一个setDataSource()方法,该方法允许在运行时更改底层使用的数据源。

DefaultSqlSession 中的数据源管理

DefaultSqlSession类中,需要维护两套数据源实例:一套针对读操作,另一套针对写操作。这样,在每次执行SQL之前,系统都能基于即将被执行的SQL类型自动切换到正确的数据源上。

通过上述步骤,不仅可以让mBatis更加灵活地支持多种类型的SQL语句配置,同时也实现了基本的读写分离机制,有助于提高应用程序性能及可扩展性。

1
2
	private DataSource readDataSource;
	private DataSource writeDataSource;

然后在selectOne()中这么判断:

1
2
3
4
5
6
7
	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.