Java開發之使用Java 8 Streams 對數據庫進行 CRUD 操作

背景

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.hareWHERE 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章