Spring JdbcTemplate詳解,這都看不懂就安心去當個鹹魚吧!
JDBC 基礎
Java程式使用JDBC介面訪問關聯式資料庫的時候,需要以下幾步:
-
建立全域性DataSource例項,表示資料庫連線池;
-
在需要讀寫資料庫的方法內部,按如下步驟訪問資料庫:
-
從全域性DataSource例項獲取Connection例項;
-
透過Connection例項建立PreparedStatement例項;
-
執行SQL語句,如果是查詢,則透過ResultSet讀取結果集,如果是修改,則獲得int結果。
正確編寫JDBC程式碼的關鍵是使用
try ... finally
釋放資源,涉及到事務的程式碼需要正確提交或回滾事務。
在Spring使用JDBC,首先我們透過IoC容器建立並管理一個
DataSource例項
,然後,Spring提供了一個
JdbcTemplate
,可以方便地讓我們操作JDBC,因此,通常情況下,我們會例項化一個
JdbcTemplate
。顧名思義,這個類主要使用了
Template模式
。
編寫示例程式碼或者測試程式碼時,我們強烈推薦使用
HSQLDB
這個資料庫,它是一個用Java編寫的關聯式資料庫,可以以記憶體模式或者檔案模式執行,本身只有一個jar包,非常適合演示程式碼或者測試程式碼,本文使用MySql
我們以實際工程為例,先建立Maven工程
spring-data-jdbc
,然後引入以下依賴:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.5.0</version> </dependency> </dependencies>
在
AppConfig
中,我們需要建立以下幾個必須的Bean:
@Configuration@ComponentScan@PropertySource("jdbc.properties")public class AppConfig { @Value("${jdbc.url}") String jdbcUrl; @Value("${jdbc.username}") String jdbcUsername; @Value("${jdbc.password}") String jdbcPassword; @Bean DataSource createDataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); config.addDataSourceProperty("autoCommit", "true"); config.addDataSourceProperty("connectionTimeout", "5"); config.addDataSourceProperty("idleTimeout", "60"); return new HikariDataSource(config); } @Bean JdbcTemplate createJdbcTemplate(@Autowired DataSource dataSource) { return new JdbcTemplate(dataSource); } }
在上述配置中:
-
透過@PropertySource(" jdbc.properties")讀取資料庫配置檔案;
-
透過@Value("${jdbc.url}")注入配置檔案的相關配置;
-
建立一個DataSource例項,它的實際型別是HikariDataSource,建立時需要用到注入的配置;
-
建立一個JdbcTemplate例項,它需要注入DataSource,這是透過方法引數完成注入的。
最後,針對HSQLDB寫一個配置檔案
jdbc.properties
:
# 資料庫檔名為testdb:jdbc.url=jdbc:hsqldb:file:testdb# Hsqldb預設的使用者名稱是sa,口令是空字串:jdbc.username=sa jdbc.password=# mysql配置# 資料庫檔名為testdb:jdbc.driverClass=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/springjdbc?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=falsejdbc.user=root jdbc.password=1234
可以透過HSQLDB自帶的工具來初始化資料庫表,這裡我們寫一個Bean,在Spring容器啟動時自動建立一個users表:
@Componentpublic class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; @PostConstruct public void init() { jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" // + "id BIGINT IDENTITY NOT NULL PRIMARY KEY, " // + "email VARCHAR(100) NOT NULL, " // + "password VARCHAR(100) NOT NULL, " // + "name VARCHAR(100) NOT NULL, " // + "UNIQUE (email))"); } }
現在,所有準備工作都已完畢。我們只需要在需要訪問資料庫的Bean中,注入
JdbcTemplate
即可:
@Componentpublic class UserService { @Autowired JdbcTemplate jdbcTemplate; ... }
JdbcTemplate 用法
Spring提供的
JdbcTemplate
採用
Template模式
,提供了一系列以回撥為特點的工具方法,目的是避免繁瑣的
try...catch
語句。
我們以具體的示例來說明
JdbcTemplate
的用法。
首先我們看
T execute(ConnectionCallback<T> action)方法
,它提供了Jdbc的
Connection
供我們使用:
public User getUserById(long id) { // 注意傳入的是ConnectionCallback: return jdbcTemplate.execute((Connection conn) -> { // 可以直接使用conn例項,不要釋放它,回撥結束後JdbcTemplate自動釋放: // 在內部手動建立的PreparedStatement、ResultSet必須用try(...)釋放: try (var ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) { ps.setObject(1, id); try (var rs = ps.executeQuery()) { if (rs.next()) { return new User( // new User object: rs.getLong("id"), // id rs.getString("email"), // email rs.getString("password"), // password rs.getString("name")); // name } throw new RuntimeException("user not found by id."); } } });}` 也就是說,上述回撥方法允許獲取Connection,然後做任何基於Connection的操作。 我們再看T execute(String sql, PreparedStatementCallback<'T'> action)的用法:public User getUserByName(String name) { // 需要傳入SQL語句,以及PreparedStatementCallback: return jdbcTemplate.execute("SELECT * FROM users WHERE name = ?", (PreparedStatement ps) -> { // PreparedStatement例項已經由JdbcTemplate建立,並在回撥後自動釋放: ps.setObject(1, name); try (var rs = ps.executeQuery()) { if (rs.next()) { return new User( // new User object: rs.getLong("id"), // id rs.getString("email"), // email rs.getString("password"), // password rs.getString("name")); // name } throw new RuntimeException("user not found by id."); } }); }
最後,我們看
T queryForObject(String sql, Object[] args, RowMapper<'T'> rowMapper)方法
:
public User getUserByEmail(String email) { // 傳入SQL,引數和RowMapper例項: return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email = ?", new Object[] { email }, (ResultSet rs, int rowNum) -> { // 將ResultSet的當前行對映為一個JavaBean: return new User( // new User object: rs.getLong("id"), // id rs.getString("email"), // email rs.getString("password"), // password rs.getString("name")); // name }); }
RowMapper定義:
@FunctionalInterfacepublic interface RowMapper<T> { @Nullable T mapRow(ResultSet var1, int var2) throws SQLException; }
方法
getUserByEmail中(ResultSet rs, int rowNum) -> {}
語句實現一個RowMapper繼承類的物件
在
queryForObject()方法
中,傳入SQL以及SQL引數後,
JdbcTemplate
會自動建立
PreparedStatement
,自動執行查詢並返回
ResultSet
,我們提供的
RowMapper
需要做的事情就是把
ResultSet
的當前行對映成一個
JavaBean
並返回。整個過程中,使用
Connection
、
PreparedStatement
和
ResultSet
都不需要我們手動管理。
RowMapper
不一定返回
JavaBean
,實際上它可以返回任何Java物件。例如,使用
SELECT COUNT(*)
查詢時,可以返回Long:
public long getUsers() { return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", null, (ResultSet rs, int rowNum) -> { // SELECT COUNT(*)查詢只有一列,取第一列資料: return rs.getLong(1); }); }
如果我們期望返回多行記錄,而不是一行,可以用query()方法:
public List<User> getUsers(int pageIndex) { int limit = 100; int offset = limit * (pageIndex - 1); return jdbcTemplate.query("SELECT * FROM users LIMIT ? OFFSET ?", new Object[] { limit, offset }, new BeanPropertyRowMapper<>(User.class)); }
上述
query()方法
傳入的引數仍然是SQL、SQL引數以及RowMapper例項。這裡我們直接使用Spring提供的
BeanPropertyRowMapper
。如果資料庫表的結構恰好和JavaBean的屬性名稱一致,那麼
BeanPropertyRowMapper
就可以直接把一行記錄按列名轉換為JavaBean。
如果我們執行的不是查詢,而是插入、更新和刪除操作,那麼需要使用update()方法:
public void updateUser(User user) { // 傳入SQL,SQL引數,返回更新的行數: if (1 != jdbcTemplate.update("UPDATE user SET name = ? WHERE id=?", user.getName(), user.getId())) { throw new RuntimeException("User not found by id"); } }
只有一種INSERT操作比較特殊,那就是如果某一列是自增列(例如自增主鍵),通常,我們需要獲取插入後的自增值。
JdbcTemplate
提供了一個
KeyHolder
來簡化這一操作:
public User register(String email, String password, String name) { // 建立一個KeyHolder: KeyHolder holder = new GeneratedKeyHolder(); if (1 != jdbcTemplate.update( // 引數1:PreparedStatementCreator (conn) -> { // 建立PreparedStatement時,必須指定RETURN_GENERATED_KEYS: var ps = conn.prepareStatement("INSERT INTO users(email,password,name) VALUES(?,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setObject(1, email); ps.setObject(2, password); ps.setObject(3, name); return ps; }, // 引數2:KeyHolder holder) ) { throw new RuntimeException("Insert failed."); } // 從KeyHolder中獲取返回的自增值: return new User(holder.getKey().longValue(), email, password, name); }
JdbcTemplate
還有許多過載方法,這裡我們不一一介紹。需要強調的是,
JdbcTemplate
只是對JDBC操作的一個簡單封裝,它的目的是儘量減少手動編寫
try(resource) {...}
的程式碼,對於查詢,主要透過
RowMapper
實現了JDBC結果集到Java物件的轉換。
我們總結一下
JdbcTemplate
的用法,那就是:
-
針對簡單查詢,優選query()和queryForObject(),因為只需提供SQL語句、引數和RowMapper;
-
針對更新操作,優選update(),因為只需提供SQL語句和引數;
-
任何複雜的操作,最終也可以透過execute(ConnectionCallback)實現,因為拿到Connection就可以做任何JDBC操作。
實際上我們使用最多的仍然是各種查詢。如果在設計表結構的時候,能夠和JavaBean的屬性一一對應,那麼直接使用
BeanPropertyRowMapper
就很方便。如果表結構和JavaBean不一致怎麼辦?那就需要稍微改寫一下查詢,使結果集的結構和JavaBean保持一致。
例如,表的列名是
office_address
,而JavaBean屬性是
workAddress
,就需要指定別名,改寫查詢如下:
SELECT id, email, office_address AS workAddress, name FROM users WHERE email = ?
RowMapper 的基本使用
使用過原生JDBC的朋友應該知道,從資料庫查詢出來的記錄全都被儲存在
ResultSet
結果集中,我們需要將結果集中的資料一條條地獲取並設定到具體的實體類上,如此,該實體類才能在接下來的程式中使用。然而問題是,每次都要這麼操作實在是太麻煩了,Spring就不應該提供什麼功能來替我們做這些事情嗎?答案當然是有的,那就是本文的主角——
RowMapper
。
Spring JDBC
中目前有兩個主要的
RowMapper
實現,使用它們應該能解決大部分的場景了。
SingleColumnRowMapper
透過名字我們就能大概瞭解,在查詢返回單列資料的時候,就該使用這個RowMapper,下面我們來看看具體的程式碼:
@Override public String getStudentNameById(String id) { String sql = "select name from test_student where id = ?"; return this.jdbcTemplate.queryForObject(sql, new Object[]{id}, new SingleColumnRowMapper<>(String.class)); } @Override public List<String> getStudentNamesByGrade(Integer grade) { String sql = "select name from test_student where grade = ?"; return this.jdbcTemplate.query(sql, new Object[]{grade}, new SingleColumnRowMapper<>(String.class)); }
測試:
@Test public void getStudentNameById(){ String name = studentService.getStudentNameById("3"); assertEquals("李清照", name); } @Test public void getStudentNamesByGrade(){ List<String> names = studentService.getStudentNamesByGrade(1); assertTrue(2 == names.size()); }
BeanPropertyRowMapper
當查詢資料庫返回的是多列資料,且你需要將這些多列資料對映到某個具體的實體類上,那麼就該使用這個RowMapper,下面是具體的使用程式碼:
@Override public Student getStudentByName2(String name) { String sql = "select name, gender from test_student where name = ?"; return this.jdbcTemplate.queryForObject(sql, new Object[]{name}, new BeanPropertyRowMapper<>(Student.class)); } @Override public List<Student> getStudentsByName2(String name) { String sql = "select name, gender from test_student where name = ?"; return this.jdbcTemplate.query(sql, new Object[]{name}, new BeanPropertyRowMapper<>(Student.class)); }
這種使用方式有一個前提,那就是資料庫SQL查出來的資料其列名與實體類中的屬性名是一致的,當然個數和順序可以不一致。比如資料庫SQL查出來的姓名列叫name,那麼對應的實體類中的姓名也必須叫name,而不能叫studentName或者其它。
定義自己的RowMapper
當然,如果你SQL查詢出來的資料列名就是和實體類的屬性名不一樣,或者想按照自己的規則來裝配實體類,那麼就可以定義並使用自己的Row Mapper。
public class StudentRowMapper implements RowMapper<Student> { @Override public Student mapRow(ResultSet rs, int i) throws SQLException { Student student = new Student(); student.setName(rs.getString("name")); student.setGender(rs.getString("gender")); student.setEmail(rs.getString("email")); return student; } } @Override public Student getStudentByName3(String name) { String sql = "select name, gender, email from test_student where name = ?"; return this.jdbcTemplate.queryForObject(sql, new Object[]{name}, new StudentRowMapper()); } @Override public List<Student> getStudentsByName3(String name) { String sql = "select name, gender, email from test_student where name = ?"; return this.jdbcTemplate.query(sql, new Object[]{name}, new StudentRowMapper()); }
小結
-
Spring提供了
JdbcTemplate
來簡化JDBC操作; -
使用
JdbcTemplate
時,根據需要優先選擇高階方法; -
任何JDBC操作都可以使用保底的
execute(ConnectionCallback)方法
。
喜歡這篇文章的朋友們可以關注個人簡介中的公眾號
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69964492/viewspace-2768938/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring JdbcTemplate之使用詳解SpringJDBC
- 鹹魚翻身隊——團隊展示
- Laravel的這10個用法,你都沒用過吧!!Laravel
- Spring框架|整合JdbcTemplateSpring框架JDBC
- 鹹魚遊戲:女性遊戲出海正當時 如何獲取流量、避免文化碰撞遊戲
- 《盜賊之海》的「鹹魚」翻身記
- 又要鹹魚翻身的HTC,國產處理器+2GB運存,網友:還是條鹹魚
- Spring 的 JdbcTemplate 和 JdbcDaoSupportSpringJDBC
- #Spring JdbcTemplate入門@FDDLCSpringJDBC
- 釣魚釣魚去
- 優秀畢業生寄語:鹹魚炒白菜
- Spring 系列之jdbcTemplate的使用SpringJDBC
- 當Spring Cloud遇上Kubernetes,天色都變了SpringCloud
- 自從裝了這個軟體後,就放心大膽摸魚了!
- Java NIO全面詳解(看這篇就夠了)Java
- SPA路由機制詳解(看不懂不要錢~~)路由
- 就這?Spring 事務失效場景及解決方案Spring
- spring下 -spring整體架構,JdbcTemplate筆記Spring架構JDBC筆記
- Spring04——Spring操作JdbcTemplate進行JDBC操作SpringJDBC
- spring 掌握這些就夠了Spring
- 做人如果沒夢想,和鹹魚有什麼區別?
- 鹹魚翻身——程式設計自學指南(一)正向路線程式設計
- 負載均衡最全詳解(看這篇就夠了)負載
- Spring詳解Spring
- 序列化: 一個老傢伙的鹹魚翻身(Java物件、XML、JSON、反序列化)Java物件XMLJSON
- kafka3.x原理詳解看這篇就夠了Kafka
- 設計模式 - 命令模式詳解及其在JdbcTemplate中的應用設計模式JDBC
- 有網友說:2020年還不懂Spring就放棄Java吧?SpringJava
- #純註解小專案:JdbcTemplate、SpEL@Transactional@PropertySource@EnableTransactionManagement#spring-tx@FDDLCJDBCSpring
- 強烈推薦這4個網站,每一個都超級實用,趕快收藏起來吧!網站
- Spring框架詳解Spring框架
- Spring AOP 詳解Spring
- Spring Bean 詳解SpringBean
- Spring Security詳解Spring
- 不學設計模式,原始碼你都看不懂!設計模式原始碼
- 這個雙11,E起送檢吧
- Spring入門這一篇就夠了Spring
- Spring容器——BeanFactory詳解SpringBean