Java 8 Streams 中的資料庫 CRUD 操作

oschina發表於2016-11-02

接觸一個新工具的時候,剛開始要克服的最大障礙就是如何讓你自己先嚐試做出一個小東西來。現在你也許對 Java 8 中新的 Stream API 的運作方式在理解上比較自信,但你也許並沒用它來進行過資料庫查詢操作。為了幫助你開始使用 Stream API 來對 SQL 資料庫進行建立、修改和讀取操作, 我已經在這個快速開始的教程中把它們整合到了一起。希望它能幫助你提升對流式API的使用水平!

背景

Speedment 是一個開放原始碼的工具集,它可以被用來生成 Java 實體,並且能將我們同資料庫的通訊過程管理起來。你可以利用一個圖形工具連線到資料庫並生成出一套完整的 ORM 框架程式碼來表示域模型。但是 Speedment 不單單只是一個程式碼生成器而已,它還是一個能插入應用程式中的執行時程式,這樣就有可能將你的 Java 8 流式程式碼翻譯成優化過的SQL查詢。這也是我將會在本文中專門講述的一個部分。

生成程式碼

要在一個 Maven 工程中開始使用 Speedment,需要你將下面幾行程式碼新增到你的 pom.xml 檔案中。在本例中,我使用的是 MySQL,而你也可以選擇使用 PostgreSQL 或者 MariaDB。面向於像Oracle這樣的專有資料庫可用於企業級客戶。

Pom.xml

<properties>
  <speedment.version>3.0.1</speedment.version>
  <db.groupId>mysql</db.groupId>
  <db.artifactId>mysql-connector-java</db.artifactId>
  <db.version>5.1.39</db.version>
</properties>

<dependencies>
  <dependency>
    <groupId>com.speedment</groupId>
    <artifactId>runtime</artifactId>
    <version>${speedment.version}</version>
    <type>pom</type>
  </dependency>

  <dependency>
    <groupId>${db.groupId}</groupId>
    <artifactId>${db.artifactId}</artifactId>
    <version>${db.version}</version>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>com.speedment</groupId>
      <artifactId>speedment-maven-plugin</artifactId>
      <version>${speedment.version}</version>

      <dependencies>
        <dependency>
          <groupId>${db.groupId}</groupId>
          <artifactId>${db.artifactId}</artifactId>
          <version>${db.version}</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>

現在你可以訪問到許多新的 Maven 資源庫,它們能讓你更加輕鬆的使用這個工具包。要啟動 Speedment UI, 執行如下命令:

mvn speedment:tool

這樣就會有一個過程引導你連線到資料庫並對程式碼生成進行配置。一開始最簡單的方法就是用預設的設定先跑起來再說。當你按下生成按鈕“Generate,” Speedment 就會對你的資料庫後設資料進行分析,然後在你的工程中新增像實體和實體管理器這樣的類。

初始化 Speedment

當你的域模型生成好了以後,Speedment 的設定就容易了。建立一個新的 Main.java 檔案然後新增如下幾行程式碼。你看到的類都是生成的,因此它們的命名都是根據資料庫模式、表以及列的名稱來決定的。

Main.java

public class Main {
  public static void main(String... param) {
    final HaresApplication app = new HaresApplicationBuilder()
      .withPassword("password")
      .build();
  }
}

上面的程式碼建立了一個新的應用程式實體,它使用了一種生成的構造器模式。構造器是的對任何執行時配置細節的設定成為可能,例如資料庫的密碼。

當我們有了一個應用實體,就可以用它來訪問生成的實體管理器了。在這裡,我的資料庫中有了四個表; “hare”, “carrot”, “human”, 以及 “friend”. (你可以在這裡找到完整的資料庫定義)。

final CarrotManager carrots = app.getOrThrow(CarrotManager.class);
final HareManager hares     = app.getOrThrow(HareManager.class);
final HumanManager humans   = app.getOrThrow(HumanManager.class);
final FriendManager hares   = app.getOrThrow(FriendManager.class);

現在這些實體管理器可以被用來執行所有的CRUD操作了。

建立實體

建立實體的方式非常直接。我們就使用實體生成的實現,把列的值設定好然後持久化到資料來源就可以了。

hares.persist(
  new HareImpl()
    .setName("Harry")
    .setColor("Gray")
    .setAge(8)
);

persist 方法會返回一個 (潛在的) Hare 新例項,裡面像“id”這種自動生成鍵已經設定好了。如果我們想在持久化之後繼續使用 Harry, 那就可以使用 persist 方法返回的這個:

final Hare harry = hares.persist(
  new HareImpl()
    .setName("Harry")
    .setColor("Gray")
    .setAge(8)
);
 如果持久化操作失敗了,例如如果有一個外來鍵違反了唯一性約束,就會有一個 SpeedmentException 丟擲。我們應該對此進行檢查,如果有默寫東西會阻止我們對這條 hare 記錄進行持久化,就應該顯示一條錯誤資訊。
try {
  final Hare harry = hares.persist(
    new HareImpl()
      .setName("Harry")
      .setColor("Gray")
      .setAge(8)
  );
} catch (final SpeedmentException ex) {
  System.err.println(ex.getMessage());
  return;
}

讀取實體

Speedment 執行時中最酷的功能特性就是能夠使用 Java 8 的 Stream API對資料庫中的資料進行流式操作。“為什麼這樣做會很酷呢?” 你可能會這樣問你自己。如今甚至Hibernate 都已經支援流式操作了!”這就是回答。

使用 Speedment 流式操作最美好的事情就是它們把構建流的中間和終止動作都考慮進去了。這就意味著如果你在流已經被建立之後新增一個過濾器進去,那麼在構建 SQL 語句時這個過濾器也會被考慮進去。

下面是一個示例,我們想要計算資料庫中 hare 記錄的總數。

final long haresTotal = hares.stream().count();
System.out.format("There are %d hares in total.%n", haresTotal);

這段程式碼將會生成的SQL查詢如下:

SELECT COUNT(id) FROM hares.hare;

這裡的終止操作就是 .count() ,因此 Speedment 就知道是要建立一個 SELECT COUNT(…) 語句。它也知道 “hare”表的主鍵是“id”這個列,如此就有可能將傳送給資料庫的整個語句 減少到這個樣子。

更加複雜的示例可能就是找出名稱以 “rry” 並且年齡大於等於 5 的兔子的數量。這個可以這樣寫:

final long complexTotal = hares.stream()
  .filter(Hare.NAME.endsWith("rry"))
  .filter(Hare.AGE.greaterOrEqual(5))
  .count();

我們使用由 Speedment 為我們生成的位於構建器來定義過濾器。這使得我們以程式設計的方式對流進行分析並且將其簡化到如下這樣一條SQL語句成為可能:

SELECT COUNT(id) FROM hares.hare
WHERE hare.name LIKE CONCAT("%", ?)
AND hare.age >= 5;

如果我們新增了一個 Speedment 不可以對流進行優化的操作, 它就會像一般的 Java 8 流那被處理。我們永遠都不會限制生成的位於構建器的使用,它能是流式操作更加的高效。

final long inefficientTotal = hares.stream()
  .filter(h -> h.getName().hashCode() == 52)
  .count();

上述程式碼會產生一條如下極其低效的語句,但是它仍然可以跑起來。

SELECT id,name,color,age FROM hares.hare;

更新實體

更新存在的實體和讀取以及持久化實體非常相似。在我們呼叫update()方法之前,對實體本地拷貝的改變,不會影響資料庫內容。

下面,我們拿到之前使用Hare建立的Harry,並將他的顏色變為棕色:

harry.setColor("brown");
final Hare updatedHarry = hares.update(harry);

如果更新被接受了,那麼管理器會返回hare的一個新的拷貝,因為我們在後面會繼續使用這個例項。就想做“建立”的例子中,更新可能會失敗。也許顏色被定義為“值唯一”,棕色已經存在於hare中。那樣的話,會丟擲一個SpeedmentException異常.

我們也可以通過合併多個實體到一個流中來同時更新他們。加入我們想將所有名字為Harry的hare變為棕色,我們可以這樣做:

hares.stream()
  .filter(Hare.NAME.equal("Harry"))
  .map(Hare.COLOR.setTo("Brown"))
  .forEach(hares.updater()); // 更新流中存在的元素

我們還應該使用try-catch語句來確保在執行過程中有失敗發生時警告使用者。

try {
  hares.stream()
    .filter(Hare.NAME.equal("Harry"))
    .map(Hare.COLOR.setTo("Brown"))
    .forEach(hares.updater());
} catch (final SpeedmentException ex) {
  System.err.println(ex.getMessage());
  return;
}

實體刪除

我們需要知道的最後一個 CRUD 操作就是從資料庫中刪除實體。這個操作幾乎和“更新”操作時等同的。假如說我們要把年齡超過10歲的兔子的記錄都刪除,就要這樣做:

try {
  hares.stream()
    .filter(Hare.AGE.greaterThan(10))
    .forEach(hares.remover()); // Removes remaining hares
} catch (final SpeedmentException ex) {
  System.err.println(ex.getMessage());
  return;
}

總結

通過閱讀本文你已經瞭解瞭如何在一個 Maven 工程中對 Speedment 進行設定,還有如何使用 Java 8 的 Stream API 來從資料庫中建立、更新、讀取以及刪除實體。這是你可以利用 Speedment 所能進行的操作的一個小的子集, 但已經是一個能讓你上手的好的開始了。更多的示例以及更加高階的使用場景可以在 GitHub-page 上找到。

相關文章