• Spring JDBC


    用过JDBC(Java DataBase Connectivity,Java数据库连接)的人都知道,JDBC非常臃肿,一点也不可爱。以致于我们每次使用JDBC操作数据库时,总会忍不住吐槽。为了让大家少些吐槽,多些舒心;致力于简化Java开发的Spring果断出手,简化了JDBC,把它封装成为Spring旗下的一个重要模块。这个模块就是著名的Spring JDBC。至于简化了多少,且让我们先用传统的JDBC实现一个小项目,再用Spring JDBC对其进行改进,进而比较直观地了解Spring JDBC对JDBC做的简化,学习Spring JDBC的基础知识。

    这个例子非常简单,就往数据库里插入人的信息,之后查询出来进行显示。因此,我们需要创建一个数据库,一张数据库表,用于保存人的信息。而这,可以通过打开先前安装过的MYSQL Workbench,执行以下SQL脚本进行创建:

     1 CREATE DATABASE sj_person_jdbc DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
     2 
     3 USE sj_person_jdbc;
     4 
     5 CREATE TABLE person (
     6     person_id     INT         NOT NULL AUTO_INCREMENT, # 数据库表ID
     7     person_name   VARCHAR(50) NOT NULL,                # 名字
     8     person_gender VARCHAR(50) NOT NULL,                # 性别
     9     PRIMARY KEY (person_id)                            # 添加主键
    10 ) ENGINE = INNODB;

    建好数据库之后,还需新建一个Java项目,用于实现前文提到的功能。这个项目名叫person,同先前一样打开IntelliJ IDEA进行创建即可。不同的是,这个项目需与数据库打交道。因此,还需添加数据库驱动程序JAR包。数据库驱动程序JAR包无需额外下载,我们安装MYSQL的时候已经装上了。只需打开MYSQL的安装目录(默认装在C:Program Files (x86)MySQLConnector J 8.0),把文件mysql-connector-java-8.0.23.jar复制到项目的libs目录即可。

    注意:复制数据库驱动程序JAR包到libs目录之后,可能需要重启一下IntelliJ IDEA。不然,IntelliJ IDEA可能不会加载新增的JAR包,导致编译错误。

    我们知道使用JDBC操作数据库之前,首先应该获取数据库连接。获取数据库连接的方式通常有两种:一种是通过DriverManager,一种是通过数据源。

    通过DriverManager获取数据库连接并不是一个好的方式。为什么呢?因为DriverManager并不支持连接池(Connection Pool)。这意味着每次通过DriverManager获取数据库连接时都得建立新的连接。这个过程涉及网络连接的建立,协议的交换,身份的验证,等等。既耗资源,又费时间,一点也不划算。因此,进行软件开发的时候,通常不会选用这种方式。

    于是,第二种方式出现了。这种方式涉及JDBC提供的一个接口:javax.sql.DataSource。这个接口就是通常所说的数据源。目前,有很多厂商对该接口进行了实现,使之作为一个组件具有把数据库连接缓存到连接池,以及对连接池里的连接进行管理的功能。这样,每次通过数据源获取数据库连接时,只需从连接池里获取,无需花费大量的资源和时间建立新的连接;用完之后把连接交还连接池就行。由此可见,通过数据源获取数据库连接可以大幅提高性能。正因如此,进行软件开发的时候,通常选用这种方式。我们的小项目也不例外。

    需要了解的是,虽然在这世上实现和提供数据源的厂商远远不止一家。但是,常用的数据源总是那样几种。比如DBCP,C3P0,Druid,等等。我们的小项目选用的是DBCP。而这,需要我们下载DBCP相关的JAR包并添加到我们的小项目中。如下:
    1.Apache Commons DBCP,下载文件commons-dbcp2-2.8.0-bin.zip:
     http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi
    2.Apache Commons Pool,下载文件commons-pool2-2.9.0-bin.zip:
     http://commons.apache.org/proper/commons-pool/download_pool.cgi
    3.Apache Commons Logging,下载文件commons-logging-1.2-bin.zip:
     http://commons.apache.org/proper/commons-logging/download_logging.cgi

    下载完成之后解压.zip文件,把下面这些JAR包复制到libs目录即可:
    1.commons-dbcp2-2.8.0.jar
    2.commons-pool2-2.9.0.jar
    3.commons-logging-1.2.jar

    现在,万事俱备,可以开始写代码了。我们的目标是使用JDBC实现两个功能,即往数据库里插入人的信息,以及从数据库里查出人的信息进行显示。因此,我们需要一个数据模型类,用于保存人的信息。如下所示:

     1 package com.dream;
     2 
     3 public class Person {
     4     private int id = 0;
     5     private String name = null;
     6     private String gender = null;
     7 
     8     public int getId() {
     9         return this.id;
    10     }
    11 
    12     public void setId(int id) {
    13         this.id = id;
    14     }
    15 
    16     public String getName() {
    17         return this.name;
    18     }
    19 
    20     public void setName(String name) {
    21         this.name = name;
    22     }
    23 
    24     public String getGender() {
    25         return this.gender;
    26     }
    27 
    28     public void setGender(String gender) {
    29         this.gender = gender;
    30     }
    31 }

    数据模型Person类定义了三个属性:id(数据库表ID),name(名字),gender(性别)。这三个属性是和数据库表person的三个字段一一对应的,刚好能够作为数据模型,在与数据库打交道时保存人的信息。当然,除了数据模型,我们还需考虑如何定义一个数据存取类,用于插入和查询人的信息。如下所示:

      1 package com.dream;
      2 
      3 import java.sql.*;
      4 import java.util.*;
      5 import javax.sql.*;
      6 import org.apache.commons.dbcp2.*;
      7 
      8 public class DaoPerson {
      9     public int addPerson(Person person) {
     10         int updatedCount = 0;
     11         Connection connection = null;
     12         PreparedStatement statement = null;
     13         SQLException sqlException = null;
     14         try {
     15             var sql = " INSERT INTO person"
     16                     + "   (person_name, person_gender)"
     17                     + " VALUES"
     18                     + "   (?, ?)";
     19             var dataSource = this.getDataSource();
     20             connection = dataSource.getConnection();
     21             statement = connection.prepareStatement(sql);
     22             statement.setString(1, person.getName());
     23             statement.setString(2, person.getGender());
     24             updatedCount = statement.executeUpdate();
     25         } catch (SQLException e) {
     26             sqlException = e;
     27         } finally {
     28             if (statement != null) {
     29                 try {
     30                     statement.close();
     31                 } catch (SQLException e) {
     32                     if (sqlException == null) {
     33                         sqlException = e;
     34                     }
     35                 }
     36             }
     37             if (connection != null) {
     38                 try {
     39                     connection.close();
     40                 } catch (SQLException e) {
     41                     if (sqlException == null) {
     42                         sqlException = e;
     43                     }
     44                 }
     45             }
     46             if (sqlException != null) {
     47                 throw new RuntimeException(sqlException);
     48             }
     49         }
     50         return updatedCount;
     51     }
     52 
     53     public List<Person> queryPersonByName(String personName) {
     54         List<Person> personList = new ArrayList<>();
     55         Connection connection = null;
     56         PreparedStatement statement = null;
     57         SQLException sqlException = null;
     58         try {
     59             var sql = " SELECT"
     60                     + "   person_id, person_name, person_gender"
     61                     + " FROM"
     62                     + "   person"
     63                     + " WHERE"
     64                     + "   person_name = ?";
     65             var dataSource = this.getDataSource();
     66             connection = dataSource.getConnection();
     67             statement = connection.prepareStatement(sql);
     68             statement.setString(1, personName);
     69             var result = statement.executeQuery();
     70             while (result.next()) {
     71                 var person = new Person();
     72                 person.setId(result.getInt(1));
     73                 person.setName(result.getString(2));
     74                 person.setGender(result.getString(3));
     75                 personList.add(person);
     76             }
     77         } catch (SQLException e) {
     78             sqlException = e;
     79         } finally {
     80             if (statement != null) {
     81                 try {
     82                     statement.close();
     83                 } catch (SQLException e) {
     84                     if (sqlException == null) {
     85                         sqlException = e;
     86                     }
     87                 }
     88             }
     89             if (connection != null) {
     90                 try {
     91                     connection.close();
     92                 } catch (SQLException e) {
     93                     if (sqlException == null) {
     94                         sqlException = e;
     95                     }
     96                 }
     97             }
     98             if (sqlException != null) {
     99                 throw new RuntimeException(sqlException);
    100             }
    101         }
    102         return personList;
    103     }
    104 
    105     private DataSource getDataSource() {
    106         var dataSource = new BasicDataSource();
    107         dataSource.setUsername("root");
    108         dataSource.setPassword("123456");
    109         dataSource.setUrl("jdbc:mysql://localhost:3306/sj_person_jdbc");
    110         dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    111         return dataSource;
    112     }
    113 }

    经过一段时间的努力,我们终于把数据存取类DaoPerson(DAO,Data Access Object,数据存取对象)写完了。不禁感叹,该类的代码真的太长了!却主要定义了三个方法:getDataSource,addPerson,queryPersonByName。

    getDataSource方法只做一件事:创建数据源对象,也就是创建BasicDataSource类的实例。BasicDataSource类是DBCP提供的一种数据源实现,实现了javax.sql.DataSource接口,使之具有缓存数据库连接到连接池以及对连接池里的连接进行管理的功能。之后,再把数据库用户名,密码,URL以及驱动类这些信息赋给刚刚创建的数据源对象;最后返回数据源对象。这样,其它代码就能调用这个方法创建数据源对象,再经数据源对象获得数据库连接了。

    addPerson方法实现了数据的插入,也就是把人的信息插入数据库。这个过程涉及这些步骤:
    01.调用getDataSource方法创建数据源对象。
    02.通过数据源对象获得数据库连接对象。
    03.通过数据库连接对象获得PreparedStatement对象。
    04.调用PreparedStatement对象的方法设置名字,性别两个SQL参数。
    05.执行SQL语句插入人的信息。
    06.处理SQL语句执行异常。
    07.关闭PreparedStatement
    08.处理PreparedStatement关闭异常。
    09.关闭数据库连接。
    10.处理数据库连接关闭异常。

    queryPersonByName方法实现了数据的查询,也就是根据人的名字查出人的信息。这个过程涉及这些步骤:
    01.调用getDataSource方法创建数据源对象。
    02.通过数据源对象获得数据库连接对象。
    03.通过数据库连接对象获得PreparedStatement对象。
    04.调用PreparedStatement对象的方法设置名字这个SQL参数。
    05.执行SQL语句查出人的信息。
    06.处理SQL语句执行异常。
    07.关闭PreparedStatement
    08.处理PreparedStatement关闭异常。
    09.关闭数据库连接。
    10.处理数据库连接关闭异常。

    看完这些步骤之后,想必“我和我的小伙伴们都惊呆了”。addPerson和queryPersonByName方法相比,除了第四步,第五步之外,其它步骤竟然都是一样的!这意味着我们正在不知疲倦地重复敲写同样的代码(俗称样板代码)实现不同的数据库操作。

    这,不是糟透了吗?

    于是我们不禁追问:“难道就没有什么办法能够消除这些糟糕的样板代码,让事情变得简单一些,优雅一些吗?”

    当然有的。比如,我们可以写些数据库存取类,把JDBC那些样板代码封装起来,尔后使用这些封装好的类进行数据库存取,这样不就可以消除样板代码,让事情简单起来了吗?关于这点,从来都是英雄所见略同。Spring也是这样想的。于是Spring大刀阔斧,把JDBC那些样板代码封装起来,消除了样板代码,产出Spring JDBC这个模块。至于Spring JDBC能够简化多少代码,且让我们使用Spring JDBC重新实现一下addPerson方法,看看实际的效果。如下所示:

     1 public int addPerson(Person person) {
     2     var sql = " INSERT INTO person"
     3             + "   (person_name, person_gender)"
     4             + " VALUES"
     5             + "   (?, ?)";
     6     var dataSource = this.getDataSource();
     7     var jdbcTemplate = new JdbcTemplate(dataSource);
     8     var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() {
     9         @Override
    10         public void setValues(PreparedStatement preparedStatement) throws SQLException {
    11             preparedStatement.setString(1, person.getName());
    12             preparedStatement.setString(2, person.getGender());
    13         }
    14     });
    15     return updatedCount;
    16 }

    哇,代码一下就减少了,而且减少不止一点!以前,我们进行数据插入时,总要经过以下十步:
    01.调用getDataSource方法创建数据源对象。
    02.通过数据源对象获得数据库连接对象。
    03.通过数据库连接对象获得PreparedStatement对象。
    04.调用PreparedStatement对象的方法设置名字,性别两个SQL参数。
    05.执行SQL语句插入人的信息。
    06.处理SQL语句执行异常。
    07.关闭PreparedStatement
    08.处理PreparedStatement关闭异常。
    09.关闭数据库连接。
    10.处理数据库连接关闭异常。

    如今不用了,只需三步即可搞定:
    1.调用getDataSource方法创建数据源对象。
    2.以数据源对象作为参数创建JdbcTemplate对象。
    3.调用JdbcTemplate对象的update方法执行SQL语句,插入人的信息。

    超级简单,对不对?于是问题来了:“通过两种实现方式的强烈对比,我们深刻体会到了Spring JDBC对JDBC的简化。可是,JdbcTemplate是什么呀?Spring JDBC封装JDBC之后产生的一个类吗?我们应该怎样用它?”带着这些问题,怀揣着美好的求知欲,我们踏上征途,上下求索。正如大家想的那样,JdbcTemplate就是Spring JDBC封装JDBC之后产生的一个类,而且是一个非常重要的核心类。该类定义了一些方法,以供我们调用之后执行任何我们想要执行的SQL语句,完成任何我们想做的数据库操作。比如创建数据库表,添加数据库索引,增删改查数据,等等。前文实现的addPerson方法正是通过调用JdbcTempdate的update方法实现人的信息的插入的。update方法的签名如下:

    public int update(String sql, PreparedStatementSetter pss)

    update方法接受两个参数。第一个参数是String类型的,用于指定即将执行的SQL语句;第二个参数是PreparedStatementSetter类型的,用于指定PreparedStatementSetter类型的对象,设置SQL参数。addPerson方法调用update方法时,指定了这条SQL语句:

    1 var sql = " INSERT INTO person"
    2         + " (person_name, person_gender)"
    3         + " VALUES"
    4         + " (?, ?)";

    这条SQL语句具有两个参数占位符,分别对应即将插入的person_name,person_gender。这意味着执行这条SQL语句之前,需先设好这些SQL参数。问题在于,这些SQL参数是怎样设置的呢?

    回答这个问题之前,最好先来瞧瞧update方法执行SQL语句的过程。大概如下:
    1.通过数据源对象获得数据库连接对象。
    2.通过数据库连接对象获得PreparedStatement对象。
    3.以PreparedStatement对象作为参数,调用PreparedStatementSetter接口的setValues方法设置SQL参数。
    4.执行SQL语句插入数据。
    5.关闭PreparedStatement,关闭数据库连接,处理异常。

    看到了吧?问题的关键在于第三步。而这,与PreparedStatementSetter接口息息相关。PreparedStatementSetter接口定义如下:

    1 @FunctionalInterface
    2 public interface PreparedStatementSetter {
    3     void setValues(PreparedStatement ps) throws SQLException;
    4 }

    这是一个函数式接口,专门用来设置SQL参数。因此,addPerson方法调用update方法时,以匿名类实现了PreparedStatementSetter接口,并作为第二个参数传了进去。如下代码片段所示:

    1 var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() {
    2     @Override
    3     public void setValues(PreparedStatement preparedStatement) throws SQLException {
    4         preparedStatement.setString(1, person.getName());
    5         preparedStatement.setString(2, person.getGender());
    6     }
    7 });

    看到了吧?在实现了PreparedStatementSetter接口的匿名类里,setValues方法设置了两个SQL参数。这样,update方法调用这个匿名类的setValues方法之后,就能正确设置SQL参数,从而顺利执行SQL语句了。SQL语句执行完成之后,update方法将返回受影响的行数,也就是成功插入数据库的数目。

    还有,PreparedStatementSetter是一个函数式接口,自然也能使用lambda表达式进行实现。而且,如果使用lambda表达式进行实现的话,addPerson方法将会更加简洁。大家不妨动手试试。另外,为了方便大家执行无需任何参数的SQL语句,Spring还提供了以下这种重载形式的update方法:

    public int update(String sql)

    这个方法非常简单,传入一条SQL语句即可执行。大家一看就知道怎么用了,这里不作介绍。却该好好聊聊update方法能做的另外两件重要的事情。兴许大家已经猜到,除了插入之外,update方法还特别善长数据的删除和修改。调用update方法进行数据的删除和修改与调用update方法进行数据的插入并无分别,只是执行的SQL语句不同而已。这里不作太多介绍,只向大家示例两个方法,大家看了之后自然知道怎么使用。这两个方法一个名为deletePersonByName,一个名为updatePersonByName。deletePersonByName方法用于删除名字等于某值的人的信息;updatePersonByName方法用于把人的名字改成新的名字。代码如下:

    public int deletePersonByName(String personName) {
        var sql = "DELETE FROM person WHERE person_name = ?";
        var dataSource = this.getDataSource();
        var jdbcTemplate = new JdbcTemplate(dataSource);
        var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement preparedStatement) throws SQLException {
                preparedStatement.setString(1, personName);
            }
        });
        return updatedCount;
    }
    
    public int updatePersonByName(String personName, String newPersonName) {
        var sql = "UPDATE person SET person_name = ? WHERE person_name = ?";
        var dataSource = this.getDataSource();
        var jdbcTemplate = new JdbcTemplate(dataSource);
        var updatedCount = jdbcTemplate.update(sql, new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement preparedStatement) throws SQLException {
                preparedStatement.setString(1, newPersonName);
                preparedStatement.setString(2, personName);
            }
        });
        return updatedCount;
    }

    增删改既已谈过,也就是时候聊聊查询那些事了。不知大家可还记得,前文曾经写过queryPersonByName方法。当时,这个方法是用JDBC实现的,能够根据人的名字查出人的信息。现在,让我们看看这个方法能用JdbcTemplate怎么实现。如下所示:

     1 public List<Person> queryPersonByName(String personName) {
     2     var sql = " SELECT"
     3             + "   person_id, person_name, person_gender"
     4             + " FROM"
     5             + "   person"
     6             + " WHERE"
     7             + "   person_name = ?";
     8     var dataSource = this.getDataSource();
     9     var jdbcTemplate = new JdbcTemplate(dataSource);
    10     var personList = jdbcTemplate.query(sql, new PreparedStatementSetter() {
    11         @Override
    12         public void setValues(PreparedStatement preparedStatement) throws SQLException {
    13             preparedStatement.setString(1, personName);
    14         }
    15     }, new RowMapper<Person>() {
    16         @Override
    17         public Person mapRow(ResultSet resultSet, int i) throws SQLException {
    18             var person = new Person();
    19             person.setId(resultSet.getInt(1));
    20             person.setName(resultSet.getString(2));
    21             person.setGender(resultSet.getString(3));
    22             return person;
    23         }
    24     });
    25     return personList;
    26 }

    可以看到,这里调用了JdbcTemplate的query方法。方法签名如下:

    public <T> List<T> query(String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper)

    这个方法接受三个参数。前两个参数和update方法一样,用于传入即将执行的SQL语句和通过实现PreparedStatementSetter接口设置SQL参数。第三个参数则不同,是支持泛型的RowMapper<T>接口。重点在于,这个接口是干嘛用的?

    回答这个问题之前,最好先来瞧瞧query方法执行SQL语句的过程。大概如下:
    01.通过数据源对象获得数据库连接对象。
    02.通过数据库连接对象获得PreparedStatement对象。
    03.以PreparedStatement对象作为参数,调用PreparedStatementSetter接口的setValues方法设置SQL参数。
    04.执行SQL语句查询数据,得到ResultSet对象。
    05.创建一个空的结果列表。
    06.循环调用ResultSet对象的next()方法,遍历查询结果。每遍历一条查询结果,就以遍历到的ResultSet对象和当前对象的索引(也就是遍历到的第几条数据)作为参数,调用一次RowMapper<T>接口的mapRow方法。
    07.mapRow方法处理查询结果之后,返回一个对象。这个对象的类型,就是我们实现RowMapper<T>接口时指定的类型。
    08.把调用mapRow方法返回的对象存进结果列表。
    09.如此循环遍历完成之后,关闭PreparedStatement,关闭数据库连接,处理异常。
    10.返回结果列表。

    看到了吧?问题的关键在于第六步和第七步。而这,与RowMapper<T>接口息息相关。RowMapper<T>接口定义如下:

    @FunctionalInterface
    public interface RowMapper<T> {
        @Nullable
        T mapRow(ResultSet rs, int rowNum) throws SQLException;
    }

    不出大家所料,RowMapper<T>也是一个函数式接口,而且是一个支持泛型的函数式接口。专门用来处理query方法每次遍历到的查询结果。因此,queryPersonByName方法调用query方法时,以匿名类实现了RowMapper<T>接口,并作为第三个参数传了进去。如下代码片段所示:

     1 var personList = jdbcTemplate.query(sql, new PreparedStatementSetter() {
     2     @Override
     3     public void setValues(PreparedStatement preparedStatement) throws SQLException {
     4         preparedStatement.setString(1, personName);
     5     }
     6 }, new RowMapper<Person>() {
     7     @Override
     8     public Person mapRow(ResultSet resultSet, int i) throws SQLException {
     9         var person = new Person();
    10         person.setId(resultSet.getInt(1));
    11         person.setName(resultSet.getString(2));
    12         person.setGender(resultSet.getString(3));
    13         return person;
    14     }
    15 });

    看到了吧?在实现了RowMapper<T>接口的匿名类里,mapRow方法创建了Person对象,并从ResultSet对象里拿到数据之后进行填充。这样,query方法每遍历到一个ResultSet查询结果,就调用一次匿名类的mapRow方法。mapRow方法处理ResultSet查询结果之后,返回一个Person类型的对象,这个对象被query方法存进Person类型的结果列表里。待到所有结果遍历完成之后,query方法就将Person类型的结果列表返回。于是,调用query方法之后,我们能够顺利拿到Person类型的结果列表。另外,为了方便大家执行无需任何参数的SQL语句,Spring还提供了这种重载的query方法:

    public <T> List<T> query(String sql, RowMapper<T> rowMapper)

    这种重载除了缺少PreparedStatementSetter类型的参数之外,其它都是一样的。大家一看就知道怎么用了,这里不作介绍。却该好好聊聊另外一种常见的查询。很多时候,我们想查的数据并非列表形式的;而是一个整数,一个字符串,一个对象,等等。比如,这个方法执行之后,就只返回一个Person对象:

     1 public Person queryPersonById(int id) {
     2     var sql = " SELECT"
     3             + "   person_id, person_name, person_gender"
     4             + " FROM"
     5             + "   person"
     6             + " WHERE"
     7             + "   person_id = ?";
     8     var dataSource = this.getDataSource();
     9     var jdbcTemplate = new JdbcTemplate(dataSource);
    10     var person = jdbcTemplate.query(sql, new PreparedStatementSetter() {
    11         @Override
    12         public void setValues(PreparedStatement preparedStatement) throws SQLException {
    13             preparedStatement.setInt(1, id);
    14         }
    15     }, new ResultSetExtractor<Person>() {
    16         @Override
    17         public Person extractData(ResultSet resultSet) throws SQLException, DataAccessException {
    18             if (resultSet.next()) {
    19                 var person = new Person();
    20                 person.setId(resultSet.getInt(1));
    21                 person.setName(resultSet.getString(2));
    22                 person.setGender(resultSet.getString(3));
    23                 return person;
    24             } else {
    25                 return null;
    26             }
    27         }
    28     });
    29     return person;
    30 }

    这里调用了query方法的另外一种重载。方法签名如下:

    public <T> T query(String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse)

    这种重载的query方法同样接受三个参数。前两个参数是一样的。第三个参数则不同,是支持泛型的ResultSetExtractor<T>接口。重点同样在于,这个接口是干嘛用的?

    回答这个问题之前,最好先来瞧瞧query方法执行SQL语句的过程。大概如下:
    1.通过数据源对象获得数据库连接对象。
    2.通过数据库连接对象获得PreparedStatement对象。
    3.以PreparedStatement对象作为参数,调用PreparedStatementSetter接口的setValues方法设置SQL参数。
    4.执行SQL语句查询数据,得到ResultSet对象。
    5.以ResultSet对象作为参数,直接调用ResultSetExtractor<T>接口的extractData方法进行查询结果的处理。
    6.关闭PreparedStatement,关闭数据库连接,处理异常。
    7.返回extractData方法处理之后的结果。

    看到了吧?问题的关键在于第五步。而这,与ResultSetExtractor<T>接口息息相关。ResultSetExtractor<T>接口定义如下:

    1 @FunctionalInterface
    2 public interface ResultSetExtractor<T> {
    3     @Nullable
    4     T extractData(ResultSet rs) throws SQLException, DataAccessException;
    5 }

    和大家想的一样,ResultSetExtractor<T>同样也是一个函数式接口,而且是一个支持泛型的函数式接口。专门用来处理查询结果。因此,queryPersonById方法调用query方法时,以匿名类实现了ResultSetExtractor<T>接口,并作为第三个参数传了进去。如下代码片段所示:

     1 var person = jdbcTemplate.query(sql, new PreparedStatementSetter() {
     2     @Override
     3     public void setValues(PreparedStatement preparedStatement) throws SQLException {
     4         preparedStatement.setInt(1, id);
     5     }
     6 }, new ResultSetExtractor<Person>() {
     7     @Override
     8     public Person extractData(ResultSet resultSet) throws SQLException, DataAccessException {
     9         if (resultSet.next()) {
    10             var person = new Person();
    11             person.setId(resultSet.getInt(1));
    12             person.setName(resultSet.getString(2));
    13             person.setGender(resultSet.getString(3));
    14             return person;
    15         } else {
    16             return null;
    17         }
    18     }
    19 });

    看到了吧?在实现了ResultSetExtractor<T>接口的匿名类里,extractData方法调用ResultSet对象的next()方法获取查询结果,之后创建Person对象,把ResultSet的数据填充进去,返回Person对象。同样的,为了方便大家执行无需任何参数的SQL语句,Spring还提供了这种重载的query方法:

    public <T> T query(String sql, ResultSetExtractor<T> rse)

    现在,我们已经知道怎样使用Spring JDBC进行增删改查了。增删改查只是Spring JDBC提供的基本功能,还有很多东西没有讨论。比如如何进行异常处理,如何执行存储过程,如何执行事务,等等。我们将在“细说Spring JDBC”的时候进行介绍。现在,我们只需知道怎样使用Spring JDBC进行增删改查就行。却从下章开始先来瞧瞧Spring关于简化Web开发的那些趣事。欢迎大家继续阅读,谢谢大家!

    返回目录    下载代码

  • 相关阅读:
    django filefield
    django HttpResponseRedirect
    Django 自定义 error_messages={} 返回错误信息
    Django validators 官方文档
    djnago 官方关系反向查询案例
    django logging settings.py
    分页 restframe work 排序,分页,自定义过滤
    论文阅读笔记六十三:DeNet: Scalable Real-time Object Detection with Directed Sparse Sampling(CVPR2017)
    基于Distiller的模型压缩工具简介
    基于Intel OpenVINO的搭建及应用,包含分类,目标检测,及分割,超分辨
  • 原文地址:https://www.cnblogs.com/evanlin/p/15455831.html
Copyright © 2020-2023  润新知