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技術乾貨,讓我們一起進步。