Spring入門(十五):使用Spring JDBC操作資料庫

  • 2019 年 10 月 14 日
  • 筆記

在本系列的之前部落格中,我們從沒有講解過操作資料庫的方法,但是在實際的工作中,幾乎所有的系統都離不開數據的持久化,所以掌握操作資料庫的使用方法就非常重要。

在Spring中,操作資料庫有很多種方法,我們可以使用JDBC、Hibernate、MyBatis或者其他的數據持久化框架,本篇部落格的重點是講解下在Spring中如何通過JDBC操作資料庫。

1. 項目構建失敗解決

在講解JDBC前,我們先解決一個問題,因為本來構建正常的程式在重新構建打包時,竟然報了如下錯誤:

網上查找資料後,說是依賴的版本有衝突,於是檢查了pom.xml中之前添加的Spring的依賴:

<dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-context</artifactId>      <version>4.3.18.RELEASE</version>  </dependency>  <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-webmvc</artifactId>      <version>4.3.18.RELEASE</version>  </dependency>  <!--spring aop支援-->  <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-aop</artifactId>      <version>5.1.8.RELEASE</version>  </dependency>  <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-test</artifactId>      <version>4.3.18.RELEASE</version>      <scope>test</scope>  </dependency>

其中spring-aop的版本是5.1.8.RELEASE,而其餘3個包的版本是4.3.18.RELEASE,將spring-aop版本也修改為4.3.18.RELEASE:

<!--spring aop支援-->  <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-aop</artifactId>      <version>4.3.18.RELEASE</version>  </dependency>

此時重新構建打包,不再報錯,打包成功:

不過上面的依賴還可以簡化成下面這樣的:

<dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-webmvc</artifactId>      <version>4.3.18.RELEASE</version>  </dependency>  <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-test</artifactId>      <version>4.3.18.RELEASE</version>      <scope>test</scope>  </dependency>

因為spring-webmvc包已經包含了spring-context和spring-aop,因此沒有必要重複添加這2個依賴:

2. 配置數據源

首先執行如下語句創建MySql資料庫spring_action_db:

CREATE DATABASE spring_action_db DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

然後執行如下語句創建表book:

use spring_action_db;    create table Book  (    book_id     bigint auto_increment comment '書籍id',    book_name   varchar(50) not null comment '書名',    author      varchar(50) not null comment '作者',    create_by   varchar(20) not null comment '創建人',    create_time datetime    not null comment '創建時間',    modify_by   varchar(20) not null comment '修改人',    modify_time datetime    not null comment '修改時間',    constraint Book_pk      primary key (book_id)  )    comment '書籍';

準備就緒後,新建配置類配置下數據源:

package chapter10.config;    import org.apache.commons.dbcp2.BasicDataSource;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.ComponentScan;  import org.springframework.context.annotation.Configuration;    @Configuration  @ComponentScan("chapter10")  public class DataSourceConfig {      @Bean      public BasicDataSource dataSource() {          BasicDataSource dataSource = new BasicDataSource();          dataSource.setDriverClassName("com.mysql.jdbc.Driver");          dataSource.setUrl("jdbc:mysql://localhost:3306/spring_action_db");          dataSource.setUsername("root");          dataSource.setPassword("root");            return dataSource;      }  }

因為我們使用的是MySql資料庫,所以驅動名稱設置的是:com.mysql.jdbc.Driver。

如果你使用的是其他類型的資料庫,需要修改成對應的名稱。

因為使用到了MySql驅動,所以我們需要在pom.xml中添加如下依賴,否則在訪問資料庫時會獲取不到連接:

<!-- MySql驅動 -->  <dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>      <version>5.1.46</version>  </dependency>

3. 使用原始的JDBC程式碼

首先,新建資料庫實體類Book:

package chapter10.domain;    import java.util.Date;    public class Book {      private Long bookId;        private String bookName;        private String author;        private String createBy;        private Date createTime;        private String modifyBy;        private Date modifyTime;        public Book(String bookName, String author, String createBy) {          this.bookName = bookName;          this.author = author;          this.createBy = createBy;          this.createTime = new Date();          this.modifyBy=createBy;          this.modifyTime=new Date();      }        public Book(Long bookId, String bookName, String author, String modifyBy) {          this.bookId = bookId;          this.bookName = bookName;          this.author = author;          this.modifyBy = modifyBy;      }        public Book() {        }        // 省略get和set方法  }

然後定義數據訪問介面BookRepository,暫時只添加addBook方法:

package chapter10.db;    import chapter10.domain.Book;    public interface BookRepository {      void addBook(Book book);  }

3.1 新增數據

新建數據訪問實現類JdbcBookRepository如下所示:

package chapter10.db.jdbc;    import chapter10.db.BookRepository;  import chapter10.domain.Book;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.stereotype.Repository;    import javax.sql.DataSource;  import java.sql.Connection;  import java.sql.PreparedStatement;  import java.sql.SQLException;  import java.sql.Timestamp;  import java.util.Calendar;  import java.util.Date;    @Repository  public class JdbcBookRepository implements BookRepository {      private static final String SQL_INSERT_BOOK =              "INSERT INTO book(book_name, author, create_by, create_time, modify_by, modify_time) VALUES (?,?,?,?,?,?);";        @Autowired      private DataSource dataSource;        @Override      public void addBook(Book book) {          Connection connection = null;          PreparedStatement preparedStatement = null;            try {              Calendar calendar = Calendar.getInstance();              calendar.setTime(new Date());                connection = dataSource.getConnection();              preparedStatement = connection.prepareStatement(SQL_INSERT_BOOK);              preparedStatement.setString(1, book.getBookName());              preparedStatement.setString(2, book.getAuthor());              preparedStatement.setString(3, book.getCreateBy());              preparedStatement.setTimestamp(4, new Timestamp(calendar.getTimeInMillis()));              preparedStatement.setString(5, book.getModifyBy());              preparedStatement.setTimestamp(6, new Timestamp(calendar.getTimeInMillis()));                preparedStatement.execute();          } catch (SQLException e) {              // 異常處理相關程式碼          } finally {              try {                  if (preparedStatement != null) {                      preparedStatement.close();                  }                  if (connection != null) {                      connection.close();                  }              } catch (SQLException e) {                  // 異常處理相關程式碼              }          }      }  }

注意事項:該類添加了@Repository註解,以便Spring能夠掃描到將其註冊為bean。

值得注意的是,在這段程式碼中,我們竟然捕獲SQLException捕獲了2次,這是因為connection = dataSource.getConnection();preparedStatement.execute();preparedStatement.close();connection.close();都會拋出檢查型異常SQLException,所以方法中必須捕獲,否則會導致編譯不通過:

Connection getConnection() throws SQLException;    boolean execute() throws SQLException;    void close() throws SQLException;    void close() throws SQLException;

最後,新建單元測試類BookRepositoryTest如下所示:

package chapter10;    import chapter10.config.DataSourceConfig;  import chapter10.db.BookRepository;  import chapter10.domain.Book;  import org.junit.Test;  import org.junit.runner.RunWith;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.test.context.ContextConfiguration;  import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;    @RunWith(SpringJUnit4ClassRunner.class)  @ContextConfiguration(classes = DataSourceConfig.class)  public class BookRepositoryTest {      @Autowired      private BookRepository bookRepository;        @Test      public void testAddBook() {          Book book = new Book("Spring實戰(第4版)", "Craig Walls", "申城異鄉人");            bookRepository.addBook(book);            book = new Book("Java EE開發的顛覆者:Spring Boot實戰", "汪雲飛", "申城異鄉人");            bookRepository.addBook(book);            book = new Book("RabbitMQ實戰指南", "朱忠華", "申城異鄉人");            bookRepository.addBook(book);      }  }

運行測試方法testAddBook(),數據成功新增到資料庫:

3.2 更新數據

首先,在數據訪問介面BookRepository中添加更新方法:

void updateBook(Book book);

然後在數據訪問實現類JdbcBookRepository中實現該方法:

private static final String SQL_UPDATE_BOOK =              "UPDATE Book SET book_name = ?,author = ?,modify_by = ?,modify_time=? WHERE book_id = ?;";    @Override  public void updateBook(Book book) {      Connection connection = null;      PreparedStatement preparedStatement = null;        try {          Calendar calendar = Calendar.getInstance();          calendar.setTime(new Date());            connection = dataSource.getConnection();          preparedStatement = connection.prepareStatement(SQL_UPDATE_BOOK);          preparedStatement.setString(1, book.getBookName());          preparedStatement.setString(2, book.getAuthor());          preparedStatement.setString(3, book.getModifyBy());          preparedStatement.setTimestamp(4, new Timestamp(calendar.getTimeInMillis()));          preparedStatement.setLong(5, book.getBookId());            preparedStatement.execute();      } catch (SQLException e) {          // 異常處理相關程式碼      } finally {          try {              if (preparedStatement != null) {                  preparedStatement.close();              }              if (connection != null) {                  connection.close();              }          } catch (SQLException e) {              // 異常處理相關程式碼          }      }  }

是不是發現它的程式碼和之前的新增程式碼幾乎是一樣的,而且也不得不對檢查型異常SQLException捕獲了2次,有程式碼潔癖的人是不是忍不住想重構,哈哈。

最後,在測試類BookRepositoryTest中添加測試方法testUpdateBook,如下所示:

@Test  public void testUpdateBook() {      Book book = new Book(1L, "Spring實戰(第4版)", "Craig Walls", "zwwhnly");        bookRepository.updateBook(book);        book = new Book(2L, "Java EE開發的顛覆者:Spring Boot實戰", "汪雲飛", "zwwhnly");        bookRepository.updateBook(book);        book = new Book(3L, "RabbitMQ實戰指南", "朱忠華", "zwwhnly");        bookRepository.updateBook(book);  }

執行該測試方法,數據更新成功:

3.3 查找數據

首先,在數據訪問介面BookRepository中添加更新方法:

Book findBook(long bookId);

然後在數據訪問實現類JdbcBookRepository中實現該方法:

private static final String SQL_SELECT_BOOK =              "SELECT book_id,book_name,author,create_by,create_time,modify_by,modify_time FROM book WHERE book_id = ?;";    @Override  public Book findBook(long bookId) {      Connection connection = null;      PreparedStatement preparedStatement = null;        ResultSet resultSet = null;      Book book = null;      try {          connection = dataSource.getConnection();          preparedStatement = connection.prepareStatement(SQL_SELECT_BOOK);          preparedStatement.setLong(1, bookId);            resultSet = preparedStatement.executeQuery();            if (resultSet.next()) {              book = new Book();              book.setBookId(resultSet.getLong("book_id"));              book.setBookName(resultSet.getString("book_name"));              book.setAuthor(resultSet.getString("author"));              book.setCreateBy(resultSet.getString("create_by"));              book.setCreateTime(resultSet.getTimestamp("create_time"));              book.setModifyBy(resultSet.getString("modify_by"));              book.setModifyTime(resultSet.getTimestamp("modify_time"));          }      } catch (SQLException e) {          // 異常處理相關程式碼      } finally {          try {              if (resultSet != null) {                  resultSet.close();              }              if (preparedStatement != null) {                  preparedStatement.close();              }              if (connection != null) {                  connection.close();              }          } catch (SQLException e) {              // 異常處理相關程式碼          }      }        return book;  }

是不是發現它的程式碼和之前的新增、更新程式碼大部分是一樣的,而且也不得不對檢查型異常SQLException捕獲了2次,有程式碼潔癖的人是不是已經開始動手重構了,哈哈。

最後,在測試類BookRepositoryTest中添加測試方法testFindBook,如下所示:

@Test  public void testFindBook() {      Book book = bookRepository.findBook(1L);      Assert.assertNotNull(book);      Assert.assertEquals(book.getBookName(), "Spring實戰(第4版)");  }

執行該測試方法,數據查詢成功:

4. 使用JDBC模板

使用了原始的JDBC操作資料庫後,好多有程式碼潔癖的同學都忍不住開始重構了,因為大部分程式碼都是樣板程式碼,只有少部分才和業務邏輯相關,好消息是Spring已經幫我們重構過了,Spring將數據訪問的樣板程式碼抽象到模板類之中,我們可以直接使用模板類,從而簡化了JDBC程式碼。

4.1 新增數據

首先,在配置類DataSourceConfig中添加如下配置:

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

然後將之前新建的類JdbcBookRepository上的@Repository註解移除掉。

接著,新建數據訪問實現類JdbcTemplateBookRepository如下所示:

package chapter10.db.jdbc;    import chapter10.db.BookRepository;  import chapter10.domain.Book;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.jdbc.core.JdbcOperations;  import org.springframework.stereotype.Repository;    import java.sql.Date;    @Repository  public class JdbcTemplateBookRepository implements BookRepository {      private static final String SQL_INSERT_BOOK =              "INSERT INTO book(book_name, author, create_by, create_time, modify_by, modify_time) VALUES (?,?,?,?,?,?);";        @Autowired      private JdbcOperations jdbcOperations;        @Override      public void addBook(Book book) {          jdbcOperations.update(SQL_INSERT_BOOK, book.getBookName(),                  book.getAuthor(),                  book.getCreateBy(),                  new Date(System.currentTimeMillis()),                  book.getModifyBy(),                  new Date(System.currentTimeMillis()));      }  }

注意事項:該類添加了@Repository註解,以便Spring能夠掃描到將其註冊為bean。

很簡潔有沒有,從之前的程式碼優化到現在的程式碼,有程式碼潔癖的同學估計開心死了。

因為之前測試類BookRepositoryTest中,我們注入的是介面,所以我們不需要修改測試類的程式碼,即可直接訪問到新建的JdbcTemplateBookRepository類的實現方法:

@Autowired  private BookRepository bookRepository;

運行之前的測試方法testAddBook(),數據成功新增到資料庫:

4.2 更新數據

在數據訪問實現類JdbcTemplateBookRepository中添加如下程式碼:

private static final String SQL_UPDATE_BOOK =              "UPDATE Book SET book_name = ?,author = ?,modify_by = ?,modify_time=? WHERE book_id = ?;";    @Override  public void updateBook(Book book) {      jdbcOperations.update(SQL_UPDATE_BOOK, book.getBookName(),              book.getAuthor(),              book.getModifyBy(),              new Timestamp(System.currentTimeMillis()),              book.getBookId());  }

然後簡單修改下之前的測試方法testUpdateBook():

@Test  public void testUpdateBook() {      Book book = new Book(4L, "Spring實戰(第4版)", "Craig Walls", "zwwhnly");        bookRepository.updateBook(book);        book = new Book(5L, "Java EE開發的顛覆者:Spring Boot實戰", "汪雲飛", "zwwhnly");        bookRepository.updateBook(book);        book = new Book(6L, "RabbitMQ實戰指南", "朱忠華", "zwwhnly");        bookRepository.updateBook(book);  }

運行之前的測試方法testUpdateBook(),數據更新成功:

4.3 查找數據

在數據訪問實現類JdbcTemplateBookRepository中添加如下程式碼:

private static final String SQL_SELECT_BOOK =              "SELECT book_id,book_name,author,create_by,create_time,modify_by,modify_time FROM book WHERE book_id = ?;";    @Override  public Book findBook(long bookId) {      return jdbcOperations.queryForObject(SQL_SELECT_BOOK, new BookRowMapper(), bookId);  }    private static final class BookRowMapper implements RowMapper<Book> {        @Override      public Book mapRow(ResultSet resultSet, int i) throws SQLException {          Book book = new Book();          book.setBookId(resultSet.getLong("book_id"));          book.setBookName(resultSet.getString("book_name"));          book.setAuthor(resultSet.getString("author"));          book.setCreateBy(resultSet.getString("create_by"));          book.setCreateTime(resultSet.getTimestamp("create_time"));          book.setModifyBy(resultSet.getString("modify_by"));          book.setModifyTime(resultSet.getTimestamp("modify_time"));              return book;      }  }

運行之前的測試方法testFindBook(),數據查詢成功:

5. 源碼及參考

源碼地址:https://github.com/zwwhnly/spring-action.git,歡迎下載。

Craig Walls 《Spring實戰(第4版)》

6. 最後

歡迎掃碼關注微信公眾號:「申城異鄉人」,定期分享Java技術乾貨,讓我們一起進步。