Spring Boot中如何使用JDBC讀取和寫入資料,JDBC和JPA的對比,JdbcTemplate和SimpleJdbcInsert的用法對比
文章目錄
JDBC和JPA的對比
-
JDBC(Java Database Connectivity)提供一種介面,它是由各種資料庫廠商提供類和介面組成的資料庫驅動,為多種資料庫提供統一訪問。我們使用資料庫時只需要呼叫JDBC介面就行了。
JDBC的用途:與資料庫建立連線、傳送 運算元據庫的語句並處理結果。
JDBC示意圖
-
JPA(Java Peisitence API)是Java持久層API。它是對java應用程式訪問ORM(物件關係對映)框架的規範。為了我們能用相同的方法使用各種ORM框架。
JPA用途:簡化現有Java EE和Java SE應用開發工作;整合ORM技術。
使用JPA只需要建立實體(這和建立一個POJO(Plain Ordinary Java Object)簡單的Java物件一樣簡單),用@entity進行註解。在Spring Data JPA中,定義一個簡單的介面,用於把物件持久化到資料庫的倉庫。
常見ORM框架:Hibernate。由於MyBatis需要手寫SQL,所以不完全屬於ORM框架,而Hibernate則完全不需要手寫SQL。
JPA示意圖
不同點:
-
使用的sql語言不同:
JDBC使用的是基於關係型資料庫的標準SQL語言;
JPA通過物件導向而非面向資料庫的查詢語言查詢資料,避免程式的SQL語句緊密耦合。
-
操作的物件不同:
JDBC操作的是資料,將資料通過SQL語句直接傳送到資料庫中執行:
JPA操作的是持久化物件,由底層持久化物件的資料更新到資料庫中。
-
資料狀態不同:
JDBC操作的資料是“瞬時”的,變數的值無法與資料庫中的值保持一致;
JPA操作的資料時可持久的,即持久化物件的資料屬性的值是可以跟資料庫中的值保持一致的。
Spring Boot中使用JDBC讀取和寫入資料
Spring對JDBC的支援主要在於JdbcTemplate
類
JdbcTemplate類中主要有如下方法
-
batchUpdate(...)//批量更新
-
execute(...)//執行SQL語句
-
query(...)//查詢並返回相應值
-
queryForList(...)//查詢並返回一個List
-
queryForObject(...)//查詢並返回一個Object
-
queryForMap(...)//查詢並返回一個Map
-
queryForRowSet(...)//查詢並返回一個RowSet
-
update(...)//執行一條插入或更新語句
使用JdbcTemplate查詢資料庫的例子
JdbcTemplate
中的queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
方法是將查詢得到的結果對映為一個Object。
public Ingredient findById(String id) {
return jdbc.queryForObject(
"select id, name, type from Ingredient where id=?",
this::mapRowToIngredient, id);
}
//mapRowToIngredient方法,ResultSet是查詢返回的結果集
private Ingredient mapRowToIngredient(ResultSet rs, int rowNum)
throws SQLException {
return new Ingredient(
rs.getString("id"),
rs.getString("name"),
Ingredient.Type.valueOf(rs.getString("type")));
}
/**************************** 等效於 ************************************/
public Ingredient findById(String id) {
return jdbc.queryForObject(
"select id, name, type from Ingredient where id=?",
new RowMapper<Ingredient>() {
public Ingredient mapRow(ResultSet rs, int rowNum)
throws SQLException {
return new Ingredient(
rs.getString("id"),
rs.getString("name"),
Ingredient.Type.valueOf(rs.getString("type")));
};
}, id);
}
findById方法中呼叫的queryForObject
方法中需要傳入一個RowMapper
的例項
@Override
@Nullable
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
return DataAccessUtils.nullableSingleResult(results);
}
RowMapper<T>
介面中只有一個T mapRow(ResultSet rs, int rowNum) throws SQLException
抽象方法。需要一個子類來繼承RowMapper
並實現mapRow
方法。如果使用Lambda,則只需要傳入相應所需執行的程式碼。
@FunctionalInterface
public interface RowMapper<T> {
@Nullable
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
-
ResultSet
是查詢資料庫所返回的結果集。 -
mapRow
方法是將查詢所得到資料庫的一行對映為一個物件,也就是將ResultSet的第一行對映為一個Object。
對Lambda表示式不熟悉的可以移步我的另一篇博文:
Java中Lambda對比匿名內部類
使用JdbcTemplate的步驟
調整物件
一般來說,為了將物件持久化到資料庫中需要增加id
和createdTime欄位,id一般都設定為自增,由資料庫自動生成。同時需要為每個實體類增加get,set方法。如果使用了Lombok
,只需要新增@Data
註解,就會在執行時自動為物件增加上get和set方法,從而避免了手寫get和set方法時的繁瑣。
import java.util.Date;
import java.util.List;
import lombok.Data;
@Data
public class Taco {
private Long id;
private Date createdAt;
...
}
匯入依賴
首先需要Jdbc的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
然後需要配置資料庫
如下給出了H2資料庫和MySQL的配置示例
配置H2資料庫
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 引入spring-boot-devtools的目的是在執行時可以訪問H2資料庫 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
如果需要初始化資料庫,特別是H2資料庫。
可以在resourse
資料夾下新建兩個sql檔案:schema.sql
和data.sql
-
scheaml.sql
中的SQL用於初始化資料庫,比如建立表。 -
data.sql
中的SQL語句用來插入資料
預設的訪問地址如下:
http://localhost:8080/h2-console/
預設的JDBC的連線為:jdbc:h2:mem:testdb
,預設的使用者名稱為sa
,密碼為空
Spring Boot控制檯列印的日誌顯示了連線H2資料庫的JDBC URL。
H2資料庫訪問頁面
配置MySQL
mysql-connector
得根據資料庫的版本來選擇,8.0的資料庫就得選擇版本為8或者以上的mysql-connector
。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
然後在application.properties
中配置連線相關的資訊
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
定義JDBC Repository
比如有一個Ingredient的物件對應著資料庫中Ingredient的表,其屬性分別對應著資料庫中(id, name, type)這幾個欄位。我們需要定義如下方法:
- 從資料中查詢所有的Ingredient的資訊,並將其儲存到一個Ingredient的集合中
- 根據id查詢單個Ingredient
- 儲存Ingredient物件到資料庫中
首先需要定義IngredientRepository的interface
package tacos.data;
import tacos.Ingredient;
public interface IngredientRepository {
Iterable<Ingredient> findAll();
Ingredient findById(String id);
Ingredient save(Ingredient ingredient);
}
然後需要使用JdbcTemplate來具體實現這個介面
package tacos.data;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import tacos.Ingredient;
@Repository//為JdbcIngredientRepository定義了@Repository以後,Spring掃描到這個類時,就會將其初始化為Spring上下文中的一個Bean
public class JdbcIngredientRepository implements IngredientRepository {
private JdbcTemplate jdbc;
//只要我們在pom.xml中匯入JDBC的依賴,Spring Boot就會為我們自動配置一個JdbcTemplate的Bean,
//我們只需要將這個Bean注入到我們的程式碼中
@Autowired
public JdbcIngredientRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
@Override
public Iterable<Ingredient> findAll() {
return jdbc.query("select id, name, type from Ingredient",
this::mapRowToIngredient);
}
@Override
public Ingredient findById(String id) {
return jdbc.queryForObject(
"select id, name, type from Ingredient where id=?",
this::mapRowToIngredient, id);
}
@Override
public Ingredient save(Ingredient ingredient) {
jdbc.update(
"insert into Ingredient (id, name, type) values (?, ?, ?)",
ingredient.getId(),
ingredient.getName(),
ingredient.getType().toString());
return ingredient;
}
private Ingredient mapRowToIngredient(ResultSet rs, int rowNum)
throws SQLException {
return new Ingredient(
rs.getString("id"),
rs.getString("name"),
Ingredient.Type.valueOf(rs.getString("type")));
}
}
在Controller中注入和使用repository
package tacos.web;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.data.IngredientRepository;
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {
private final IngredientRepository ingredientRepo;
//IngredientRepository使用了@Repository註解,IngredientRepository的Bean就會被註冊到Spring的上下文中,因此這裡只需注入即可
@Autowired
public DesignTacoController(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
@GetMapping
public String showDesignForm(Model model) {
List<Ingredient> ingredients = new ArrayList<>();
ingredientRepo.findAll().forEach(i -> ingredients.add(i));
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
return "design";
}
private List<Ingredient> filterByType(List<Ingredient> ingredients, Type type) {
return ingredients
.stream()
.filter(x -> x.getType().equals(type))
.collect(Collectors.toList());
}
}
插入資料
到此JdbcTemplate
的基本使用就到此結束,下面將介紹SimpleJdbcInsert
。對比JdbcTemplate
,它的功能更強大些,插入資料也更加方便
插入一行資料可以直接使用JdbcTemplate中的update方法。
但是考慮到多表關聯的情況,使用JdbcTemplate就有些麻煩:
如下所示:有三張表,一個使用者可以建立多個訂單,所以user_order表中一個user可以對應多條order。
我們在建立訂單的時候,除了需要將該訂單插入order表中,還需要將order_id插入到user_order表中。通常來說id欄位都是自增的,我們只需要往order表中插入order_name以及createTime就會自動為該記錄生成一個order_id。插入成功以後,我們需要取出該記錄的order_id,與user_id一起插入到user_order表中。
對比兩段程式碼來看看在實現方法上兩者的差別
程式碼中相應的表的欄位以及相互關係如下:
Taco對應Java物件有如下屬性:
如下程式碼的作用都是分別將order中的資訊分別插入到Taco
表和Taco_Ingredients表
表
使用JdbcTemplate進行插入
package tacos.data;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import java.util.Date;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import tacos.Ingredient;
import tacos.Taco;
@Repository
public class JdbcTacoRepository implements TacoRepository {
private JdbcTemplate jdbc;
public JdbcTacoRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
@Override
public Taco save(Taco taco) {
long tacoId = saveTacoInfo(taco);
taco.setId(tacoId);
for (Ingredient ingredient : taco.getIngredients()) {
saveIngredientToTaco(ingredient, tacoId);
}
return taco;
}
private long saveTacoInfo(Taco taco) {
taco.setCreatedAt(new Date());
PreparedStatementCreator psc =
new PreparedStatementCreatorFactory(
"insert into Taco (name, createdAt) values (?, ?)",
Types.VARCHAR, Types.TIMESTAMP
).newPreparedStatementCreator(
Arrays.asList(
taco.getName(),
new Timestamp(taco.getCreatedAt().getTime())));
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbc.update(psc, keyHolder);
return keyHolder.getKey().longValue();
}
private void saveIngredientToTaco(
Ingredient ingredient, long tacoId) {
jdbc.update(
"insert into Taco_Ingredients (taco, ingredient) " +
"values (?, ?)",
tacoId, ingredient.getId());
}
}
使用SimpleJdbcInsert進行插入
package tacos.data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import tacos.Ingredient;
import tacos.Taco;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Repository
public class JdbcTacoRepository implements TacoRepository {
private SimpleJdbcInsert tacoInserter;
private SimpleJdbcInsert tacoIngredientsInserter;
@Autowired
public JdbcTacoRepository(JdbcTemplate jdbc) {
this.tacoInserter = new SimpleJdbcInsert(jdbc)
.withTableName("Taco")
.usingGeneratedKeyColumns("id");
this.tacoIngredientsInserter = new SimpleJdbcInsert(jdbc)
.withTableName("Taco_Ingredients");
}
@Override
public Taco save(Taco taco) {
long tacoId = saveTacoInfo(taco);
taco.setId(tacoId);
for (Ingredient ingredient : taco.getIngredients()) {
saveIngredientToTaco(ingredient, tacoId);
}
return taco;
}
private void saveIngredientToTaco(Ingredient ingredient, long tacoId) {
Map<String, Object> values = new HashMap<>();
values.put("taco", tacoId);
values.put("ingredient", ingredient.getId());
tacoIngredientsInserter.execute(values);
}
private long saveTacoInfo(Taco taco) {
taco.setCreatedAt(new Date());
Map<String, Object> values = new HashMap<>();
values.put("createdAt", taco.getCreatedAt());
values.put("name", taco.getName());
long tacoId = tacoInserter
.executeAndReturnKey(values)
.longValue();
return tacoId;
}
}
對比SimpleJdbcInsert和JdbcTemplate的使用
左邊是使用JdbcTemplate
。為了得到插入資料以後生成的tacoId。需要使用PreparedStatementCreator
和keyHolder
。相比之下,SimpleJdbcInsert
的程式碼則要簡潔很多。
參考
- 《Spring實戰 第5版》
- JPA 與 JDBC 的區別和基本用法
相關文章
- 【Spring Boot】使用JDBC 獲取相關的資料Spring BootJDBC
- SAP ABAP ADBC和Java JDBC的使用比較JavaJDBC
- JDBC連線openGauss6.0和PostgreSQL16.2效能對比JDBCSQL
- 說說如何在 Spring Boot 中使用 JdbcTemplate 讀寫資料Spring BootJDBC
- Spring 對JDBC的支援SpringJDBC
- Spring Boot 2和JPA入門Spring Boot
- 對比JavaScript中的Continue和BreakJavaScript
- ReferenceField、EmbeddedDocumentField和LazyReferenceField的使用和總結對比
- JDBC中的executeQuery和executeUpdateJDBC
- Spring Boot入門(五):使用JDBC訪問MySql資料庫Spring BootJDBCMySql資料庫
- Spring系列之JDBC對不同資料庫異常如何抽象的?SpringJDBC資料庫抽象
- Git和SVN的對比Git
- Python 和 Ruby 的對比Python
- 關於Spring的JDBC連線mysql(與傳統jdbc比較)SpringJDBCMySql
- Go 與 C++ 的對比和比較GoC++
- 基於JDBC寫一個和mybatis類似的對映框架—DBUtilsJDBCMyBatis框架
- 使用ShardingSphere-JDBC完成Mysql的分庫分表和讀寫分離JDBCMySql
- 素描構圖中的對比與調和
- python中__init__ 和__new__的對比Python
- 對比Javascript和TypeScriptJavaScriptTypeScript
- 對比XcodeDebugMemoryGraph和FBMemoryProfilerXCode
- vite和webpack對比ViteWeb
- WinRunner和QTP對比QT
- TCP和UDP對比TCPUDP
- HttpClientFactory 使用說明 及 對 HttpClient 的回顧和對比HTTPclient
- Sharding-JDBC 使用入門和基本配置JDBC
- PaddleFluid和TensorFlow基本使用概念對比UI
- Spring 的 JdbcTemplate 和 JdbcDaoSupportSpringJDBC
- aes和sm4對128bit資料加密的速度對比加密
- react和vue的渲染流程對比ReactVue
- Kotlin和Java的簡單對比KotlinJava
- Class和ClassLoader的getResource方法對比
- 【資料結構】棧和佇列的總結對比資料結構佇列
- Spring04——Spring操作JdbcTemplate進行JDBC操作SpringJDBC
- Spring Cloud是什麼,和Dubbo對比呢?SpringCloud
- JDBC 批量插入資料優化, 使用 addBatch 和 executeBatchJDBC優化BAT
- not in 和 not exists 比較和用法
- 使用Exp和Expdp匯出資料的效能對比與最佳化