【保存版】Thymeleafのif条件分岐 完全マスター!実践で使える15のテクニック

Thymeleafのif条件分岐とは?基礎から解説

th:ifの基本的な書き方と動作原理

Thymeleafのth:if属性は、HTML要素の表示/非表示を条件によって制御する機能です。この属性は、Spring Bootアプリケーションでの画面開発において最も頻繁に使用される機能の1つです。

基本構文

<div th:if="${条件式}">
    条件が真の場合に表示される内容
</div>

評価ルール

Thymeleafは以下の値を「偽(false)」として評価します:

  • false(ブール値)
  • 数値の0
  • null
  • 空文字列""
  • 空配列
  • 空のコレクション

それ以外の値は全て「真(true)」として評価されます。

実践的な使用例

<!-- 基本的な変数の判定 -->
<div th:if="${user.isActive}">
    このユーザーはアクティブです
</div>

<!-- null チェック -->
<div th:if="${user != null}">
    ユーザー情報が存在します
</div>

<!-- 数値の比較 -->
<div th:if="${product.stock > 0}">
    在庫があります
</div>

<!-- 文字列の比較 -->
<div th:if="${user.role == 'ADMIN'}">
    管理者向けメニュー
</div>

th:unlessで否定条件を簡潔に書く方法

th:unlessth:ifの否定版で、条件が偽の場合に要素を表示します。コードの可読性を高めたい場合に特に有用です。

th:unlessの基本構文

<div th:unless="${条件式}">
    条件が偽の場合に表示される内容
</div>

th:if vs th:unlessの使い分け

以下の例で、同じ条件をそれぞれの方法で表現する方法を見てみましょう:

<!-- th:ifを使用した場合 -->
<div th:if="${product.stock == 0}">
    在庫切れです
</div>

<!-- th:unlessを使用した場合 -->
<div th:unless="${product.stock > 0}">
    在庫切れです
</div>
使い分けのポイント:
  • 否定の条件が自然に読める場合はth:unlessを使用
  • 複雑な条件式の場合はth:ifを使用
  • チーム内で統一的な使用方針を決めることが重要

Switch文との使い分けポイント

条件分岐にはth:ifの他にth:switch/th:caseも使用できます。それぞれの特徴を理解し、適切に使い分けることが重要です。

Switch文の基本構文

<div th:switch="${user.role}">
    <p th:case="'ADMIN'">管理者です</p>
    <p th:case="'USER'">一般ユーザーです</p>
    <p th:case="*">権限がありません</p>
</div>

使い分けの指針

  1. th:ifを使用するケース:
    • 条件が独立している場合
    • 真偽の判定が主な目的の場合
    • 複雑な条件式を使用する場合
  2. th:switchを使用するケース:
    • 1つの変数に対して複数の値をチェックする場合
    • 相互排他的な条件分岐の場合
    • コードの見通しをよくしたい場合

実践的な使い分け例

<!-- th:ifの適切な使用例 -->
<div th:if="${user.isAdmin()}">
    <button>ユーザー管理</button>
</div>
<div th:if="${user.canEditPosts()}">
    <button>投稿編集</button>
</div>

<!-- th:switchの適切な使用例 -->
<div th:switch="${order.status}">
    <p th:case="'PENDING'">処理待ち</p>
    <p th:case="'PROCESSING'">処理中</p>
    <p th:case="'COMPLETED'">完了</p>
    <p th:case="'CANCELLED'">キャンセル</p>
    <p th:case="*">不明なステータス</p>
</div>

このように、使用シーンに応じて適切な条件分岐方法を選択することで、メンテナンス性の高いコードを実現できます。

実践で使える!Thymeleafのif条件分岐15のテクニック

複数条件を組み合わせた高度な分岐処理

1. AND条件の組み合わせ

<!-- 複数条件のAND結合 -->
<div th:if="${user.isActive} and ${user.hasPermission('ADMIN')}">
    管理者向けコンテンツ
</div>

<!-- ネストされた条件チェック -->
<div th:if="${order != null and order.status == 'SHIPPED' and order.trackingNumber != null}">
    配送状況を確認する
</div>

2. OR条件の活用

<!-- 複数条件のOR結合 -->
<div th:if="${user.isAdmin} or ${user.isModerator}">
    コンテンツ管理メニュー
</div>

<!-- 複雑なOR条件の組み合わせ -->
<div th:if="${item.isDiscounted} or (${item.stock &lt; 5} and ${item.isPopular})">
    注目商品!
</div>

3. 複合条件式の最適化

<!-- 条件式を変数として定義 -->
<div th:with="isEligible=${user.age >= 20 and user.hasValidId}">
    <div th:if="${isEligible}">
        サービスを利用できます
    </div>
</div>

ElvisオペレータとNullチェックの組み合わせ技

4. Nullセーフな条件分岐

<!-- Elvisオペレータを使用したnull安全な表示 -->
<div th:if="${user?.premium} ?: false">
    プレミアム会員向けコンテンツ
</div>

<!-- ネストされたプロパティのnullチェック -->
<div th:if="${order?.customer?.address} != null">
    配送先住所が登録されています
</div>

5. デフォルト値を使用した条件分岐

<!-- デフォルト値との組み合わせ -->
<div th:with="status=${order?.status} ?: 'PENDING'">
    <div th:if="${status == 'COMPLETED'}">
        注文完了
    </div>
</div>

リストや配列の要素チェックテクニック

6. コレクションの空チェック

<!-- リストの存在・空チェック -->
<div th:if="${not #lists.isEmpty(items)}">
    <ul>
        <li th:each="item : ${items}" th:text="${item.name}">商品名</li>
    </ul>
</div>

<!-- 配列の長さチェック -->
<div th:if="${#arrays.length(selectedOptions) > 0}">
    選択されたオプション一覧
</div>

7. 特定要素の存在チェック

<!-- リスト内の特定要素チェック -->
<div th:if="${#lists.contains(user.roles, 'ADMIN')}">
    管理者権限があります
</div>

セッション・リクエストパラメータでの条件分岐

8. セッション変数のチェック

<!-- セッション変数の存在チェック -->
<div th:if="${session.containsKey('userId')}">
    ログイン中です
</div>

<!-- セッション属性を使用した条件分岐 -->
<div th:if="${session.preferences?.darkMode} ?: false">
    ダークモード有効
</div>

9. リクエストパラメータの活用

<!-- パラメータの存在チェック -->
<div th:if="${param.containsKey('error')}">
    <div class="alert alert-danger">
        ログインに失敗しました
    </div>
</div>

<!-- パラメータ値による分岐 -->
<div th:if="${param.view != null and param.view[0] == 'detailed'}">
    詳細ビュー
</div>

Spring Securityと組み合わせた権限制御

10. 認証状態のチェック

<!-- 認証済みユーザーのチェック -->
<div th:if="${#authentication.isAuthenticated()}">
    ログイン中のユーザー: <span th:text="${#authentication.name}">username</span>
</div>

11. ロールベースの表示制御

<!-- 特定ロールの確認 -->
<div th:if="${#authorization.expression('hasRole(''ROLE_ADMIN'')')}">
    <a href="/admin/dashboard">管理画面へ</a>
</div>

<!-- 複数ロールの組み合わせ -->
<div th:if="${#authorization.expression('hasAnyRole(''ROLE_ADMIN'', ''ROLE_MANAGER'')')}">
    <button>高度な設定</button>
</div>

12. カスタム権限のチェック

<!-- カスタムセキュリティ式の使用 -->
<div th:if="${#authorization.expression('hasPermission(#id, ''Product'', ''EDIT'')')}">
    <button>商品を編集</button>
</div>

13. ユーザー情報に基づく細かな制御

<!-- プリンシパル情報を使用した条件分岐 -->
<div th:if="${#authentication.principal.department == 'SALES'}">
    <div>売上レポート</div>
</div>

14. セキュリティコンテキストの高度な活用

<!-- セキュリティコンテキストと業務ロジックの組み合わせ -->
<div th:with="hasAccess=${#authorization.expression('hasRole(''ADMIN'')') or 
                         (#authorization.expression('hasRole(''USER'')') and ${item.creator.id == #authentication.principal.id})}">
    <div th:if="${hasAccess}">
        <button>コンテンツを編集</button>
    </div>
</div>

15. エラーハンドリングとの組み合わせ

<!-- 権限エラーの適切な表示 -->
<div th:if="${#authorization.expression('!hasRole(''ADMIN'')')}">
    <div class="alert alert-warning">
        この操作には管理者権限が必要です
    </div>
</div>

これらのテクニックを適切に組み合わせることで、より柔軟で保守性の高い条件分岐処理を実現できます。

現場で役立つ!if条件分岐のベストプラクティス

可読性を高めるリファクタリングテクニック

1. 条件式の抽出と再利用

<!-- Before: 複雑な条件式を直接記述 -->
<div th:if="${user.age >= 20 and user.hasValidId and user.subscriptionStatus == 'ACTIVE'}">
    プレミアムコンテンツ
</div>

<!-- After: th:withを使用して条件を抽出 -->
<div th:with="canAccessPremium=${user.age >= 20 and user.hasValidId and user.subscriptionStatus == 'ACTIVE'}">
    <div th:if="${canAccessPremium}">
        プレミアムコンテンツ
    </div>
</div>

2. Helper関数の活用

// Controllerまたはユーティリティクラスで定義
@Component
public class UserPermissionUtils {
    public boolean canAccessPremiumContent(User user) {
        return user.getAge() >= 20 
            && user.hasValidId() 
            && "ACTIVE".equals(user.getSubscriptionStatus());
    }
}
<!-- Helperメソッドを使用した簡潔な条件式 -->
<div th:if="${@userPermissionUtils.canAccessPremiumContent(user)}">
    プレミアムコンテンツ
</div>

3. 意図が明確な命名規則

<!-- Bad: 意図が不明確な命名 -->
<div th:if="${flag && status == 1}">

<!-- Good: 意図が明確な命名 -->
<div th:if="${isUserVerified && orderStatus == 'COMPLETED'}">

パフォーマンスを考慮した条件式の書き方

1. 条件式の評価順序の最適化

<!-- Bad: 重い処理が先に評価される -->
<div th:if="${heavyDatabaseOperation() and simpleCheck}">

<!-- Good: 軽い処理を先に評価 -->
<div th:if="${simpleCheck and heavyDatabaseOperation()}">

2. キャッシュの活用

// Controllerで条件をキャッシュ
@ModelAttribute("commonConditions")
public Map<String, Boolean> prepareCommonConditions() {
    Map<String, Boolean> conditions = new HashMap<>();
    conditions.put("isSystemAvailable", systemService.checkAvailability());
    conditions.put("isMaintenanceMode", configService.isMaintenanceMode());
    return conditions;
}
<!-- キャッシュされた条件を使用 -->
<div th:if="${commonConditions.isSystemAvailable}">
    システムは利用可能です
</div>

3. 不要な条件評価の回避

<!-- Bad: ループ内で毎回条件を評価 -->
<tr th:each="item : ${items}">
    <td th:if="${#authorization.expression('hasRole(''ADMIN'')')}">
        管理者操作
    </td>
</tr>

<!-- Good: ループの外で一度だけ評価 -->
<div th:with="isAdmin=${#authorization.expression('hasRole(''ADMIN'')')}">
    <tr th:each="item : ${items}">
        <td th:if="${isAdmin}">
            管理者操作
        </td>
    </tr>
</div>

保守性を高めるための設計パターン

1. 責務の分離

// ビジネスロジックをサービス層に分離
@Service
public class ContentVisibilityService {
    public boolean shouldShowPremiumContent(User user) {
        return user.isPremiumMember() 
            && !user.isSubscriptionExpired() 
            && user.hasAccessToFeature("premium_content");
    }
}
<!-- テンプレートではシンプルな呼び出しのみ -->
<div th:if="${@contentVisibilityService.shouldShowPremiumContent(user)}">
    プレミアムコンテンツ
</div>

2. 条件のカプセル化

// 条件をenumで管理
public enum DisplayCondition {
    SHOW_PREMIUM_CONTENT {
        @Override
        public boolean evaluate(User user) {
            return user.isPremiumMember();
        }
    },
    SHOW_ADMIN_PANEL {
        @Override
        public boolean evaluate(User user) {
            return user.hasRole("ADMIN");
        }
    };

    public abstract boolean evaluate(User user);
}
<!-- カプセル化された条件を使用 -->
<div th:if="${@displayConditionEvaluator.evaluate(DisplayCondition.SHOW_PREMIUM_CONTENT, user)}">
    プレミアムコンテンツ
</div>

3. テスト容易性の確保

// テスト可能な設計
@Component
public class FeatureToggleService {
    private final ConfigurationService configService;

    public boolean isFeatureEnabled(String featureName, User user) {
        return configService.isFeatureActive(featureName) 
            && user.hasAccessTo(featureName);
    }
}
<!-- テスト可能な条件分岐 -->
<div th:if="${@featureToggleService.isFeatureEnabled('new_dashboard', user)}">
    新しいダッシュボード
</div>

これらのベストプラクティスを適用することで、より保守性が高く、パフォーマンスの良い条件分岐処理を実装できます。また、チーム開発においても一貫性のあるコードベースを維持することが可能になります。

初心者がハマりやすい!if条件分岐の注意点と対策

よくあるエラーとその解決方法

1. NullPointerException関連のエラー

よくある問題

<!-- Bad: Nullチェックなしで直接プロパティにアクセス -->
<div th:if="${user.premium}">
    プレミアム会員です
</div>

解決策

<!-- Good: Safe Navigation演算子を使用 -->
<div th:if="${user?.premium}">
    プレミアム会員です
</div>

<!-- Better: 完全なNull対策 -->
<div th:if="${user != null and user.premium}">
    プレミアム会員です
</div>

2. 比較演算子の誤用

よくある問題

<!-- Bad: 等価比較の誤った使用 -->
<div th:if="${status = 'ACTIVE'}">  <!-- 代入演算子を使用してしまっている -->
    アクティブユーザー
</div>

解決策

<!-- Good: 正しい等価比較 -->
<div th:if="${status == 'ACTIVE'}">
    アクティブユーザー
</div>

<!-- Better: 文字列比較の場合はequalsを使用 -->
<div th:if="${#strings.equals(status, 'ACTIVE')}">
    アクティブユーザー
</div>

3. スコープの誤認識

よくある問題

<!-- Bad: スコープを明示していない -->
<div th:if="${username}">
    ようこそ、<span th:text="${username}">ゲスト</span>さん
</div>

解決策

<!-- Good: スコープを明示的に指定 -->
<div th:if="${session.username != null}">
    ようこそ、<span th:text="${session.username}">ゲスト</span>さん
</div>

デバッグのコツと効率的なトラブルシューティング

1. デバッグ情報の表示

<!-- 変数の中身を確認 -->
<div th:text="${#vars}"></div>

<!-- 特定の変数の型と値を確認 -->
<div th:text="${#objects.nullSafe(user, 'null')}"></div>
<div th:text="${#objects.nullSafe(user?.getClass(), 'null')}"></div>

2. 条件式の段階的な検証

<!-- 複雑な条件を分解して確認 -->
<div th:with="condition1=${user != null}, 
              condition2=${user.age >= 20}, 
              condition3=${user.hasValidSubscription()}">

    <div>条件1: <span th:text="${condition1}">false</span></div>
    <div>条件2: <span th:text="${condition2}">false</span></div>
    <div>条件3: <span th:text="${condition3}">false</span></div>

    <div th:if="${condition1 and condition2 and condition3}">
        全ての条件を満たしています
    </div>
</div>

3. デバッグモードの活用

# application.properties
spring.thymeleaf.cache=false
logging.level.org.thymeleaf=TRACE

テストコードでの条件分岐の検証方法

1. ユニットテストの作成

@Test
public void testUserAccessConditions() {
    User user = new User();
    user.setAge(25);
    user.setSubscriptionStatus("ACTIVE");

    ModelAndView modelAndView = new ModelAndView("user/profile");
    modelAndView.addObject("user", user);

    String rendered = thymeleafEngine.process("user/profile", context);
    assertTrue(rendered.contains("プレミアムコンテンツ"));
}

2. テストケースの網羅

@TestFactory
Stream<DynamicTest> testVariousConditions() {
    return Stream.of(
        // 正常系テスト
        TestCase.of("通常ユーザー", createNormalUser(), true),
        TestCase.of("管理者ユーザー", createAdminUser(), true),
        // エッジケース
        TestCase.of("null ユーザー", null, false),
        TestCase.of("無効化ユーザー", createDisabledUser(), false)
    ).map(testCase -> DynamicTest.dynamicTest(
        testCase.name,
        () -> assertCondition(testCase.user, testCase.expected)
    ));
}

3. 条件分岐のモック化

@Test
public void testComplexCondition(@Mock UserService userService) {
    when(userService.isUserEligible(any())).thenReturn(true);

    // テンプレートのレンダリング
    Context context = new Context();
    context.setVariable("userService", userService);

    String result = templateEngine.process("template", context);
    assertTrue(result.contains("条件を満たしています"));
}

デバッグ時の主なチェックポイント

  1. 変数の存在確認
    • セッション変数、リクエストパラメータ、モデル属性が期待通りに存在するか
    • スコープは正しく設定されているか
  2. 型の一致確認
    • 比較対象の型は一致しているか
    • 暗黙の型変換が期待通りに行われているか
  3. 条件式の評価順序
    • 複合条件の場合、各部分が期待通りに評価されているか
    • Short-circuit評価が意図通りに機能しているか
  4. 文字列比較の方法
    • ==equalsの使い分けは適切か
    • 大文字小文字の区別は意図通りか

これらの注意点を意識し、適切なデバッグとテストを行うことで、より信頼性の高い条件分岐処理を実装できます。

実践的なサンプルコードで学ぶ応用テクニック

ユーザー管理画面での活用例

1. ユーザー一覧画面の実装

<!-- users/list.html -->
<div class="user-management">
    <!-- 管理者向け操作メニュー -->
    <div th:if="${#authorization.expression('hasRole(''ADMIN'')')}" class="admin-controls">
        <button class="btn btn-primary" onclick="location.href='/users/new'">新規ユーザー登録</button>
        <button class="btn btn-secondary" onclick="exportUserList()">ユーザー一覧出力</button>
    </div>

    <!-- ユーザー一覧テーブル -->
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>名前</th>
                <th>ステータス</th>
                <th th:if="${#authorization.expression('hasAnyRole(''ADMIN'', ''MANAGER'')')}">操作</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="user : ${users}">
                <td th:text="${user.id}">1</td>
                <td>
                    <!-- プレミアムユーザーの場合、アイコンを表示 -->
                    <i th:if="${user.isPremium}" class="fas fa-crown" title="プレミアム会員"></i>
                    <span th:text="${user.name}">山田太郎</span>
                </td>
                <td>
                    <!-- ステータスに応じて表示を変更 -->
                    <span th:if="${user.status == 'ACTIVE'}" class="badge badge-success">有効</span>
                    <span th:if="${user.status == 'SUSPENDED'}" class="badge badge-warning">停止中</span>
                    <span th:if="${user.status == 'DELETED'}" class="badge badge-danger">削除済</span>
                </td>
                <td th:if="${#authorization.expression('hasAnyRole(''ADMIN'', ''MANAGER'')')}">
                    <!-- ユーザーステータスに応じた操作ボタン -->
                    <div class="btn-group">
                        <button th:if="${user.status != 'DELETED'}" 
                                th:onclick="'editUser(' + ${user.id} + ')'"
                                class="btn btn-sm btn-info">編集</button>
                        <button th:if="${user.status == 'ACTIVE'}" 
                                th:onclick="'suspendUser(' + ${user.id} + ')'"
                                class="btn btn-sm btn-warning">停止</button>
                        <button th:if="${user.status == 'SUSPENDED'}" 
                                th:onclick="'reactivateUser(' + ${user.id} + ')'"
                                class="btn btn-sm btn-success">再開</button>
                    </div>
                </td>
            </tr>
            <!-- データが存在しない場合のメッセージ -->
            <tr th:if="${#lists.isEmpty(users)}">
                <td colspan="4" class="text-center">ユーザーが存在しません</td>
            </tr>
        </tbody>
    </table>
</div>

2. ユーザー詳細画面の実装

<!-- users/detail.html -->
<div class="user-detail">
    <!-- ユーザーが存在しない場合のエラー表示 -->
    <div th:if="${user == null}" class="alert alert-danger">
        指定されたユーザーは存在しません。
    </div>

    <!-- ユーザー情報の表示 -->
    <div th:if="${user != null}" class="user-info">
        <h2>ユーザー詳細情報</h2>

        <!-- プロフィール情報 -->
        <div class="profile-section">
            <div class="profile-header">
                <!-- プレミアムステータスバッジ -->
                <div th:if="${user.isPremium}" class="premium-badge">
                    <span class="badge badge-gold">Premium</span>
                    <small th:text="${'有効期限: ' + #dates.format(user.premiumExpireDate, 'yyyy/MM/dd')}">
                        2024/12/31
                    </small>
                </div>

                <!-- アカウントステータス -->
                <div th:switch="${user.status}" class="status-indicator">
                    <span th:case="'ACTIVE'" class="text-success">アクティブ</span>
                    <span th:case="'PENDING'" class="text-warning">認証待ち</span>
                    <span th:case="'LOCKED'" class="text-danger">ロック中</span>
                </div>
            </div>

            <!-- 管理者向け操作パネル -->
            <div th:if="${#authorization.expression('hasRole(''ADMIN'')')}" class="admin-panel">
                <div th:if="${user.status == 'LOCKED'}" class="alert alert-warning">
                    <p>アカウントがロックされています。</p>
                    <p th:text="${'ロック理由: ' + user.lockReason}">不正アクセス試行</p>
                    <button class="btn btn-success" th:onclick="'unlockAccount(' + ${user.id} + ')'">
                        ロック解除
                    </button>
                </div>

                <div th:if="${user.warningCount > 0}" class="alert alert-info">
                    <p th:text="${'警告回数: ' + user.warningCount + '回'}">警告回数: 2回</p>
                </div>
            </div>
        </div>
    </div>
</div>

商品一覧・詳細画面での実装例

1. 商品一覧画面

<!-- products/list.html -->
<div class="product-listing">
    <!-- 商品フィルターセクション -->
    <div class="filter-section" th:with="hasFilters=${param.category != null or param.price != null}">
        <div th:if="${hasFilters}" class="active-filters">
            <span>適用中のフィルター:</span>
            <span th:if="${param.category}" th:text="${param.category[0]}">カテゴリー</span>
            <span th:if="${param.price}" th:text="${'¥' + param.price[0] + '以下'}">価格</span>
            <a href="/products" class="clear-filters">クリア</a>
        </div>
    </div>

    <!-- 商品グリッド -->
    <div class="product-grid">
        <div th:each="product : ${products}" class="product-card">
            <!-- 在庫状況に応じたバッジ -->
            <div class="stock-status">
                <span th:if="${product.stock == 0}" class="badge badge-danger">在庫切れ</span>
                <span th:if="${product.stock > 0 and product.stock < 5}" class="badge badge-warning" 
                      th:text="${'残り' + product.stock + '個'}">残り3個</span>
            </div>

            <!-- セール商品の表示 -->
            <div th:if="${product.isOnSale}" class="sale-badge">
                <span th:text="${product.discountRate + '%OFF'}">30%OFF</span>
            </div>

            <!-- 商品情報 -->
            <img th:src="${product.imageUrl}" alt="商品画像" class="product-image">
            <div class="product-info">
                <h3 th:text="${product.name}">商品名</h3>
                <div class="price-section">
                    <span th:if="${product.isOnSale}" class="original-price" 
                          th:text="${'¥' + product.originalPrice}">¥1,000</span>
                    <span class="current-price" 
                          th:text="${'¥' + product.currentPrice}">¥700</span>
                </div>
            </div>
        </div>
    </div>
</div>

2. 商品詳細画面

<!-- products/detail.html -->
<div class="product-detail">
    <!-- 商品が存在しない場合 -->
    <div th:if="${product == null}" class="alert alert-danger">
        商品が見つかりませんでした。
    </div>

    <!-- 商品情報表示 -->
    <div th:if="${product != null}" class="product-container">
        <!-- 在庫状況による購入ボタンの制御 -->
        <div class="purchase-section">
            <div th:if="${product.stock > 0}">
                <button class="btn btn-primary" 
                        th:onclick="'addToCart(' + ${product.id} + ')'">
                    カートに追加
                </button>
                <!-- 残り僅かな場合の警告 -->
                <div th:if="${product.stock < 5}" class="stock-warning">
                    残り在庫僅か!
                </div>
            </div>
            <div th:unless="${product.stock > 0}" class="out-of-stock">
                <button class="btn btn-secondary" disabled>在庫切れ</button>
                <!-- 再入荷通知ボタン -->
                <button th:if="${user != null}" 
                        th:onclick="'notifyRestock(' + ${product.id} + ')'"
                        class="btn btn-outline-primary">
                    再入荷通知を受け取る
                </button>
            </div>
        </div>

        <!-- セール情報の表示 -->
        <div th:if="${product.isOnSale}" class="sale-info">
            <div class="countdown" th:with="remaining=${product.saleEndTime - currentTime}">
                <span>セール終了まで:</span>
                <span th:text="${remaining + '時間'}">24時間</span>
            </div>
        </div>
    </div>
</div>

フォーム入力画面での条件分岐活用法

1. ユーザー登録フォーム

<!-- users/register.html -->
<form th:action="@{/users/register}" method="post" class="registration-form">
    <!-- フォームの状態に応じたメッセージ -->
    <div th:if="${param.error}" class="alert alert-danger">
        入力内容にエラーがあります。
    </div>
    <div th:if="${param.emailTaken}" class="alert alert-warning">
        このメールアドレスは既に使用されています。
    </div>

    <!-- 入力フィールド -->
    <div class="form-group">
        <label for="userType">ユーザータイプ</label>
        <select id="userType" name="userType" class="form-control" 
                th:with="selectedType=${param.userType}">
            <option value="INDIVIDUAL">個人</option>
            <option value="CORPORATE">法人</option>
        </select>
    </div>

    <!-- 法人ユーザー向け追加フィールド -->
    <div th:if="${param.userType == 'CORPORATE'}" class="corporate-fields">
        <div class="form-group">
            <label for="companyName">会社名</label>
            <input type="text" id="companyName" name="companyName" 
                   class="form-control" required>
        </div>
        <div class="form-group">
            <label for="department">部署名</label>
            <input type="text" id="department" name="department" 
                   class="form-control">
        </div>
    </div>

    <!-- プレミアム会員オプション -->
    <div class="premium-option">
        <div class="form-check">
            <input type="checkbox" id="premium" name="premium" 
                   class="form-check-input">
            <label class="form-check-label" for="premium">
                プレミアム会員に登録する
            </label>
        </div>
        <!-- プレミアム特典の表示 -->
        <div th:if="${param.premium}" class="premium-benefits">
            <h4>プレミアム特典</h4>
            <ul>
                <li>配送料無料</li>
                <li>会員限定セール参加可能</li>
                <li>ポイント2倍</li>
            </ul>
        </div>
    </div>
</form>

これらの実装例は、実際のプロジェクトですぐに活用できる形で提供されています。条件分岐を効果的に使用することで、ユーザーエクスペリエンスを向上させ、より直感的なインターフェースを実現できます。

まとめ:Thymeleafのif条件分岐を使いこなすために

本記事のポイント整理

1. 基本的な使い方

  • th:if属性を使用した基本的な条件分岐
  • th:unlessによる否定条件の表現
  • th:switch/th:caseとの使い分け

2. 実践的なテクニック

  • 複数条件の組み合わせ方
  • Nullセーフな条件分岐の実装
  • Spring Securityとの連携による権限制御

3. パフォーマンスと保守性

<!-- パフォーマンスを考慮した実装例 -->
<div th:with="isAdmin=${#authorization.expression('hasRole(''ADMIN'')')}">
    <div th:if="${isAdmin}">
        <!-- 管理者向けコンテンツ -->
    </div>
</div>

実装時のベストプラクティス

  1. 可読性を重視した実装
    • 複雑な条件はヘルパーメソッドに切り出す
    • 意図が明確な命名を心がける
    • コメントで条件の意図を説明する
  2. 保守性の高いコード設計
    • ビジネスロジックはサービス層に分離
    • 共通の条件はユーティリティクラスに集約
    • テストコードでの検証を忘れずに
  3. セキュリティへの配慮
    • 権限チェックは必ず行う
    • センシティブな情報の表示制御
    • XSS対策の実施

こんなときはこう使う!実践ガイド

ユースケース推奨される実装方法
単純な真偽判定th:if="${flag}"
Null考慮が必要th:if="${object?.property}"
複数条件の組み合わせth:if="${condition1 and condition2}"
権限による表示制御th:if="${#authorization.expression('hasRole(...)')}"
否定条件th:unless="${condition}"

次のステップ

  1. スキルアップの方向性
    • Thymeleafの他の機能との組み合わせ
    • Spring SecurityのSpEL式の習得
    • パフォーマンスチューニングの実践
  2. 実践的な学習方法
    • 小規模なプロジェクトでの実装練習
    • 既存コードのリファクタリング
    • ユニットテストの作成
  3. よくある問題への対処
    • デバッグツールの活用
    • エラーログの解析
    • パフォーマンスモニタリング

最後に

Thymeleafの条件分岐機能は、適切に使用することで保守性が高く、セキュアで効率的なWebアプリケーションの実装を可能にします。本記事で紹介した基礎知識とベストプラクティスを活用し、実際のプロジェクトで実践してください。

困ったときは以下のリソースを参考にしてください:

  • Thymeleaf公式ドキュメント
  • Spring Framework公式ガイド
  • Stack Overflowでのディスカッション

また、実装時は必ずテストを作成し、セキュリティ面にも配慮することを忘れずに。チーム開発では、ここで紹介した命名規則やコーディング規約を共有し、一貫性のある実装を心がけましょう。