天天看点

3000字让程序员拥有SpringJDBC实战经验

作者:程序员高级码农II

Spring JDBC实战经验

JDBC是一个复杂的开发规范,在使用过程中存在很多可以挖掘的优化点。在本节中,我们将逐一分析这些优化点,并给出对应的最佳实践。

优化事务隔离等级

事务隔离级别的概念不仅限于Spring框架,而且适用于任何与数据库交互的应用程序。隔离级别定义了一个事务对数据库所做的更改如何影响其他并发事务,以及更改后的数据如何、何时可供其他事务使用。在Spring框架中,定义事务的隔离级别可以使用@Transaction注解,如代码清单8-29所示。

代码清单8-29 @Transaction注解使用示例代码

@Transactional(isolation=Isolation.READ_UNCOMMITTED)

public void someTransactionalMethod(User user) {

// 执行数据库操作

}

这里我们定义了隔离级别为READ_UNCOMMITTED,而其他的隔离级别还包括READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE,它们的执行效率依次递减。

Spring采用的默认隔离级别一般与数据库一致。例如,MySQL默认采用的就是REPEATABLE_READ。通常情况下,使用Spring默认的隔离级别即可。在用户请求并发量比较少的情况下,为了提高性能,也可以采用

READ_COMMITTED。另外两种隔离级别一般很少用。

优化Fetch Size

应用程序和数据库服务器之间的网络通信是影响性能的关键因素之一。

显然,如果能够减少网络流量,就能帮助我们提高性能。这时就可以使用Fetch Size属性。根据JDBC驱动程序,Fetch Size可以用来指定一次从数据库中检索的行数,而大多数JDBC驱动程序的默认值是10。这样,如果想要检索1000行,那么需要在应用程序和数据库服务器之间进行100次网络通信。通过合理设置Fetch Size的大小,就可以通过减少网络通信的次数来提升系统性能。

Spring JDBC同样提供了方便的API供开发人员设置Fetch Size。基于JdbcTemplate,我们可以使用如代码清单8-30所示的代码。

代码清单8-30 Fetch Size使用示例代码

JdbcTemplate jdbc = new JdbcTemplate(dataSource);

jdbc.setFetchSize(200);

在优化Fetch Size时,需要确保JDBC驱动程序支持对应的大小。同时,Fetch Size不应该采用硬编码,而需要确保它的可配置性,因为它影响到JVM堆内存大小,不同的环境会有所不同。如果Fetch Size设置过大,应用程序可能会遇到内存不足的问题。

优化连接池配置

JDBC在访问数据库时使用连接池,连接池在应用程序性能方面具有显著的优势。我们知道频繁地打开和关闭连接可能是昂贵的,因此可以合理限制数据库的连接数,直到新的连接可用,这在分布式环境中非常有用。接下来,我们将实现最佳的连接池配置策略,这方面的工作通常涉及三部分内容,即设置连接池大小、验证连接以及检查连接泄漏。

与设置连接池大小相关的常见属性包括以下几个。

initialSize:该属性定义启动连接池时将建立的连接数。

maxActive:该属性可用于限制与数据库建立的最大连接数。

maxIdle:该属性用于始终保持池中空闲连接的最大数量。

minIdle:该属性用于始终保持池中空闲连接的最小数量。

timeBetweenEvictionRunsMillis:该属性用于设置清理线程的触发周期。清理线程运行在后台,会测试空闲的、被放弃的连接,并在连接池处于活动状态时调整池的大小,同时还会负责连接泄漏检测。该属性应设置为不低于1s。

验证连接相关配置的目的在于永远不使用无效的连接,这点可以帮助我们防止客户端出现错误,其实现方法就是向服务器发送一个小查询。与验证连接相关的配置项如下所示。

testOnBorrow:当该属性定义为true时,将在使用之前验证连接对象。如果验证失败,它将被丢弃到池中,并选择另一个连接对象。

validationInterval:该属性定义验证连接的时间间隔,一般不应超过30s。如果我们设置一个较大的值,会增加应用程序中存在过时连接的可能性。

validationQuery:该属性通过发送SELECT 1查询语句来验证池中的连接。

最后,可以使用如下属性来检查连接是否泄漏。

removeBandonedTimeout:如果连接运行的时间超过这个属性设置的值,则认为该连接已被放弃。

removeAbandoned:此标志应为true,意味着那些已被放弃的连接会被自动删除。

作为总结,我们给出一个完整的连接池配置,如代码清单8-31所示。

代码清单8-31 连接池配置示例代码

<Resource type="javax.sql.DataSource"

name="testDB"

factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"

driverClassName="com.mysql.cj.jdbc.Driver"

url="jdbc:mysql://localhost:3306/testDB "

username="test"

password="test"

initialSize="10"

maxActive="100"

maxIdle="50"

minIdle="10"

suspectTimeout="60"

timeBetweenEvictionRunsMillis="30000"

minEvictableIdleTimeMillis="60000"

testOnBorrow="true"

validationInterval="34000"

validationQuery="SELECT 1"

removeAbandoned="true"

removeAbandonedTimeout="60"

logAbandoned="true"

/>

选择合适的Statement

在使用JDBC API时,我们需要在Statement、PreparedStatement和CallableStatement接口之间进行选择,这取决于你计划如何使用该接口。

Statement接口针对SQL语句的一次执行进行了优化,而PreparedStatement则针对需要多次执行的SQL语句进行了优化,CallableStatement通常优先用于执行存储过程。

在互联网应用程序中,一般选择PreparedStatement即可。但如果涉及批量数据处理,那么建议使用下面要介绍的批处理机制。

使用批处理

当多次使用INSERT语句将大量数据插入数据库时,会增加JDBC API调用的次数并影响性能。这时候可以使用PreparedStatement接口的addBatch()方法一次向数据库发送多个查询。我们可以对比一下两种实现方法,如代码清单8-32所示。

代码清单8-32 addBatch()方法使用示例代码

//不使用批处理的数据插入代码

PreparedStatement ps = conn.prepareStatement( "INSERT INTO USER VALUES

(?, ?)");

for(n=0;n<100;n++){

ps.setInt(userNumber[n]);

ps.setString(usertName[n]);

ps.executeUpdate();

}

//使用批处理的数据插入代码

PreparedStatement ps = conn.prepareStatement("INSERT INTO USER VALUES

(?, ?)");for(n=0;n<100;n++){

ps.setInt(userNumber[n]);

ps.setString(userName[n]);

ps.addBatch();

}

ps.executeBatch();

在上述示例中,我们使用了addBatch()方法。它合并了所有100个INSERT语句,并且只使用了两次网络调来执行整个操作:一次用于准备语句,另一次用于执行一批合并的SQL。

选择合适的提交模式

关于如何选择合适的提交模式实际上并没有标准答案。在大多数标准JDBC API中,默认的提交模式是自动提交(Auto Commit)。也就是说,数据库驱动程序在每个SQL语句操作之后向数据库发送一个提交请求。这个请求需要一次网络调用,即使执行SELECT这种没有对数据库进行任何更改的SQL语句也是如此。这显然会影响性能,因为在提交事务时,数据库服务器必须将事务所做的更改写入数据库,这一过程涉及昂贵的磁盘输入/输出。因此,我们可以把自动提交模式设置为OFF以提高应用程序的性能。

但是对于某些应用程序,将自动提交模式设置为关闭并执行手动提交是不可取的。例如,一个允许用户将钱从一个账户转移到另一个账户的银行应用程序,为了确保转账工作的数据完整性,需要两个账户都在新金额更新之后提交事.

Spring JDBC面试题分析

面试题1:JDBC规范包含哪些核心编程对象?

答案:JDBC规范是Java领域针对关系型数据访问的业界标准,该规范提供了包括DriverManger、DataSource、Connection、Statement以及ResultSet在内的一组编程对象。开发人员通过DriverManger获取具体数据库的驱动,然后使用DataSource构建数据源。一旦具备了数据源,就可以获取针对某一次数据访问的连接对象Connection,并构建对应的查询语句Statement。最后,基于Statement返回的结果集对象ResultSet,我们就可以获取对应的数据库数据。事实上,JDBC规范还是比较偏底层的,日常开发过程中不推荐直接使用这些编程对象进行数据访问,但这并不能说明我们不需要对它们进行深入的了解。在面试过程中,JDBC规范还是比较常见的一道面试题。

面试题2:使用JdbcTemplate如何实现数据的插入?

答案:开发人员可以使用JdbcTemplate提供的update()方法实现数据库的插入。在执行数据插入时,比较常见的一种场景是需要返回数据库的自增主键值。这时候,基于JdbcTemplate的实现过程是比较复杂的。为了简化这个过程,Spring Boot专门提供了一个SimpleJdbcInsert工具类。我们可以使用该类来简化数据插入操作。

面试题3:JdbcTemplate在实现上采用了哪些设计模式?

答案:从命名上看,JdbcTemplate显然使用了模板方法这一设计模式。

在实现方式上,我们通常使用抽象类来实现模板方法模式。在Spring框架中大量应用了模板方法来实现基类与子类之间的职责分离和协作,JdbcTemplate就是非常典型的一个实现示例。同时,JdbcTemplate还充分利用了回调机制来实现数据访问过程与业务逻辑之间的解耦。

本文给大家讲解的内容是springboot数据访问:Spring JDBC实战经验

  • 下文给大家讲解的是应用Spring ORM最佳实践: Spring Data架构与应用