JdbcTemplate 是Spring提供的一套JDBC模板框架,利用AOP 技術來解決直接使用JDBC時大量重復代碼的問題。JdbcTemplate雖然沒有MyBatis 那么靈活,但是直接使用JDBC要方便很多。Spring Boot中對Jdbc Template的使用提供了自動化配置類JdbcTemplateAutoConfiguration,部分源碼如下:
1 package org.springframework.boot.autoconfigure.jdbc; 2 3 // 省略 import 行 4 5 @Configuration 6 // 僅在類 DataSource,JdbcTemplate 存在於 classpath 時生效, 7 // 這兩個類屬於 spring-jdbc 8 @ConditionalOnClass({ DataSource.class, JdbcTemplate.class }) 9 // 僅在單數據源bean存在時才生效 10 @ConditionalOnSingleCandidate(DataSource.class) 11 // 在數據源自動配置應用之后應用 12 @AutoConfigureAfter(DataSourceAutoConfiguration.class) 13 // 確保前綴為 spring.jdbc 的配置參數被加載到 bean JdbcProperties 14 @EnableConfigurationProperties(JdbcProperties.class) 15 public class JdbcTemplateAutoConfiguration { 16 17 // 內嵌配置類 18 @Configuration 19 static class JdbcTemplateConfiguration { 20 21 private final DataSource dataSource; 22 23 private final JdbcProperties properties; 24 25 JdbcTemplateConfiguration(DataSource dataSource, JdbcProperties properties) { 26 this.dataSource = dataSource; 27 this.properties = properties; 28 } 29 30 // 定義 bean JdbcTemplate jdbcTemplate 31 @Bean 32 @Primary 33 // 僅在此bean沒有被定義時才定義 34 @ConditionalOnMissingBean(JdbcOperations.class) 35 public JdbcTemplate jdbcTemplate() { 36 JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource); 37 JdbcProperties.Template template = this.properties.getTemplate(); 38 jdbcTemplate.setFetchSize(template.getFetchSize()); 39 jdbcTemplate.setMaxRows(template.getMaxRows()); 40 if (template.getQueryTimeout() != null) { 41 jdbcTemplate 42 .setQueryTimeout((int) template.getQueryTimeout().getSeconds()); 43 } 44 return jdbcTemplate; 45 } 46 47 } 48 49 // 內嵌配置類 50 @Configuration 51 // 導入 JdbcTemplateConfiguration 配置類 52 @Import(JdbcTemplateConfiguration.class) 53 static class NamedParameterJdbcTemplateConfiguration { 54 55 // 定義 bean NamedParameterJdbcTemplate namedParameterJdbcTemplate 56 @Bean 57 @Primary 58 @ConditionalOnSingleCandidate(JdbcTemplate.class) 59 @ConditionalOnMissingBean(NamedParameterJdbcOperations.class) 60 public NamedParameterJdbcTemplate namedParameterJdbcTemplate( 61 JdbcTemplate jdbcTemplate) { 62 return new NamedParameterJdbcTemplate(jdbcTemplate); 63 } 64 65 } 66 ...... 67 }
從上面這段源碼中可以看出,當classpath下存在DataSource和JdbcTemplate 並且DataSource只有一個實例時,自動配置才會生效,若開發者沒有提供JdbcOperations,則Spring Boot會自動向容器中注入一個JdbcTemplate(Jdbc Template是JdbcOperations的子類)。由此可以看到,開發者想要使用Jdbc Template,只需要提供JdbcTemplate的依賴和DataSource依賴即可。具體操作步驟
1,創建數據庫表並插入數據
1 CREATE DATABASE 'chapter05'DEFAULT CHARACTER SET utf8; 2 USE‘chapter05; 3 CREATE TABLE book( 4 id int(11)NOT NULL AUTO INCREMENT, 5 name varchar(128)DEFAULT NULL, 6 author varchar(64)DEFAULT NULL, 7 PRIMARY KEY('id') 8 ENGINE=InnoDB DEFAULT CHARSET=utf8; 9 insert into book'(id,name,author)values(1,'三國演義,羅貫中'),(2,'水滸傳','施耐庵');
2,添加依賴
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-jdbc</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-web</artifactId> 8 </dependency> 9 <dependency> 10 <groupId>mysql</groupId> 11 <artifactId>mysql-connector-java</artifactId> 12 <scope>runtime</scope> 13 </dependency> 14 <dependency> 15 <groupId>com.alibaba</groupId> 16 <artifactId>druid</artifactId> 17 <version>1.1.9</version> 18 </dependency>
3.數據庫配置(注意數據庫的配置)
在application.properties中配置數據庫基本連接信息:
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql:///chapter05 spring.datasource.username=root(你自己的) spring.datasource.password=123(你自己的密碼,沒有不用填)
4.創建實體類,
創建Book實體類,代碼如下:
public class Book{ private Integer id; private String name; private String author; //省略 getter/setter }
5.創建數據庫訪問層
1 @Repository 2 public class BookDao { 3 @Autowired 4 JdbcTemplate jdbcTemplate; 5 public int addBook(Book book) { 6 return jdbcTemplate.update("INSERT INTO book(name,author) VALUES (?,?)", 7 book.getName(), book.getAuthor()); 8 } 9 public int updateBook(Book book) { 10 return jdbcTemplate.update("UPDATE book SET name=?,author=? WHERE id=?", 11 book.getName(), book.getAuthor(), book.getId()); 12 } 13 public int deleteBookById(Integer id) { 14 return jdbcTemplate.update("DELETE FROM book WHERE id=?", id); 15 } 16 public Book getBookById(Integer id) { 17 return jdbcTemplate.queryForObject("select * from book where id=?", 18 new BeanPropertyRowMapper<>(Book.class), id); 19 } 20 public List<Book> getAllBooks() { 21 return jdbcTemplate.query("select * from book", 22 new BeanPropertyRowMapper<>(Book.class)); 23 } 24 }
代碼解釋:
創建BookDao,注入Jdbc Template。由於已經添加了spring-jdbc相關的依賴,JdbcTemplate會被自動注冊到Spring容器中,因此這里可以直接注入Jdbc Template使用。在JdbcTemplate中,增刪改三種類型的操作主要使用update和batchUpdate方法來完成。query和queryForObject方法主要用來完成查詢功能。另外,還有execute方法可以用來執行任意的SQL、cal方法用來調用存儲過程等。·在執行查詢操作時,需要有一個/RowMapper f查詢出來的列和實體類中的屬性一一對應起)來。如果列名和屬性名都是相同的,那么可以直接使用BeanPropertyRowMapper;如果列名和屬性名不同就需要開發者自己實見RowMapper 接口,將列和實體類屬性一一對應起來。
6.創建Service和Controller
創建BookService和BookController,代碼如下:
BookService
1 @Service 2 public class BookService { 3 @Autowired 4 BookDao bookDao; 5 public int addBook(Book book) { 6 return bookDao.addBook(book); 7 } 8 public int updateBook(Book book) { 9 return bookDao.updateBook(book); 10 } 11 public int deleteBookById(Integer id) { 12 return bookDao.deleteBookById(id); 13 } 14 public Book getBookById(Integer id) { 15 return bookDao.getBookById(id); 16 } 17 public List<Book> getAllBooks() { 18 return bookDao.getAllBooks(); 19 } 20 }
BookController
1 package org.sang; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.web.bind.annotation.GetMapping; 5 import org.springframework.web.bind.annotation.RestController; 6 7 import java.util.List; 8 9 /** 10 * Created by 半仙 11 */ 12 @RestController 13 public class BookController { 14 @Autowired 15 BookService bookService; 16 @GetMapping("/bookOps") 17 public void bookOps() { 18 Book b1 = new Book(); 19 b1.setId(99); 20 b1.setName("西廂記"); 21 b1.setAuthor("王實甫"); 22 int i = bookService.addBook(b1); 23 System.out.println("addBook>>>" + i); 24 Book b2 = new Book(); 25 b2.setId(1); 26 b2.setName("朝花夕拾"); 27 b2.setAuthor("魯迅"); 28 int updateBook = bookService.updateBook(b2); 29 System.out.println("updateBook>>>"+updateBook); 30 Book b3 = bookService.getBookById(1); 31 System.out.println("getBookById>>>"+b3); 32 int delete = bookService.deleteBookById(2); 33 System.out.println("deleteBookById>>>"+delete); 34 List<Book> allBooks = bookService.getAllBooks(); 35 System.out.println("getAllBooks>>>"+allBooks); 36 } 37 }
最后,在瀏覽器中訪問http://localhost:8080/bookOps地址,控制台打印日志如圖5-1所示
addBook>〉>1
updateBoolk>〉>1
getBookById>>>Book{id=1,name='朝花夕拾’,author='魯迅’}
deleteBoolkById>>>1
getA11Books>>>[Book{id=1,name='朝花夕拾’,author='魯迅’},Book{id=4,name='西廂記’,author='王實甫’}]
這里只是介紹SpringBoot集成JDBCTemplate的Demo,重點在於上面說的這句話“·在執行查詢操作時,需要有一個/RowMapper f查詢出來的列和實體類中的屬性一一對應起)來。如果列名和屬性名都是相同的,那么可以直接使用BeanPropertyRowMapper;如果列名和屬性名不同就需要開發者自己實見RowMapper 接口,將列和實體類屬性一一對應起來。”
那么在上面我們看到,JdbcTemplate已經封裝了對應增刪改的方法,我們只需直接調用即可,關鍵在於query查詢時候RowMapper的這點上,正如上面所說,如果列名和屬性名都是相同的,我們可以直接使用BeanPropertyRowMapper,如果列名和屬性名不同就需要開發者自己實見RowMapper 接口,將列和實體類屬性一一對應起來。”
那么到底這個RowMapper是什么 ,官方的源碼如下
package org.springframework.jdbc.core; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.lang.Nullable; @FunctionalInterface public interface RowMapper<T> { @Nullable T mapRow(ResultSet var1, int var2) throws SQLException; }
不難看出這是一個方法接口 ,里面的抽象法中的ResultSet又是什么呢 ,表示數據庫結果集的數據表,通常由執行查詢數據庫的語句生成,這里我只貼出官方的版本,想看的直接點擊ctrl+鼠標左鍵點進去看,我用的是IDEA
1 ** 2 * A table of data representing a database result set, which 3 * is usually generated by executing a statement that queries the database. 4 * 5 * <P>A <code>ResultSet</code> object maintains a cursor pointing 6 * to its current row of data. Initially the cursor is positioned 7 * before the first row. The <code>next</code> method moves the 8 * cursor to the next row, and because it returns <code>false</code> 9 * when there are no more rows in the <code>ResultSet</code> object, 10 * it can be used in a <code>while</code> loop to iterate through 11 * the result set. 12 * <P> 13 * A default <code>ResultSet</code> object is not updatable and 14 * has a cursor that moves forward only. Thus, you can 15 * iterate through it only once and only from the first row to the 16 * last row. It is possible to 17 * produce <code>ResultSet</code> objects that are scrollable and/or 18 * updatable. The following code fragment, in which <code>con</code> 19 * is a valid <code>Connection</code> object, illustrates how to make 20 * a result set that is scrollable and insensitive to updates by others, and 21 * that is updatable. See <code>ResultSet</code> fields for other 22 * options. 23 * <PRE> 24 * 25 * Statement stmt = con.createStatement( 26 * ResultSet.TYPE_SCROLL_INSENSITIVE, 27 * ResultSet.CONCUR_UPDATABLE); 28 * ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE2"); 29 * // rs will be scrollable, will not show changes made by others, 30 * // and will be updatable 31 * 32 * </PRE> 33 * The <code>ResultSet</code> interface provides 34 * <i>getter</i> methods (<code>getBoolean</code>, <code>getLong</code>, and so on) 35 * for retrieving column values from the current row. 36 * Values can be retrieved using either the index number of the 37 * column or the name of the column. In general, using the 38 * column index will be more efficient. Columns are numbered from 1. 39 * For maximum portability, result set columns within each row should be 40 * read in left-to-right order, and each column should be read only once. 41 * 42 * <P>For the getter methods, a JDBC driver attempts 43 * to convert the underlying data to the Java type specified in the 44 * getter method and returns a suitable Java value. The JDBC specification 45 * has a table showing the allowable mappings from SQL types to Java types 46 * that can be used by the <code>ResultSet</code> getter methods. 47 * 48 * <P>Column names used as input to getter methods are case 49 * insensitive. When a getter method is called with 50 * a column name and several columns have the same name, 51 * the value of the first matching column will be returned. 52 * The column name option is 53 * designed to be used when column names are used in the SQL 54 * query that generated the result set. 55 * For columns that are NOT explicitly named in the query, it 56 * is best to use column numbers. If column names are used, the 57 * programmer should take care to guarantee that they uniquely refer to 58 * the intended columns, which can be assured with the SQL <i>AS</i> clause. 59 * <P> 60 * A set of updater methods were added to this interface 61 * in the JDBC 2.0 API (Java™ 2 SDK, 62 * Standard Edition, version 1.2). The comments regarding parameters 63 * to the getter methods also apply to parameters to the 64 * updater methods. 65 *<P> 66 * The updater methods may be used in two ways: 67 * <ol> 68 * <LI>to update a column value in the current row. In a scrollable 69 * <code>ResultSet</code> object, the cursor can be moved backwards 70 * and forwards, to an absolute position, or to a position 71 * relative to the current row. 72 * The following code fragment updates the <code>NAME</code> column 73 * in the fifth row of the <code>ResultSet</code> object 74 * <code>rs</code> and then uses the method <code>updateRow</code> 75 * to update the data source table from which <code>rs</code> was derived. 76 * <PRE> 77 * 78 * rs.absolute(5); // moves the cursor to the fifth row of rs 79 * rs.updateString("NAME", "AINSWORTH"); // updates the 80 * // <code>NAME</code> column of row 5 to be <code>AINSWORTH</code> 81 * rs.updateRow(); // updates the row in the data source 82 * 83 * </PRE> 84 * <LI>to insert column values into the insert row. An updatable 85 * <code>ResultSet</code> object has a special row associated with 86 * it that serves as a staging area for building a row to be inserted. 87 * The following code fragment moves the cursor to the insert row, builds 88 * a three-column row, and inserts it into <code>rs</code> and into 89 * the data source table using the method <code>insertRow</code>. 90 * <PRE> 91 * 92 * rs.moveToInsertRow(); // moves cursor to the insert row 93 * rs.updateString(1, "AINSWORTH"); // updates the 94 * // first column of the insert row to be <code>AINSWORTH</code> 95 * rs.updateInt(2,35); // updates the second column to be <code>35</code> 96 * rs.updateBoolean(3, true); // updates the third column to <code>true</code> 97 * rs.insertRow(); 98 * rs.moveToCurrentRow(); 99 * 100 * </PRE> 101 * </ol> 102 * <P>A <code>ResultSet</code> object is automatically closed when the 103 * <code>Statement</code> object that 104 * generated it is closed, re-executed, or used 105 * to retrieve the next result from a sequence of multiple results. 106 * 107 * <P>The number, types and properties of a <code>ResultSet</code> 108 * object's columns are provided by the <code>ResultSetMetaData</code> 109 * object returned by the <code>ResultSet.getMetaData</code> method. 110 * 111 * @see Statement#executeQuery 112 * @see Statement#getResultSet 113 * @see ResultSetMetaData 114 */
如何實現自己的RowMapper?參照如下代碼
public class BeanRowMapper implements RowMapper<Article> { //這里的類名只是為了掩飾 根據自己的情況修改 @Override public Article mapRow(ResultSet resultSet, int i) throws SQLException { Bean bean= new Bean();
//設置不一致的列名與實體字段對應 bean.setId(resultSet.getInt("id")); bean.setTitle(resultSet.getString("title")); bean.setDescription(resultSet.getString("description")); return Bean; } }
然后修改對應的Dao代碼
/** * 查詢所有數據 */ public List<Bean> findAll() { String sql = "SELECT id, title, description FROM Bean"; return jdbcTemplate.query(sql, new BeanRowMapper()); } /** * 查詢單條數據 */ public BeanfindById(Integer id) { String sql = "SELECT id, title, description FROM BeanWHERE id = ?"; return jdbcTemplate.queryForObject(sql, new BeanRowMapper()); }
然后在測試對應的接口
才疏學淺,有錯誤請指出
