vlambda博客
学习文章列表

读书笔记《building-microservices-with-spring》使用Spring和JDBC模板模式访问数据库

Chapter 5. Accessing a Database with Spring and JDBC Template Patterns

在前面的章节中,您了解了 Spring 核心模块,如 Spring IoC 容器、DI 模式、容器生命周期和使用的设计模式。您还看到了 Spring 如何使用 AOP 创造奇迹。现在是进入具有持久数据的真正 Spring 应用程序战场的正确时机。你还记得你在大学期间处理数据库访问的第一个应用程序吗?那时,您可能不得不编写无聊的样板代码来加载数据库驱动程序、初始化数据访问框架、打开连接、处理各种异常以及关闭连接。您还必须非常小心该代码。如果出现任何问题,您将无法在应用程序中建立数据库连接,即使除了编写实际的 SQL 和业务代码之外,您会在这些无聊的代码上投入大量时间。

因为我们总是试图让事情变得更好和更简单,所以我们必须专注于解决数据访问的繁琐工作。 Spring 为数据访问的枯燥乏味的工作提供了一个解决方案——它删除了数据访问的代码。 Spring 提供数据访问框架以与各种数据访问技术集成。它允许您直接使用 JDBC 或任何 对象关系映射 (ORM ) 框架,如 Hibernate,用于持久化数据。 Spring 为您的应用程序中的数据访问工作处理所有低级代码;您可以只编写 SQL、应用程序逻辑和管理应用程序的数据,而不是花时间编写代码来建立和关闭数据库连接等等。

现在,您可以选择任何技术,例如 JDBC、Hibernate、Java Persistence API (JPA ) 或其他。持久化应用程序的数据。无论您选择什么,Spring 都会为您的应用程序提供对所有这些技术的支持。在本章中,我们将探索 Spring 对 JDBC 的支持。它将涵盖以下几点:

  • The best approach to designing your data access
  • Implementing the template design pattern
  • Problems with the traditional JDBC
  • Solving problems with the Spring JdbcTemplate
  • Configuring the data source
  • Using the object pool design pattern to maintain database connections
  • Abstracting database access by the DAO pattern
  • Working with JdbcTemplate
  • The Jdbc callback interfaces
  • Best practices for configuring JdbcTemplate in the application

在我们继续讨论有关 JDBC 和模板设计模式的更多信息之前,让我们先看看在分层架构中定义数据访问层的最佳方法。

The best approach to designing your data-access


在前面的章节中,您已经看到 Spring 的目标之一是允许 遵循 OOP 原则之一来开发应用程序对接口的编码。任何企业应用程序都需要读取数据并将数据写入任何类型的数据库,为了满足这个要求,我们必须编写持久化逻辑。 Spring 允许您避免将持久性逻辑分散到应用程序中的所有模块中。为此,我们可以为数据访问和持久化逻辑创建一个不同的组件,这个组件称为数据访问对象DAO)。让我们看看,在以下图中,在分层应用程序中创建模块的最佳方法:

读书笔记《building-microservices-with-spring》使用Spring和JDBC模板模式访问数据库

如上图所示,为了更好的方法,许多 enterprise 应用程序由以下三个逻辑层组成:

  • The service layer (or application layer): This layer of the application exposes high-level application functions like use-cases and business logic. All application services are defined here.
  • The data access layer: This layer of the application defines an interface to the application's data repository (such as a Relational or NoSQL database). This layer has the classes and interfaces which have the data-access logic's data persisting in the application.
  • The infrastructure layer: This layer of the application exposes low-level services to the other layers, such as configuring DataSource by using the database URL, user credentials, and so on. Such configuration comes under this layer.

在上图中,您可以看到 Service LayerData Access Layer 。为了避免应用程序逻辑和数据访问逻辑之间的耦合,我们应该通过接口公开它们的功能,因为接口促进了协作组件之间的解耦。如果我们通过实现接口来使用数据访问逻辑,我们可以为应用程序配置任何特定的数据访问策略,而无需在 服务层。下图显示了设计数据访问层的正确方法:

读书笔记《building-microservices-with-spring》使用Spring和JDBC模板模式访问数据库

如上图所示,您的应用服务对象,即TransferService,不处理自己的数据访问。相反,他们将数据访问权限委托给存储库。存储库的接口,即应用程序中的 AccountRepository,使其与服务对象保持松散耦合。您可以配置实现的任何变体 - AccountRepository 的 Jpa 实现(JpaAccountRepository),或 AccountRepository 的 Jdbc 实现 (JdbcAccountRepository< /跨度>)。

Spring 不仅在分层架构中工作在不同层的应用程序组件之间提供了松耦合,而且还有助于管理企业分层架构应用程序中的资源。让我们看看 Spring 是如何管理资源的,以及 Spring 使用什么设计模式来解决资源管理问题。

The resource management problem

让我们通过一个真实的例子来理解resource管理问题。您一定曾经在某个时候在网上订购过披萨。如果是这样,从订购比萨饼到交付的过程中涉及的步骤是什么?这个过程有很多步骤——我们首先去披萨公司的在线门户,选择披萨的大小和配料。之后,我们下订单并结帐。订单被最近的披萨店接受;他们相应地准备我们的披萨,相应地放上配料,将披萨包装在袋子中,送货员上门将披萨交给您,最后,您和朋友一起享用披萨。尽管此过程有很多步骤,但您只积极参与其中的几个步骤。比萨公司负责烹饪比萨并顺利送达。您仅在需要时参与,其他步骤由比萨公司负责。正如您在此示例中所见,管理此流程涉及许多步骤,我们还必须相应地将资源分配给每个步骤,以便将其视为完整的任务,而不会中断流程。这是强大的设计模式模板方法模式的完美场景。 Spring 框架实现了这个模板设计模式来处理应用程序的 DAO 层中的此类场景。让我们看看如果我们不使用 Spring,而使用传统应用程序,我们会面临哪些问题。

在传统应用程序中,我们使用 JDBC API 来访问数据库中的数据。这是一个简单的应用程序,我们使用 JDBC API 访问和持久化数据,对于这个应用程序,需要以下步骤:

  1. Define the connection parameters.
  2. Access a data source, and establish a connection.
  3. Begin a transaction.
  4. Specify the SQL statement.
  5. Declare the parameters, and provide parameter values.
  6. Prepare and execute the statement.
  7. Set up the loop to iterate through the results.
  8. Do the work for each iteration--execute the business logic.
  9. Process any exception.
  10. Commit or roll back the transaction.
  11. Close the connection, statement, and resultset.

如果您为同一个应用程序使用 Spring 框架,那么您必须为前面的步骤列表中的某些步骤编写代码,而 Spring 会处理所有涉及低级过程的步骤,例如建立连接、开始一个事务,处理数据层中的任何异常,并关闭连接。 Spring 通过使用模板方法设计模式来管理这些步骤,我们将在下一节中学习。

Implementing the template design pattern

在操作中定义算法的骨架,将一些步骤推迟到子类。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。

-GOF 设计模式

模板方法设计模式应用广泛,属于GOF设计模式家族的结构化设计模式。该模式定义了算法的轮廓或骨架,并将细节留给稍后的具体实现。这种模式隐藏了大量的样板代码。 Spring提供了很多模板类,如JdbcTemplateJmsTemplateRestTemplate、和 WebServiceTemplate。大多数情况下,这种模式隐藏了前面在比萨示例中讨论的低级资源管理。

在该示例中,该过程是从在线门户订购披萨送货上门。比萨公司遵循的流程对每个客户都有一些固定的步骤,比如接单、准备比萨、根据客户的规格添加配料,然后将其送到客户的地址。我们可以添加这些步骤,或者将这些步骤定义为特定的算法。然后系统可以相应地实施该算法。

Spring 实现了这种模式来访问数据库中的数据。在数据库或任何其他技术中,有一些步骤总是常见的,例如建立与数据库的连接、处理事务、处理异常以及每个数据访问过程所需的一些清理操作。但也有一些步骤不是固定的,而是取决于应用程序的要求。定义这些步骤是开发人员的责任。但是 Spring 允许我们将数据访问过程的固定和动态部分分离为不同的部分,作为模板和回调。所有固定步骤都在模板下,动态自定义步骤在回调下。下图详细描述了两者:

读书笔记《building-microservices-with-spring》使用Spring和JDBC模板模式访问数据库

如上图所示,数据访问流程的所有固定部分都封装到了 Spring Framework 的模板类中,如打开和关闭连接、打开和关闭语句、处理异常和管理资源。但其他步骤,如编写 SQL、声明连接参数等,都是回调的一部分,回调由开发人员处理。

Spring提供了几种模板方法设计模式的实现,例如JdbcTemplateJmsTemplateRestTemplate< /code> 和 WebServiceTemplate,但在本章中,我将仅解释其对 JDBC API 的实现,即 JdbcTemplateJdbcTemplate-NamedParameterJdbcTemplate还有另一个变种,它包装了一个JdbcTemplate来提供命名参数,而不是传统的JDBC“?" 占位符。

Problems with the traditional JDBC

以下是我们在使用传统 JDBC API工作 时必须面对的问题:

  • Redundant results due to error-prone code: The traditional JDBC API required a lot of tedious code to work with the data access layer. Let's see the following code to connect the Database and execute the desired query:
        public List<Account> findByAccountNumber(Long accountNumber) { 
          List<Account> accountList = new ArrayList<Account>(); 
          Connection conn = null; 
          String sql = "select account_name,
          account_balance from ACCOUNT where account_number=?"; 
          try { 
            DataSource dataSource = DataSourceUtils.getDataSource(); 
            conn = dataSource.getConnection(); 
            PreparedStatement ps = conn.prepareStatement(sql); 
            ps.setLong(1, accountNumber); 
            ResultSet rs = ps.executeQuery(); 
            while (rs.next()) { 
              accountList.add(new Account(rs.getString(
                "account_name"), ...)); 
            } 
          } catch (SQLException e) { /* what to be handle here? */ } 
          finally { 
            try { 
              conn.close(); 
            } catch (SQLException e) { /* what to be handle here ?*/ } 
          } 
          return accountList; 
        } 

正如您在前面的代码中看到的,有一些行被突出显示;只有这个粗体代码很重要——其余的都是样板文件。此外,此代码在应用程序中处理 SQLException 的效率很低,因为开发人员不知道应该在那里处理什么。现在让我们看一下传统 JDBC 代码中的另一个问题。

  • Leads to poor exception handling: In the preceding code, the exceptions in the application are handled very poorly. The developers are not aware of what exceptions are to be handled here. SQLException is a checked Exception, which means it forces the developers to handle errors, but if you can't handle it, you must declare it. It is a very bad way of handling exceptions, and the intermediate methods must declare exception(s) from all methods in the code. It is a form of tight coupling.

Solving problems with Spring's JdbcTemplate

Spring 的 JdbcTemplate 解决了上一节中列出的 problemsJdbcTemplate 极大地简化了 JDBC API 的使用,并消除了重复的样板代码。它减轻了常见的错误原因,并在不牺牲能力的情况下正确处理 SQLExceptions。它提供对标准 JDBC 结构的完全访问。让我们看看使用 Spring 的 JdbcTemplate 类来解决这两个问题的相同代码:

  • Removing redundant code from the application using JdbcTemplate: Suppose you want a count of the accounts in a bank. The following code is required if you use the JdbcTemplate class:
        int count = jdbcTemplate.queryForObject("SELECT COUNT(*)
         FROM ACCOUNT", Integer.class); 
 

如果要访问特定用户 ID 的帐户列表:

        List<Account> results = jdbcTemplate.query(someSql,
         new RowMapper<Account>() { 
           public Account mapRow(ResultSet rs, int row) throws 
            SQLException { 
              // map the current row to an Account object 
            } 
        }); 

正如您在前面的代码中看到的,您不需要编写打开和关闭数据库连接的代码,准备执行查询的语句等等。

  • Data Access Exceptions: Spring provides a consistent exception hierarchy to handle technology-specific exceptions like SQLException to its own exception class hierarchy with DataAccessException as the root exception. Spring wraps these original exceptions into different unchecked exceptions. Now Spring does not force the developers to handle these exceptions at development time. Spring provides the DataAccessException hierarchy to hide whether you are using JPA, Hibernate, JDBC, or similar. Actually, it is a hierarchy of sub-exceptions, and not just one exception for everything. It is consistent across all the supported data access technologies. The following diagram depicts the Spring Data Access Exception hierarchy:
读书笔记《building-microservices-with-spring》使用Spring和JDBC模板模式访问数据库
  • As you can see in the preceding figure, Spring's DataAccessException extends the RuntimeException, that is, it is an unchecked exception. In an enterprise application, unchecked exceptions can be thrown up the call hierarchy to the best place to handle it. The good thing is that the methods in between don't know about it in the application.

在声明 Spring 应用程序中的模板和存储库之前,让我们首先讨论如何使用数据源配置 Spring 以能够连接数据库。

Configuring the data source and object pool pattern


在 Spring Framework 中,DataSource 是 JDBC API 的一部分,它提供到数据库的连接。它从应用程序代码中隐藏了许多用于连接池、异常处理和事务管理问题的样板代码。作为开发人员,您只需要专注 处理您的业务逻辑。不用担心连接池、异常处理和管理事务;如何在生产环境中设置容器管理的 data 源是应用程序管理员的责任。您只需编写代码并测试该代码。

在企业应用程序中,我们可以通过多种方式检索 DataSource。我们可以使用 JDBC 驱动来检索 DataSource,但这并不是在生产环境中创建 DataSource 的最佳方法。由于性能是应用程序开发过程中的关键问题之一,因此 Spring 实现了对象池模式以非常有效的方式为应用程序提供 DataSource。对象池模式表明创建对象比重用成本高。

Spring 允许我们实现对象池模式,以便在应用程序中重用 DataSource 对象。您可以使用应用程序服务器和容器管理池 (JNDI),也可以使用 DBCP、c3p0 等第三方库创建容器。这些池有助于以更好的方式管理可用的数据源。

在您的 Spring 应用程序中,有几个选项可以配置数据源 bean,它们如下所示:

  • Configuring data source using a JDBC driver
  • Implementing the object pool design pattern to provide data source objects
    • Configuring the data source using JNDI
    • Configuring the data source using pool connections
      • Implementing the Builder pattern to create an embedded data source
  • 让我们看看如何在 Spring 应用程序中配置数据源 bean。

Configuring a data source using a JDBC driver

使用 JDBC 驱动程序configure 数据源 bean 是最简单的 data Spring 中的源代码。 Spring提供的三个数据源类如下​​:

  • DriverManagerDataSource: It always creates a new connection for every connection request
  • SimpleDriverDataSource: It is similar to the DriverManagerDataSource except that it works with the JDBC driver directly
  • SingleConnectionDataSource: It returns the same connection for every connection request, but it is not a pooled data source

让我们看看以下代码,用于在应用程序中使用 Spring 的 DriverManagerDataSource 类配置数据源 bean:

在基于Java的配置中,代码如下:

    DriverManagerDataSource dataSource = new DriverManagerDataSource(); 
    dataSource.setDriverClassName("org.h2.Driver"); 
    dataSource.setUrl("jdbc:h2:tcp://localhost/bankDB"); 
    dataSource.setUsername("root"); 
    dataSource.setPassword("root"); 

在基于 XML 的配置中,代码将如下所示:

    <bean id="dataSource" class="org.springframework.jdbc.datasource .DriverManagerDataSource"> 
     <property name="driverClassName" value="org.h2.Driver"/> 
     <property name="url" value="jdbc:h2:tcp://localhost/bankDB"/> 
     <property name="username" value="root"/> 
     <property name="password" value="root"/> 
    </bean> 

前面代码中定义的数据源是一个非常简单的数据源,我们可以在开发环境中使用。它不是适合生产的数据源。我个人更喜欢使用 JNDI 来配置生产环境的数据源。让我们看看如何。

让我们实现对象池设计模式以提供数据源对象通过配置数据源使用 JNDI。

在 Spring 应用程序中,您可以使用 JNDI 查找来配置数据源。 Spring 提供了来自 Spring 的 JEE 命名空间的 <jee:jndi-lookup> 元素。让我们看看这个配置的代码。

在XML配置中,代码如下:

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource" /> 

在Java配置中,代码如下:

    @Bean 
    public JndiObjectFactoryBean dataSource() { 
      JndiObjectFactoryBean jndiObject = new JndiObjectFactoryBean(); 
      jndiObject.setJndiName("jdbc/datasource"); 
      jndiObject.setResourceRef(true); 
      jndiObject.setProxyInterface(javax.sql.DataSource.class); 
      return jndiObject; 
    } 

WebSphere 或 JBoss 等应用程序服务器允许您配置要通过 JNDI 准备的数据源。甚至像 Tomcat 这样的 Web 容器也允许您配置要通过 JNDI 准备的数据源。这些服务器管理应用程序中的数据源。这是有益的,因为数据源的性能会更好,因为应用程序服务器通常是池化的。它们可以完全在应用程序外部进行管理。这是配置要通过 JNDI 检索的数据源的最佳方法之一。如果您无法在生产环境中通过 JNDI 查找进行检索,您可以选择另一个更好的选项,我们将在下面讨论。

Configuring the data source using pool connections

以下开源技术提供汇集的数据 来源:

  • Apache commons DBCP
  • c3p0
  • BoneCP

以下代码配置 DBCP 的 BasicDataSource

基于 XML 的 DBCP 配置如下:

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
      <property name="driverClassName" value="org.h2.Driver"/> 
      <property name="url" value="jdbc:h2:tcp://localhost/bankDB"/> 
      <property name="username" value="root"/> 
      <property name="password" value="root"/> 
      <property name="initialSize" value="5"/> 
      <property name="maxActive" value="10"/> 
    </bean> 

基于 Java 的 DBCP 配置如下:

    @Bean 
    public BasicDataSource dataSource() { 
      BasicDataSource dataSource = new BasicDataSource(); 
      dataSource.setDriverClassName("org.h2.Driver"); 
      dataSource.setUrl("jdbc:h2:tcp://localhost/bankDB"); 
      dataSource.setUsername("root"); 
      dataSource.setPassword("root"); 
      dataSource.setInitialSize(5); 
      dataSource.setMaxActive(10); 
      return dataSource; 
    } 

正如您在前面的代码中看到的,还为池数据源提供程序引入了许多其他属性。下面列出了 Spring 中 BasicDataSource 类的属性:

  • initialSize: This is the number of connections created at the time of initialization of the pool.
  • maxActive: This is the maximum number of connections that can be allocated from the pool at the time of initialization of the pool. If you set this value to 0, that means there's no limit.
  • maxIdle: This is the maximum number of connections that can be idle in the pool without extras being released. If you set this value to 0, that means there's no limit.
  • maxOpenPreparedStatements: This is the maximum number of prepared statements that can be allocated from the statement pool at the time of initialization of the pool. If you set this value to 0, that means there's no limit.
  • maxWait: This is the maximum waiting time for a connection to be returned to the pool before an exception is thrown. If you set it to 1, it means wait indefinitely.
  • minEvictableIdleTimeMillis: This is the maximum time duration a connection can remain idle in the pool before it's eligible for eviction.
  • minIdle: This is the minimum number of connections that can remain idle in the pool without new connections being created.

Implementing the Builder pattern to create an embedded data source


在应用程序开发中,嵌入式数据库非常有用,因为它不需要您的应用程序连接的单独数据库服务器。 Spring 为嵌入式数据库提供了另一种 data 源。它对于生产环境来说还不够强大。我们可以将embedded数据源用于开发和测试环境。在 Spring 中,jdbc 命名空间配置了一个嵌入式数据库,H2,如下所示:

在XML配置中,H2配置如下:

    <jdbc:embedded-database id="dataSource" type="H2"> 
     <jdbc:script location="schema.sql"/> 
     <jdbc:script location="data.sql"/> 
    </jdbc:embedded-database> 

在Java配置中,H2配置如下:

    @Bean 
    public DataSource dataSource(){ 
      EmbeddedDatabaseBuilder builder =
        new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2); 
      builder.addScript("schema.sql"); 
      builder.addScript("data.sql"); 
      return builder.build(); 
    } 

正如您在前面的代码中看到的,Spring 提供了 EmbeddedDatabaseBuilder 类。它实际上实现了 Builder 设计模式来创建 EmbeddedDatabaseBuilder 类的对象。

让我们在下一节中再看一个设计模式。

Abstracting database access using the DAO pattern

数据访问层是 business 层和数据库之间的一个方面。数据访问取决于业务调用,并且取决于数据源,例如数据库、平面文件、XML 等。因此,我们可以通过提供接口来抽象所有访问。这称为数据访问对象模式。从应用程序的角度来看,访问关系数据库或使用 DAO 解析 XML 文件没有区别。

在早期版本中,EJB 提供由容器管理的实体 bean;它们是分布式的、安全的和事务性的组件。这些 bean 对客户端是非常透明的,也就是说,对于应用程序中的服务层,它们具有自动持久性,无需底层数据库的关心。但大多数情况下,您的应用程序不需要这些实体 bean 提供的功能,因为您需要将数据持久保存到数据库中。由于,实体 bean 的一些非必需功能(如网络流量)增加了,并且您的应用程序的性能受到了影响。而那个时候,实体 bean 需要在 EJB 容器内运行,这就是为什么很难测试的原因。

简而言之,如果您使用传统的 JDBC API 或更早的 EJB 版本,您的应用程序将面临以下问题:

  • In a traditional JDBC application, you merge the business tier logic with persistence logic.
  • The Persistence tier or DAO layer is not consistent for the service layer or business tier. But DAO should be consistent for the service layer in an enterprise application.
  • In a traditional JDBC application, you have to handle a lot of boilerplate code like making and closing connection, preparing statement, handling exceptions, and so on. It degrades reusability and increases development time.
  • With EJB, the entity bean was created as an overhead to the application, and was difficult to test.

让我们看看spring是如何解决这些问题的。

The DAO pattern with the Spring Framework


Spring 提供了一个全面的 JDBC 模块来设计和开发基于 JDBC 的 DAO。应用程序中的这些 DAO 负责处理 JDBC API 的所有样板代码,并有助于为数据访问提供一致的 API。在 Spring JDBC 中,DAO 是一个通用对象,用于访问 business 层的数据,它为业务层的服务。 DAO 类背后的主要目标是从业务层的服务中抽象出底层数据访问逻辑。

在我们之前的示例中,我们看到了比萨公司如何帮助我们理解资源管理问题,现在,我们将继续我们的银行应用程序。让我们看一下如何在应用程序中实现 DAO 的以下示例。假设,在我们的银行应用程序中,我们想要在城市的一个分行中的账户总数。为此,我们将首先为 DAO 创建一个接口。如前所述,它将编程提升为接口。这是设计原则的最佳实践之一。这个DAO接口会被注入业务层的服务,我们可以根据应用程序中的底层数据库创建多个DAO接口的具体类。这意味着我们的 DAO 层将与业务层保持一致。让我们创建一个DAO接口,如下所示:

    package com.packt.patterninspring.chapter7.bankapp.dao; 
    public interface AccountDao { 
      Integer totalAccountsByBranch(String branchName); 
    } 

让我们看看使用 Spring 的 JdbcDaoSupport 类的 DAO 接口的具体实现:

    package com.packt.patterninspring.chapter7.bankapp.dao; 
 
    import org.springframework.jdbc.core.support.JdbcDaoSupport; 
    public class AccountDaoImpl extends JdbcDaoSupport implements
     AccountDao { 
       @Override 
       public Integer totalAccountsByBranch(String branchName) { 
         String sql = "SELECT count(*) FROM Account WHERE branchName =
          "+branchName; 
         return this.getJdbcTemplate().queryForObject(sql,
          Integer.class); 
       } 
    } 

在上述代码中,可以看到 AccountDaoImpl 类实现了 AccountDao DAO 接口,并扩展了 Spring 的 JdbcDaoSupport 类以简化基于 JDBC 的开发。此类通过使用 getJdbcTemplate() 为其子类提供 JdbcTemplateJdbcDaoSupport 类与数据源相关联,并提供在 DAO 中使用的 JdbcTemplate 对象。

Working with JdbcTemplate

正如您之前所了解的,Spring 的 JdbcTemplate 解决了应用程序中的两个主要问题。它解决了应用程序中数据访问代码的冗余代码问题以及不良异常handling。如果您的应用程序中没有 JdbcTemplate,查询一行只需要 20% 的代码,但 80% 是处理异常和管理资源的样板。如果您使用 JdbcTemplate,则无需担心 80% 的样板代码。 Spring的JdbcTemplate,简而言之,负责以下工作:

  • Acquisition of the connection
  • Participation in the transaction
  • Execution of the statement
  • Processing of the result set
  • Handling any exceptions
  • Release of the connection

让我们看看在应用程序中何时使用 JdbcTemplate,以及如何创建它。

When to use JdbcTemplate

JdbcTemplatestandalone 应用程序中以及在需要 JDBC 的任何时候都很有用。它适用于实用程序或测试代码以清理杂乱的遗留代码。此外,在任何分层应用程序中,您都可以实现存储库或数据访问对象。让我们看看如何在应用程序中创建它。

Creating a JdbcTemplate in an application

如果你想创建一个 JdbcTemplate 类的对象来访问你的 Spring 应用程序中的数据,你需要记住它需要一个 DataSource 创建数据库连接。让我们创建 一次模板,然后重复使用它。不要为每个线程创建一个,它在构造后是线程安全的:

    JdbcTemplate template = new JdbcTemplate(dataSource); 

让我们在 Spring 中使用以下 @Bean 方法配置一个 JdbcTemplate bean:

    @Bean 
    public JdbcTemplate jdbcTemplate(DataSource dataSource) { 
      return new JdbcTemplate(dataSource); 
    } 

在前面的代码中,我们使用构造函数注入将 DataSourceJdbcTemplate bean 注入 Spring 应用程序中。被引用的 dataSource bean 可以是 javax.sql.DataSource 的任何实现。让我们看看如何在基于 JDBC 的存储库中使用 JdbcTemplate bean 来访问应用程序中的数据库。

Implementing a JDBC-based repository

我们可以使用 Spring 的 JdbcTemplate 类来implement Spring 中的存储库应用。让我们看看如何基于 JDBC 模板实现存储库类:

    package com.packt.patterninspring.chapter7.bankapp.repository; 
 
    import java.sql.ResultSet; 
    import java.sql.SQLException; 
 
    import javax.sql.DataSource; 
 
    import org.springframework.jdbc.core.JdbcTemplate; 
    import org.springframework.jdbc.core.RowMapper; 
    import org.springframework.stereotype.Repository; 
  
    import com.packt.patterninspring.chapter7.bankapp.model.Account; 
    @Repository 
    public class JdbcAccountRepository implements AccountRepository{ 
    
      JdbcTemplate jdbcTemplate; 
    
      public JdbcAccountRepository(DataSource dataSource) { 
        super(); 
        this.jdbcTemplate = new JdbcTemplate(dataSource); 
      } 
 
      @Override 
      public Account findAccountById(Long id){ 
        String sql = "SELECT * FROM Account WHERE id = "+id; 
        return jdbcTemplate.queryForObject(sql,
         new RowMapper<Account>(){ 
           @Override 
           public Account mapRow(ResultSet rs, int arg1) throws
           SQLException { 
             Account account = new Account(id); 
             account.setName(rs.getString("name")); 
             account.setBalance(new Long(rs.getInt("balance"))); 
             return account; 
           } 
         }); 
      } 
    } 

在前面的代码中,DataSource bean 通过构造函数注入被 JdbcAccountRepository 类注入。通过使用这个 DataSource,我们创建了一个 JdbcTemplate 对象来访问数据。 JdbcTemplate 提供了以下方法来访问数据库中的数据:

  • queryForObject(..): This is a query for simple java types (int, long, String, Date ...) and for custom domain objects.
  • queryForMap(..): This is used when expecting a single row. JdbcTemplate returns each row of a ResultSet as a Map.
  • queryForList(..): This is used when expecting multiple rows.

Note

注意 queryForIntqueryForLong 自 Spring 3.2 起已被弃用;您可以只使用 queryForObject 代替(API 在 Spring 3 中得到改进)。

通常,将关系数据映射到域对象是很有用的,例如,将 ResultSet 映射到最后一个代码中的 Account。 Spring 的 JdbcTemplate 通过使用回调方法来支持这一点。让我们在下一节讨论 Jdbc 回调接口。

Jdbc callback interfaces

Spring为JDBC提供了三个回调接口,如下:

  • Implementing RowMapper: Spring provides a RowMapper interface for mapping a single row of a ResultSet to an object. It can be used for both single and multiple row queries. It is parameterized as of Spring 3.0:
      public interface RowMapper<T> { 
        T mapRow(ResultSet rs, int rowNum) 
        throws SQLException; 
      } 
  • Let's understand this with the help of an example.
Creating a RowMapper class

以下 示例中,AccountRowMapper 类实现了 RowMapper接口:

    package com.packt.patterninspring.chapter7.bankapp.rowmapper; 
 
    import java.sql.ResultSet; 
    import java.sql.SQLException; 
    import org.springframework.jdbc.core.RowMapper; 
    import com.packt.patterninspring.chapter7.bankapp.model.Account; 
    public class AccountRowMapper implements RowMapper<Account>{ 
      @Override 
      public Account mapRow(ResultSet rs, int id) throws SQLException { 
        Account account = new Account(); 
        account.setId(new Long(rs.getInt("id"))); 
        account.setName(rs.getString("name")); 
        account.setBalance(new Long(rs.getInt("balance"))); 
        return account; 
      } 
    } 

在上述代码中,AccountRowMapper 类将结果集的一行映射到域对象。这个 row-mapper 类实现了 Spring Jdbc 模块的 RowMapper 回调接口。

使用 JdbcTemplate 查询单行

现在让我们看看行映射器如何在以下代码中将单行映射到应用程序中的域对象:

    public Account findAccountById(Long id){ 
      String sql = "SELECT * FROM Account WHERE id = "+id; 
      return jdbcTemplate.queryForObject(sql, new AccountRowMapper()); 
    } 

在这里,不需要为 Account 对象添加类型转换。 AccountRowMapper 类将行映射到 Account 对象。

查询多行

以下代码显示了行映射器如何将多行映射到域对象列表:

    public List<Account> findAccountById(Long id){ 
      String sql = "SELECT * FROM Account "; 
      return jdbcTemplate.queryForList(sql, new AccountRowMapper()); 
    } 

ResultSet 的每一行都映射到域对象时,RowMapper 是最佳选择。

Implementing RowCallbackHandler

Spring 在没有返回对象时提供了一个更简单的 RowCallbackHandler 接口。它用于将行流式传输到文件,将行转换为 XML,并在添加到集合之前对其进行过滤。但是 SQL 中的过滤效率更高,并且比用于大型查询的 JPA 更快。让我们看一下以下的例子:

    public interface RowCallbackHandler { 
      void processRow(ResultSet rs) throws SQLException; 
    } 

 

使用 RowCallbackHandler 的示例

以下代码是应用程序中 RowCallbackHandler 的示例:

    package com.packt.patterninspring.chapter7.bankapp.callbacks; 
    import java.sql.ResultSet; 
    import java.sql.SQLException; 
    import org.springframework.jdbc.core.RowCallbackHandler; 
    public class AccountReportWriter implements RowCallbackHandler { 
      public void processRow(ResultSet resultSet) throws SQLException { 
        // parse current row from ResultSet and stream to output 
        //write flat file, XML 
      } 
    } 

在前面的代码中,我们创建了一个 RowCallbackHandler 实现; AccountReportWriter 类实现了这个接口来处理从数据库返回的结果集。让我们看看下面的代码如何使用 AccountReportWriter 回调类:

    @Override 
    public void generateReport(Writer out, String branchName) { 
      String sql = "SELECT * FROM Account WHERE branchName = "+
       branchName; 
      jdbcTemplate.query(sql, new AccountReportWriter()); 
    } 

RowCallbackHandler 是不应该从每一行的回调方法返回值的最佳选择,尤其是对于大型查询。

Implementing ResultSetExtractor

Spring 提供了一个 ResultSetExtractor 接口,用于一次处理整个 ResultSet。在这里,您负责迭代 ResultSet,例如,将整个 ResultSet 映射到单个对象。让我们看看 以下 示例:

    public interface ResultSetExtractor<T> { 
      T extractData(ResultSet rs) throws SQLException,
      DataAccessException; 
    } 

使用 ResultSetExtractor 的示例

以下代码行在应用程序中实现了 ResultSetExtractor 接口:

    package com.packt.patterninspring.chapter7.bankapp.callbacks; 
 
    import java.sql.ResultSet; 
    import java.sql.SQLException; 
    import java.util.ArrayList; 
    import java.util.List; 
 
    import org.springframework.dao.DataAccessException; 
    import org.springframework.jdbc.core.ResultSetExtractor; 
 
    import com.packt.patterninspring.chapter7.bankapp.model.Account; 
 
    public class AccountExtractor implements
     ResultSetExtractor<List<Account>> { 
       @Override 
       public List<Account> extractData(ResultSet resultSet) throws
        SQLException, DataAccessException { 
          List<Account> extractedAccounts = null; 
          Account account = null; 
          while (resultSet.next()) { 
            if (extractedAccounts == null) { 
              extractedAccounts = new ArrayList<>(); 
              account = new Account(resultSet.getLong("ID"),
               resultSet.getString("NAME"), ...); 
            } 
            extractedAccounts.add(account); 
          } 
          return extractedAccounts; 
       } 
    } 

前面这个类AccountExtractor,实现了ResultSetExtractor,用于为返回的结果集的全部数据创建一个对象从数据库。让我们看看如何在您的应用程序中使用这个类:

    public List<Account> extractAccounts() { 
      String sql = "SELECT * FROM Account"; 
      return jdbcTemplate.query(sql, new AccountExtractor()); 
    } 

前面的代码负责访问银行的所有帐户,并使用 AccountExtractor 类准备帐户列表。该类实现了Spring Jdbc模块的ResultSetExtractor回调接口。

ResultSet 的多行映射到单个对象时,ResultSetExtractor 是最佳选择。

Best practices for Jdbc and configuring JdbcTemplate


JdbcTemplate 类的实例在配置后是线程安全的。作为在 Spring 应用程序中配置 JdbcTemplate 的最佳实践,它应该在 constructor 通过将数据源 bean 传递 将数据源 bean 注入或设置器注入JdbcTemplate 类的构造函数参数。这导致 DAO 部分看起来如下所示:

    @Repository 
    public class JdbcAccountRepository implements AccountRepository{ 
      JdbcTemplate jdbcTemplate; 
    
      public JdbcAccountRepository(DataSource dataSource) { 
        super(); 
        this.jdbcTemplate = new JdbcTemplate(dataSource); 
      } 
      //... 
    } 
    Let's see some best practices to configure a database and write
    the code for the DAO layer: 
  • If you want to configure the embedded database at the time of development of the application, as the best practice, the embedded database will always be assigned a uniquely generated name. This is because in the Spring container, the embedded database is made available by configuring a bean of type javax.sql.DataSource, and that data source bean is injected to the data access objects.
  • Always use object pooling; this can be achieved in two ways:
    • Connection pooling: It allows the pool manager to keep the connections in a pool after they are closed
    • Statement pooling: It allows the driver to reuse the prepared Statement objects.
      • Choose the commit mode carefully
      • Consider removing the auto-commit mode for your application, and use manual commit instead to better control the commit logic, as follows:
                  Connection.setAutoCommit(false); 

Summary


没有数据的应用程序就像没有燃料的汽车。数据是应用程序的核心。世界上可能存在一些应用程序没有数据,但这些应用程序只是静态博客等展示应用程序。数据是应用程序的重要组成部分,您需要为应用程序开发数据访问代码。这段代码应该非常简单、健壮且可定制。

在传统的 Java 应用程序中,您可以使用 JDBC 来访问数据。这是一种非常基本的方式,但有时,定义规范、处理 JDBC 异常、建立数据库连接、加载驱动程序等非常混乱。 Spring 通过删除样板代码和简化 JDBC 异常处理来简化这些事情。您只需编写应在应用程序中执行的 SQL,其余部分由 Spring 框架管理。

在本章中,您看到了 Spring 如何在后端为数据访问和数据持久性提供支持。 JDBC 很有用,但直接使用 JDBC API 是一项乏味且容易出错的任务。 JdbcTemplate 简化了数据访问,并加强了一致性。 Spring 的数据访问使用分层架构原则——高层不应该知道数据管理。它通过数据访问异常隔离 SQLException,并创建一个层次结构以使它们更易于处理。