說說如何在 Spring Boot 中使用 JdbcTemplate 讀寫資料
首先在 pom.xml 中引入依賴。
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--h2-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
示例程式打算使用 h2 快取資料庫,所以這裡也一併引用。
1 h2 快取資料庫
h2是一個開源的嵌入式(非嵌入式裝置)資料庫引擎,基於Java開發,可直接嵌入到應用程式中,與應用程式一起打包釋出,不受平臺限制。
啟動應用後,在瀏覽器位址列輸入 http://127.0.0.1:8080/h2-console,就可以開啟 h2 控制檯。
首先選擇控制檯編碼格式為中文,接著輸入 JDBC URL,然後點選“測試連線”,如果連線成功,就會提示“測試成功”。
最後點選“連線”按鈕,就會開啟資料庫控制檯客戶端,連線到 h2 資料庫:
2 初始化表結構與資料
在 src/main/resources/ 下,新建 schema.sql 檔案編寫表結構 SQL。在同一個目錄下,新建 data.sql 檔案,編寫初始化資料 SQL。這樣在應用啟動時,Spring Boot 就會執行這些指令碼。
schema.sql:
create table if not exists Book
( id varchar( 4) not null, name varchar( 25) not null, type varchar( 10) not null );
data.sql:
insert into Book
(id, name, type)
values
('1', '兩京十五日', '小說');
insert into Book
(id, name, type)
values
('2', '把自己作為方法', '歷史');
insert into Book
(id, name, type)
values
('3', '正常人', '小說');
啟動成功後,就會在 h2 資料庫控制檯客戶端中看到新建好的表與資料。
點選左側的 Book,就會在右側的 SQL 輸入框中自動生成查詢該表的 SQL 語句,然後點選 “Run”,執行它。我們就會在右下角看到初始化的表資料。
3 編碼
3.1 新建實體類
@Data
@RequiredArgsConstructor
public class Book {
private final String id;
private final String name;
private final String type;
}
這裡用了 Lombok 外掛。Lombok 是一種 Java 實用工具,可用來幫助我們消除 Java 冗長的樣板式程式碼。
加了 @Data 註解的Java 類,在編譯之後會自動為我們加上這些方法:
- 所有屬性的get和set方法;
- toString 方法;
- hashCode方法;
- equals方法。
@RequiredArgsConstructor 註解會將類中所有帶有 @NonNull 註解和以final修飾的未經初始化的欄位作為建構函式的入參。
3.2 新建 Repository 類
首先定義一個 Repository 介面,然後新建這個介面的實現類。
介面:
public interface BookRepository {
Iterable<Book> findAll();
Book findOne(String id);
Book save(Book Book);
}
實現類:
@Repository
public class JdbcBookRepository implements BookRepository {
private JdbcTemplate jdbc;
@Autowired
public JdbcBookRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
@Override
public Iterable<Book> findAll() {
return null;
}
@Override
public Book findOne(String id) {
return null;
}
@Override
public Book save(Book Book) {
return null;
}
}
@Repository和@Controller、@Service、@Component的作用差不多,目的都是把物件交給spring管理。@Repository一般用在持久層的實現類上。
@Autowired註解可通過byType的形式,來給指定的欄位或方法注入所需的外部資源。
autowire 有以下四種模式:
模式 | 說明 |
---|---|
byName | 根據屬性的名字自動裝配 |
byType | 根據屬性的型別自動裝配 |
constructor | 與 byType 類似,不同之處在於它應用於構造器引數,如果沒有找到會丟擲異常 |
autodetect | 會在 byType 和 constructor 中智慧選擇 |
這裡通過 @Autowired 標註的構造器將 JdbcTemplate 注入進來。這個構造器將 JdbcTemplate 賦值給一個例項變數,這個變數會被其他方法用來執行資料庫查詢或更新等操作。
3.3 查詢操作
假設我們需要查詢出所有的書籍,那麼就可以呼叫 JdbcTemplate 的 List<T> query(String sql, RowMapper<T> rowMapper)
方法。
@Override
public Iterable<Book> findAll() {
return jdbc.query("select id, name, type from Book",
this::mapRowToBook);
}
private Book mapRowToBook(ResultSet rs, int rowNum) throws SQLException {
return new Book(rs.getString("id"), rs.getString("name"),
rs.getString("type"));
}
這裡利用了 Java 的方法引用,來編寫 RowMapper 入參。這樣做的好處是:相對於原來的匿名內部類的寫法,方法引用的寫法更加簡潔。
3.4 update()
JdbcTemplate 的 update() 方法可以用來新增或更新資料。
@Override
public Book save(Book book) {
jdbc.update("insert into Book (id,name,type) values (?,?,?)",
book.getId(),
book.getName(),
book.getType()
);
return book;
}
update() 方法定義如下:
int update(String sql, @Nullable Object... args)
它接受一個包含佔位符的 SQL 語句以及多個入參。返回實際影響到的記錄數。
建立 Spring 單元測試類來驗證剛剛新建的 save() 方法:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class JdbcBookRepositoryTest {
@Autowired
private JdbcBookRepository jdbcBookRepository;
@Test
public void save() {
Book book = new Book("4", "比利時的哀愁", "小說");
jdbcBookRepository.save(book);
Book find=jdbcBookRepository.findOne("4");
assertEquals("比利時的哀愁",find.getName());
}
}
3.5 SimpleJdbcInsert 包裝器類
SimpleJdbcInsert 一般用於多表插入場景。SimpleJdbcInsert 有兩個方法執行資料插入操作: execute() 和 executeAndReturnKey()。 它們都接受 Map<String ,Object>
作為引數,其中的 key 對應資料表中的列名,而 value 對應要插入到列中的實際值。
我們舉一個圖書示例。一本圖書可以包含多個標籤;而一個標籤也可以隸屬於多本圖書。它們之間是多對多的關係,因此建立圖書與標籤的對映表來專門存放這些關係。具體如下圖所示:
首先在 schema.sql 中,加入這些表結構建立語句:
create table if not exists Book
( id identity, name varchar( 25) not null, type varchar( 10) not null );
create table if not exists Tag
( id identity, name varchar( 25) not null);
create table if not exists Book_Tags ( book bigint not null, tag bigint not null );
alter table Book_Tags add foreign key (book) references Book( id);
alter table Book_Tags add foreign key (tag) references Tag( id);
接著,建立這些實體類:
@Data
@RequiredArgsConstructor
public class Tag {
private final Long id;
private final String name;
}
@Data
@RequiredArgsConstructor
public class Book {
private final String id;
private final String name;
private final String type;
private List<Tag> tags = new ArrayList<>();
}
然後在 Repository 實現類的建構函式中,初始化每張表的 SimpleJdbcInsert 例項:
@Repository
public class JdbcBookRepository implements BookRepository {
private static final Logger log = LogManager.getFormatterLogger();
private final SimpleJdbcInsert bookInserter;
private final SimpleJdbcInsert tagInserter;
private final SimpleJdbcInsert bookTagsInserter;
private final ObjectMapper objectMapper;
private JdbcTemplate jdbc;
@Autowired
public JdbcBookRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
this.bookInserter = new SimpleJdbcInsert(jdbc)
.withTableName("Book")
.usingGeneratedKeyColumns("id");
this.tagInserter = new SimpleJdbcInsert(jdbc)
.withTableName("Tag")
.usingGeneratedKeyColumns("id");
this.bookTagsInserter = new SimpleJdbcInsert(jdbc)
.withTableName("Book_Tags");
this.objectMapper = new ObjectMapper();
}
...
}
SimpleJdbcInsert 的 withTableName() 方法用於指定表名;而 usingGeneratedKeyColumns() 方法用於指定主鍵。
具體儲存操作程式碼為:
public Book saveIncludeTags(Book book) {
//儲存圖書
Map<String, Object> values = objectMapper.convertValue(book, Map.class);
long bookId = bookInserter.executeAndReturnKey(values).longValue();
//儲存標籤
List<Long> tagIds = new ArrayList<>();
for (Tag tag : book.getTags()) {
values = objectMapper.convertValue(tag, Map.class);
long tagId = tagInserter.executeAndReturnKey(values).longValue();
tagIds.add(tagId);
}
//關聯圖書與標籤
for (Long tagId : tagIds) {
values.clear();
values.put("book", bookId);
values.put("tag", tagId);
log.info("values -> %s", values);
bookTagsInserter.execute(values);
}
return book;
}
- SimpleJdbcInsert 的 executeAndReturnKey() 與 execute() 方法都支援
Map<String, ?>
形式的入參。它們之間的區別是 executeAndReturnKey() 會返回 Number 形式的主鍵值。 - 可以利用 Jackson 的
ObjectMapper.convertValue(Object fromValue, Class<T> toValueType)
方法把一個 POJO 轉換為相應的 Map 物件值。 - Number 型別可以根據場景對其進行轉換。
這段程式碼首先先儲存圖書,得到圖書主鍵;然後儲存標籤,得到標籤主鍵;最後把前面得到的圖書主鍵與標籤主鍵儲存到它們之間的關係表中。
相關文章
- Spring Boot中如何使用JDBC讀取和寫入資料,JDBC和JPA的對比,JdbcTemplate和SimpleJdbcInsert的用法對比Spring BootJDBC
- Spring boot 五 jdbcTemplateSpring BootJDBC
- 說說在 Python 中,如何讀取檔案中的資料Python
- 說說資料分析中的資料建模
- Spring Boot 2.7.0 更新說明Spring Boot
- Spring Boot 2.x基礎教程:使用JdbcTemplate訪問MySQL資料庫Spring BootJDBCMySql資料庫
- 說說如何在Vue專案中應用TypeScript?VueTypeScript
- Spring Boot 2.x基礎教程:使用JdbcTemplate訪Spring BootJDBC
- Spring Boot入門系列(十四)使用JdbcTemplate運算元據庫,配置多資料來源!Spring BootJDBC
- 說說敏捷大資料敏捷大資料
- 如何在Oracle資料庫中查詢表和欄位說明Oracle資料庫
- SpringBoot魔法堂:說說帶智慧提示的spring-boot-starterSpring Boot
- 說說 Spring AOP 中 @Aspect 的高階用法Spring
- 資料庫欄位簡寫說明資料庫
- 簡說Spring中的資源載入Spring
- Spring Boot + Mybatis 多資料來源配置實現讀寫分離Spring BootMyBatis
- 說說在 Python 中,如何寫入檔案Python
- 說說資料庫事務資料庫
- 請你說說SpringSpring
- 說說如何在 Vue.js 中實現標籤頁元件Vue.js元件
- 備忘錄一:Spring Boot HikariCP 配置說明Spring Boot
- spring boot 讀寫引數到sessionSpring BootSession
- 說說如何使用 Spring Security 保護 web 請求SpringWeb
- spring中的IOC說明Spring
- Spring Boot乾貨系列:(八)資料儲存篇-SQL關係型資料庫之JdbcTemplate的使用 | 掘金技術徵文Spring BootSQL資料庫JDBC
- Spring Boot MyBatis 動態資料來源切換、多資料來源,讀寫分離Spring BootMyBatis
- Spring Boot(十一):Spring Boot 中 MongoDB 的使用Spring BootMongoDB
- Spring Boot(三):Spring Boot 中 Redis 的使用Spring BootRedis
- 簡單說說U-boot的修改(轉)boot
- Spring Boot中使用PostgreSQL資料庫Spring BootSQL資料庫
- 如何在spring boot 使用 gitlab的ApiSpring BootGitlabAPI
- Spring JdbcTemplate之使用詳解SpringJDBC
- Spring 系列之jdbcTemplate的使用SpringJDBC
- spring boot 版本由 2 升級到 3 ,需要修改程式碼嗎? 也就是說spring boot 3 是否相容 spring boot 2 所寫的程式碼Spring Boot
- 說說在 Spring 中,如何基於 XML 來配置事務SpringXML
- spring boot中zookeeper使用Spring Boot
- spring boot中redis使用Spring BootRedis
- Spring Boot中Dockerfile使用Spring BootDocker