知識庫 / Spring / Spring Boot RSS 訂閱

使用 JDBI 與 Spring Boot

Persistence,Spring Boot
HongKong
12
01:11 PM · Dec 06 ,2025

1. 引言

在之前的教程中,我們介紹了 JDBI 的基本知識,一個開源庫,用於關係數據庫訪問,它消除了大量與直接 JDBC 使用相關的樣板代碼。

現在,我們將學習如何在 Spring Boot 應用程序中使用 JDBI。 我們還將涵蓋該庫的一些方面,使其在某些情況下成為 Spring Data JPA 的良好替代方案。

2. 項目設置

首先,讓我們將適當的 JDBI 依賴項添加到我們的項目中。現在我們將使用 JDBI 的 Spring 集成插件,它包含了所有必需的核心依賴項。 我們還將引入 SqlObject 插件,該插件為基礎 JDBI 添加了一些額外的功能,我們將在此插件中使用這些功能:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>3.0.5</version>
</dependency>
<dependency>
    <groupId>org.jdbi</groupId>
    <artifactId>jdbi3-spring5</artifactId>
    <version>3.38.0</version>
</dependency>
<dependency>
    <groupId>org.jdbi</groupId>
    <artifactId>jdbi3-sqlobject</artifactId>
    <version>3.38.0</version> 
</dependency>

最新的這些組件可以從 Maven Central 找到:

我們還需要一個合適的 JDBC 驅動程序來訪問我們的數據庫。 在本文中,我們將使用 H2,因此必須將其驅動程序添加到我們的依賴項列表中:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
    <scope>runtime</scope>
</dependency>

3. JDBI 實例化與配置

我們之前文章已經提到,Jdbi 實例是訪問 JDBI API 的入口點。 考慮到我們身處 Spring 生態中,將該類的實例作為 Bean 暴露是一個不錯的選擇。

我們將利用 Spring Boot 的自動配置功能,初始化 DataSource 並將其傳遞到一個帶有 @Bean 註解的方法中,該方法將創建我們的全局 Jdbi 實例。

我們還將傳遞任何發現的插件和 RowMapper 實例到該方法中,以便在應用程序啓動時進行註冊。

@Configuration
public class JdbiConfiguration {
    @Bean
    public Jdbi jdbi(DataSource ds, List<JdbiPlugin> jdbiPlugins, List<RowMapper<?>> rowMappers) {        
        TransactionAwareDataSourceProxy proxy = new TransactionAwareDataSourceProxy(ds);        
        Jdbi jdbi = Jdbi.create(proxy);

        // Register all available plugins
        log.info("[I27] Installing plugins... ({} found)", jdbiPlugins.size());
        jdbiPlugins.forEach(jdbi::installPlugin);

        // Register all available rowMappers
        log.info("[I31] Installing rowMappers... ({} found)", rowMappers.size());
        rowMappers.forEach(jdbi::registerRowMapper);
        return jdbi;
    }
}

在這裏,我們使用了可用的 DataSource 並將其包裹在 TransactionAwareDataSourceProxy 中。 我們需要這個包裝器以便將 Spring 託管的事務與 JDBI 集成,正如稍後會看到的

註冊插件和 RowMapper 實例非常簡單。我們只需要為每個可用的 JdbiPluginRowMapper 調用 installPlugininstallRowMapper。 之後,我們就可以使用完全配置好的 Jdbi 實例在我們的應用程序中使用。

4. 示例領域

我們的示例使用一個非常簡單的領域模型,僅包含兩個類:CarMakerCarModel。由於 JDBI 不需要對領域類進行任何註解,因此我們可以使用簡單的 POJO:

public class CarMaker {
    private Long id;
    private String name;
    private List<CarModel> models;
    // getters and setters ...
}

public class CarModel {
    private Long id;
    private String name;
    private Integer yearDate;
    private String sku;
    private Long makerId;
    // getters and setters ...
}

5. 創建 DAO

現在,讓我們為我們的領域類創建數據訪問對象 (DAO)。 JDBI 的 SqlObject 插件提供了一種簡單的方法來實現這些類,這與 Spring Data 處理類似的主題方式相似。

我們只需要定義一個接口並添加一些註解,JDBI 將自動處理所有低級別的任務,例如處理 JDBC 連接、創建和銷燬語句以及 ResultSet

@UseClasspathSqlLocator
public interface CarMakerDao {
    @SqlUpdate
    @GetGeneratedKeys
    Long insert(@BindBean CarMaker carMaker);
    
    @SqlBatch("insert")
    @GetGeneratedKeys
    List<Long> bulkInsert(@BindBean List<CarMaker> carMakers);
    
    @SqlQuery
    CarMaker findById(Long id);
}

@UseClasspathSqlLocator
public interface CarModelDao {    
    @SqlUpdate
    @GetGeneratedKeys
    Long insert(@BindBean CarModel carModel);

    @SqlBatch("insert")
    @GetGeneratedKeys
    List<Long> bulkInsert(@BindBean List<CarModel> models);

    @SqlQuery
    CarModel findByMakerIdAndSku(@Bind("makerId") Long makerId, @Bind("sku") String sku );
}

這些接口被廣泛註釋,我們現在快速地查看一下它們。

5.1. @UseClasspathSqlLocator

@UseClasspathSqlLocator 標註告訴 JDBI 實際與每個方法關聯的 SQL 語句位於外部資源文件中。 默認情況下,JDBI 將使用接口的完全限定名和方法來查找資源。 例如,給定接口的 FQN 為 a.b.c.Foo 且具有 findById() 方法,JDBI 將查找名為 a/b/c/Foo/findById.sql 的資源。

此默認行為可以通過為 @SqlXXX 註解傳遞資源名稱來覆蓋任何給定方法的行為。

5.2. @SqlUpdate/@SqlBatch/@SqlQuery

我們使用 @SqlUpdate@SqlBatch@SqlQuery 註解來標記數據訪問方法,這些方法將使用提供的參數執行。這些註解可以接受可選的字符串值,該字符串值將是執行的字面 SQL 語句——包括任何命名參數——或者當與 @UseClasspathSqlLocator 一起使用時,它將包含該語句的資源名稱。

當註解為 @SqlBatch 的方法具有類似集合的參數,並且可以為可用項中的每個項執行相同的 SQL 語句,通過批量語句。

5.3. @GetGeneratedKeys

作為名稱所示,<strong class="annotation">@GetGeneratedKeys</strong> 註解允許我們在成功執行後檢索生成的鍵。它主要用於 <em class="statement">insert</em> 語句,其中數據庫會自動生成新的標識符,並且我們需要在代碼中檢索它們。

5.4. @BindBean/@Bind

我們使用 @BindBean@Bind 註解,將 SQL 語句中的命名參數與方法參數綁定。 @BindBean 使用標準的 Bean 約定從 POJO 中提取屬性,包括嵌套屬性。 @Bind 使用參數名稱或提供的值將其值映射到命名參數。

6. 使用 DAO

為了在我們的應用程序中使用這些 DAO,我們需要使用 JDBI 中提供的工廠方法進行實例化。

在 Spring 上下文中,最簡單的方法是為每個 DAO 創建一個 Bean,使用 onDemand 方法:

@Bean
public CarMakerDao carMakerDao(Jdbi jdbi) {        
    return jdbi.onDemand(CarMakerDao.class);       
}

@Bean
public CarModelDao carModelDao(Jdbi jdbi) {
    return jdbi.onDemand(CarModelDao.class);
}

onDemand 創建的實例是線程安全的,並在方法調用期間僅使用數據庫連接。由於我們使用 TransactionAwareDataSourceProxy,這意味着我們可以無縫地與 Spring 管理的事務一起使用。

雖然這種方法簡單,但在處理多於幾個表的情況下,它並不是理想的。為了避免編寫這種樣板代碼,可以創建一個自定義 。但描述如何實現這樣一個組件超出了本教程的範圍。

7. 事務性服務

讓我們使用 DAO 類在一個簡單的服務類中創建幾個 CarModel 實例,給定一個填充了模型 CarMaker。 首先,我們將檢查給定的 CarMaker 是否之前已保存,如果需要則將其保存到數據庫中。 然後,我們將逐個插入每個 CarModel

如果在任何時候發生唯一鍵衝突(或其他錯誤)時,整個操作必須失敗,並且應執行完整的回滾

JDBI 提供了一個 @Transaction 註解,但我們在這裏不能使用它,因為它不瞭解可能參與同一業務事務的其他資源。 相反,我們將使用服務方法中的 Spring 的 @Transactional 註解:

@Service
public class CarMakerService {
    
    private CarMakerDao carMakerDao;
    private CarModelDao carModelDao;

    public CarMakerService(CarMakerDao carMakerDao,CarModelDao carModelDao) {        
        this.carMakerDao = carMakerDao;
        this.carModelDao = carModelDao;
    }    
    
    @Transactional
    public int bulkInsert(CarMaker carMaker) {
        Long carMakerId;
        if (carMaker.getId() == null ) {
            carMakerId = carMakerDao.insert(carMaker);
            carMaker.setId(carMakerId);
        }
        carMaker.getModels().forEach(m -> {
            m.setMakerId(carMaker.getId());
            carModelDao.insert(m);
        });                
        return carMaker.getModels().size();
    }
}

這個操作的實現本身相當簡單:我們採用的標準約定是,在 id 字段中值為 null 的情況下,表示該實體尚未持久化到數據庫。如果這種情況發生,我們使用構造函數中注入的 CarMakerDao 實例來在數據庫中插入一條新記錄並獲取生成的 id

一旦我們獲取了 CarMakerid,我們就遍歷模型,為每個模型設置 makerId 字段,然後再將其保存到數據庫中。

所有這些數據庫操作將使用相同的底層連接,並且將作為同一事務的一部分。這裏的關鍵在於我們如何使用 TransactionAwareDataSourceProxy 將 JDBI 與 Spring 綁定,以及創建 onDemand DAOs。當 JDBI 請求一個新的 Connection 時,它將獲取與當前事務關聯的現有連接,從而將它的生命週期集成到其他可能已註冊的資源中。

8. 結論

在本文中,我們展示瞭如何快速地將 JDBI 集成到 Spring Boot 應用程序中。這在某些原因下我們無法使用 Spring Data JPA,但仍然希望利用所有其他功能(如事務管理、集成等)時,是一種強大的組合。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.