今日の学習内容

今日から CAP Java の学習を始めました。今まで Node.js 版で学んできた CAP の知識を活かしながら、Java ならではのビジネスロジック実装方法を学びました。

① 環境セットアップ(Java 21 + Maven)

# Java 21 インストール
sudo apt install -y openjdk-21-jdk

# Maven インストール
sudo apt install -y maven

# バージョン確認
java --version
mvn --version

② CAP Java プロジェクトの作成

Maven の archetype を使って CAP Java プロジェクトを作成します。

mvn -B archetype:generate   -DarchetypeArtifactId=cds-services-archetype   -DarchetypeGroupId=com.sap.cds   -DarchetypeVersion=RELEASE   -DgroupId=com.example   -DartifactId=my-cap-java   -Dpackage=com.example.bookshop

作成されるプロジェクト構造:

my-cap-java/
├── db/                    ← CDS データモデル
├── srv/                   ← CDS サービス + Java Handler
│   └── src/main/java/
│       └── com/example/bookshop/
│           └── Application.java
├── pom.xml                ← Maven 設定
└── package.json

③ gen/ フォルダの仕組み

CAP Java の重要な概念として、CDS ファイルから Java クラスが自動生成されます。

db/schema.cds + srv/cat-service.cds
        ↓  mvn compile
srv/src/gen/java/cds/gen/
    ├── catalogservice/
    │   ├── Books.java        ← getter/setter 付きデータクラス
    │   ├── Books_.java       ← メタ情報(CDS_NAME など)
    │   └── CatalogService_.java
    └── my/bookshop/
        └── Books.java

⚠️ 重要:gen/ フォルダは絶対に手動で編集しないこと!mvn compile するたびに上書きされます。

④ Java Handler の実装

CAP Java では Handler クラスを作成してビジネスロジックを実装します。イベントには @Before@On@After の3種類があります。

アノテーション タイミング 主な用途
@BeforeDB 保存の前バリデーション・データ加工
@OnDB 保存の代わり完全カスタムロジック
@AfterDB 保存の後ログ・通知・後処理

実装したコード

@Component
@ServiceName(CatalogService_.CDS_NAME)
public class CatalogServiceHandler implements EventHandler {

    // ① Before:登録前のバリデーション
    @Before(event = CqnService.EVENT_CREATE, entity = Books_.CDS_NAME)
    public void beforeCreateBooks(List<Books> books) {
        for (Books book : books) {
            if (book.getStock() != null && book.getStock() < 0) {
                throw new IllegalArgumentException(
                    "在庫数は0以上でなければなりません。"
                );
            }
            if (book.getPrice() != null && book.getPrice().signum() <= 0) {
                throw new IllegalArgumentException(
                    "価格は0より大きくなければなりません。"
                );
            }
        }
    }

    // ② After:登録後のログ出力
    @After(event = CqnService.EVENT_CREATE, entity = Books_.CDS_NAME)
    public void afterCreateBooks(List<Books> books) {
        for (Books book : books) {
            System.out.println(
                "✅ 新しい本が登録されました: [ID=" + book.getId() +
                ", タイトル=" + book.getTitle() +
                ", 在庫=" + book.getStock() + "]"
            );
        }
    }

    // ③ After READ:在庫切れ警告
    @After(event = CqnService.EVENT_READ, entity = Books_.CDS_NAME)
    public void afterReadBooks(List<Books> books) {
        for (Books book : books) {
            if (book.getStock() != null && book.getStock() == 0) {
                System.out.println(
                    "⚠️ 在庫切れ: [ID=" + book.getId() +
                    ", タイトル=" + book.getTitle() + "]"
                );
            }
        }
    }
}

⑤ 動作確認

CAP Java は Spring Boot で起動するため、ポートが Node.js 版と異なります。

CAP Node.js CAP Java
起動コマンドcds watchmvn spring-boot:run
ポート40048080
サービスパス/odata/v4/catalog/odata/v4/CatalogService

バリデーションテスト(在庫 -1 でエラー):

POST http://localhost:8080/odata/v4/CatalogService/Books
{
  "ID": 2,
  "title": "Invalid Book",
  "stock": -1,
  "price": 4800.00
}

→ {"error":{"code":"500","message":"在庫数は0以上でなければなりません。"}}

イベント処理の流れ

リクエスト受信
    ↓
@Before  ← バリデーション・データ加工
    ↓
@On      ← DB操作(省略時は CAP が自動処理)
    ↓
@After   ← ログ・通知・後処理
    ↓
レスポンス返却

この記事は「SAP BTP 学習」シリーズの第5回です。6ヶ月間の独学で SAP BTP 開発へのキャリアチェンジを目指しています。