はじめに (対象読者・この記事でわかること)

この記事は、Liferay開発に携わっている方、Javaを用いてWebアプリケーションからデータベースに接続する方法を学びたい方を対象としています。特に、Liferayのポートレットから外部のデータベース(今回はローカルのMySQL)に直接接続し、そのテーブルデータをWeb画面に表示する方法について、具体的な手順を追って解説します。

この記事を読むことで、Liferayの標準的なService Builderを使用せず、既存のMySQLデータベースに対してJDBC接続を行い、その内容をポートレットとして出力する基本的な実装方法がわかるようになります。これにより、既存システムとの連携や、特定の用途に特化したデータ表示ポートレットの開発が可能になります。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Javaの基本的なプログラミング知識 - Liferay DXP/7.x以降でのポートレット開発の基本的な知識(Liferay WorkspaceやBlade CLIの利用経験) - SQLの基本的な知識とMySQLデータベースの操作経験 - MavenまたはGradleによる依存関係管理の基本的な知識

Liferayポートレットと外部データベース接続の基本

Liferayでは、Service Builderという強力なツールを提供しており、Liferay自身のデータベース(通常はバンドルされているH2やPostgreSQLなど)に対してエンティティを定義し、CRUD操作を行うためのサービスレイヤーを自動生成できます。これはLiferayのデータベース上に新しいデータモデルを構築する際に非常に便利です。

しかし、既存の外部データベース(例えば、会社の基幹システムが利用しているMySQLデータベース)に接続してデータを参照したい場合、Service Builderは必ずしも最適な選択肢ではありません。このようなケースでは、Service Builderの枠組みに縛られずに、Javaの標準的なJDBC(Java Database Connectivity)APIを利用して直接データベースに接続する方法が有効です。

この記事では、LiferayポートレットからJDBC経由でローカルのMySQLデータベースに接続し、そのテーブルの内容を動的に表示するアプローチを取ります。これにより、Service Builderに依存しない柔軟なデータベース連携が可能になります。

LiferayポートレットでローカルMySQLに接続し、テーブル要素を出力する手順

ここからは、LiferayポートレットからローカルMySQLデータベースに接続し、その内容をウェブページに出力する具体的な手順を解説します。

ステップ1: MySQLデータベースとテストデータの準備

まず、ローカル環境にMySQLデータベースを準備し、ポートレットで読み込むためのテストテーブルとデータをセットアップします。

  1. MySQLのインストールと起動: Dockerを利用すると手軽にMySQL環境を構築できます。以下のコマンドでMySQLコンテナを起動します。

    bash docker run --name local-mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=liferay_test_db -p 3306:3306 -d mysql:8.0 これで liferay_test_db というデータベースが作成され、rootユーザーのパスワードは password となります。

  2. テストテーブルとデータの作成: MySQLクライアント(mysql-client や DBeaver など)を使って liferay_test_db に接続し、以下のSQLを実行してテーブルを作成し、データを挿入します。

    ```sql -- データベースの選択 USE liferay_test_db;

    -- users テーブルの作成 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

    -- サンプルデータの挿入 INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com'); ```

ステップ2: Liferayポートレットプロジェクトの作成と依存関係の追加

次に、Blade CLIを使用してLiferayポートレットプロジェクトを作成し、MySQL JDBCドライバの依存関係を追加します。

  1. ポートレットプロジェクトの作成: Liferay Workspaceのmodulesディレクトリ内で、Blade CLIを使ってポートレットプロジェクトを作成します。

    bash cd [Liferay Workspace Root]/modules blade create -t mvc-portlet -p com.liferay.docs.mysql -c MySqlDisplayPortlet mysql-display-portlet これにより、mysql-display-portletという名前のMVCポートレットプロジェクトが作成されます。

  2. MySQL JDBCドライバの依存関係を追加: mysql-display-portlet/build.gradleファイルを開き、dependenciesブロックにMySQL JDBCドライバの依存関係を追加します。

    ```gradle dependencies { // ... 既存の依存関係

    // MySQL JDBC Driver
    compileOnly group: 'mysql', name: 'mysql-connector-java', version: '8.0.33'
    
    // ... その他の依存関係
    

    } ``compileOnlyを使用することで、ビルド時にドライバが解決され、ランタイム時にはLiferayのOSGi環境で提供される(またはバンドルされる)ようになります。 もしmysql-connector-javaがLiferayのOSGiバンドルリポジトリに存在しない場合や、より確実に組み込みたい場合はimplementationに変更することも検討してください。今回はcompileOnly`で進めます。

ステップ3: データベース接続処理の実装

ポートレット内でデータベースに接続し、データを取得するJavaコードを実装します。

  1. MySqlDisplayPortlet.javaの修正: src/main/java/com/liferay/docs/mysql/MySqlDisplayPortlet.javaファイルを開き、renderメソッド内でデータベースからデータを取得し、JSPに渡す処理を追加します。

    ```java package com.liferay.docs.mysql;

    import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet; import com.liferay.portal.kernel.util.ParamUtil;

    import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List;

    import javax.portlet.Portlet; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse;

    import org.osgi.service.component.annotations.Component;

    /* * @author Kousukei / @Component( immediate = true, property = { "com.liferay.portlet.display-category=category.sample", "com.liferay.portlet.header-portlet-css=/css/main.css", "com.liferay.portlet.instanceable=true", "javax.portlet.display-name=MySQL Display Portlet", "javax.portlet.init-param.template-path=/", "javax.portlet.init-param.view-template=/view.jsp", "javax.portlet.name=com_liferay_docs_mysql_MySqlDisplayPortlet", "javax.portlet.resource-bundle=content.Language", "javax.portlet.security-role-ref=power-user,user" }, service = Portlet.class ) public class MySqlDisplayPortlet extends MVCPortlet {

    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/liferay_test_db";
    private static final String DB_USER = "root";
    private static final String DB_PASSWORD = "password";
    
    @Override
    public void render(RenderRequest renderRequest, RenderResponse renderResponse)
            throws IOException, PortletException {
    
        List<User> userList = new ArrayList<>();
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
    
        try {
            // JDBCドライバのロード (Java 6以降では不要な場合が多いが明示的に記述)
            Class.forName("com.mysql.cj.jdbc.Driver");
    
            // データベース接続の確立
            connection = DriverManager.getConnection(JDBC_URL, DB_USER, DB_PASSWORD);
    
            // SQLクエリの実行
            String sql = "SELECT id, name, email FROM users";
            statement = connection.createStatement();
            resultSet = statement.executeQuery(sql);
    
            // 結果セットの処理
            while (resultSet.next()) {
                long id = resultSet.getLong("id");
                String name = resultSet.getString("name");
                String email = resultSet.getString("email");
                userList.add(new User(id, name, email));
            }
        } catch (ClassNotFoundException e) {
            _log.error("MySQL JDBC Driver not found.", e);
            renderRequest.setAttribute("errorMessage", "データベースドライバが見つかりません。");
        } catch (SQLException e) {
            _log.error("Database connection error.", e);
            renderRequest.setAttribute("errorMessage", "データベース接続中にエラーが発生しました: " + e.getMessage());
        } finally {
            // リソースの解放
            try {
                if (resultSet != null) resultSet.close();
                if (statement != null) statement.close();
                if (connection != null) connection.close();
            } catch (SQLException e) {
                _log.error("Failed to close database resources.", e);
            }
        }
    
        renderRequest.setAttribute("userList", userList);
        renderRequest.setAttribute("testMessage", "MySQL接続テスト");
    
        super.render(renderRequest, renderResponse);
    }
    
    private static class User {
        long id;
        String name;
        String email;
    
        public User(long id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }
    
        public long getId() { return id; }
        public String getName() { return name; }
        public String getEmail() { return email; }
    }
    
    private static final org.slf4j.Logger _log = org.slf4j.LoggerFactory.getLogger(MySqlDisplayPortlet.class);
    

    } `` **注意:** ログ出力のためにSLF4Jを使用しています。Liferayのデフォルトロギングシステム(Log4j2)にマッピングされますが、build.gradleに明示的にSLF4Jの依存関係がない場合、コンパイルエラーになることがあります。Liferay DXP/7.xでは通常、SLF4J APIが提供されているので問題ないことが多いですが、もしエラーが出る場合はimplementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'`などを追加してください。

  2. Userクラスの追加: 上記コードで使用しているUserクラスを、MySqlDisplayPortlet.javaの内部クラスとして定義しました。必要であれば、別のファイルとしてcom.liferay.docs.mysql.model.Userとして作成しても良いでしょう。

  3. view.jspの修正: src/main/resources/META-INF/resources/view.jspファイルを開き、データベースから取得したデータを表示するためのJSPコードを記述します。

    ```jsp <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %> <%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %><%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %><%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>

    <%= renderRequest.getAttribute("testMessage") %>

    エラー: ${errorMessage}

    Users from MySQL

    ID Name Email
    ${user.id} ${user.name} ${user.email}

    表示するユーザーがいません。

    ```

ステップ4: ポートレットのデプロイと動作確認

プロジェクトをビルドし、Liferayサーバーにデプロイして動作を確認します。

  1. ポートレットのビルド: Liferay Workspaceのルートディレクトリで以下のコマンドを実行します。

    bash blade deploy または、mysql-display-portletディレクトリ内で

    bash ../gradlew deploy これにより、ポートレットのOSGiバンドルがビルドされ、Liferayサーバーのdeployフォルダに配置されます。Liferayが自動的にポートレットを認識し、デプロイします。

  2. Liferayへの配置と動作確認: Liferay Portalの管理画面にログインし、ページに作成したポートレットを追加します。 「アプリケーション」メニューから「Sample」カテゴリを探し、「MySQL Display Portlet」をページにドラッグ&ドロップします。 正しく設定されていれば、MySQLデータベースから取得したユーザーデータがテーブル形式で表示されるはずです。

ハマった点やエラー解決

Liferay環境でのデータベース接続は、一般的なJavaアプリケーションと比べていくつかの注意点があります。

  1. ClassNotFoundException: com.mysql.cj.jdbc.Driver:

    • 原因: MySQL JDBCドライバがポートレットのクラスパス上に存在しないか、OSGiコンテナがドライバをロードできていない。
    • 解決策:
      • build.gradle(またはpom.xml)にmysql-connector-javaの依存関係が正しく追加されているか確認する。
      • compileOnlyではなくimplementationを使用してみる。implementationを使用すると、ドライバがポートレットのバンドル内に含まれてデプロイされるため、クラスローダーの問題が解決されやすい。
      • LiferayのOSGiコンソール(telnet localhost 11311などでアクセス)でlb | grep mysqlを実行し、MySQLドライババンドルがアクティブになっているか確認する。もしLiferay自体がドライバをOSGiバンドルとして提供していない場合、別途OSGiバンドル化されたMySQLドライバをデプロイする必要があるケースもある。しかし、Liferay DXP/7.x以降でBlade CLIで生成したポートレットの場合、implementation依存であれば多くの場合問題なく動作するはず。
  2. SQLException: Access denied for user 'root'@'localhost' または Communication link failure:

    • 原因: MySQLのユーザー名、パスワード、ホスト名、ポート番号、データベース名が間違っているか、MySQLサーバーが起動していない、あるいはファイアウォールによって接続がブロックされている。
    • 解決策:
      • JDBC_URL, DB_USER, DB_PASSWORD の値が正しいか再確認する。
      • MySQLサーバー(Dockerコンテナなど)が起動しているか確認する。docker ps で確認。
      • MySQLのrootユーザーに外部からの接続権限がない場合がある。ローカル環境であれば通常は問題ないが、厳格な設定のMySQLを使用している場合はユーザーの権限を確認・変更する。
      • ファイアウォールが3306ポートをブロックしていないか確認する。
  3. Liferayのログにエラーが出力されない:

    • 原因: ロギングフレームワークの設定が不適切か、ログレベルが低く設定されている。
    • 解決策: Liferayのログレベルを調整する。Control Panel > Configuration > Server Administration > Log Levels で、com.liferay.docs.mysql パッケージのログレベルを DEBUG または INFO に設定する。

解決策

これらの問題は、多くの場合、以下の確認と対応で解決できます。

  • 依存関係の確認: build.gradle(またはpom.xml)のmysql-connector-javaのバージョンがLiferay環境と互換性があるか、implementationスコープで確実にバンドルされているかを確認します。
  • 接続情報の正確性: JDBC_URL, DB_USER, DB_PASSWORDがMySQLサーバーの設定と完全に一致していることを確認します。MySQLのCLIツールや別のJavaアプリケーションで同じ接続情報を使って接続できるかテストするのも有効です。
  • MySQLサーバーの状態: Dockerであればdocker psでコンテナがUp状態であることを確認し、必要であればdocker logs local-mysqlでログを確認します。
  • リソース解放の徹底: finallyブロックでのConnection, Statement, ResultSetのクローズを怠ると、リソースリークやパフォーマンス低下の原因となります。try-with-resources文を使うと、これらのリソースが自動的にクローズされるため、より安全です。
Java
// try-with-resources を使用した例 try (Connection connection = DriverManager.getConnection(JDBC_URL, DB_USER, DB_PASSWORD); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { while (resultSet.next()) { // ... データ処理 } } catch (SQLException e) { _log.error("Database error.", e); renderRequest.setAttribute("errorMessage", "データベース処理中にエラーが発生しました: " + e.getMessage()); }

ただし、Class.forName("com.mysql.cj.jdbc.Driver");Connectionの外で一度だけ実行する必要があるため、この例ではClass.forNametryブロックの外か、staticイニシャライザに記述します。

まとめ

本記事では、LiferayポートレットからローカルのMySQLデータベースに直接JDBC接続し、そのテーブルの要素をWebページに出力する手順を解説しました。

  • MySQLデータベースの準備: Dockerを使用して手軽にMySQL環境を構築し、テストデータを投入しました。
  • ポートレットの作成と依存関係: Blade CLIでポートレットプロジェクトを作成し、build.gradleにMySQL JDBCドライバの依存関係を追加しました。
  • JDBC接続の実装: MVCPortletrenderメソッド内で、DriverManagerを使用してデータベースに接続し、SQLクエリを実行してデータを取得するJavaコードを実装しました。
  • データ表示: 取得したデータをJSPページでテーブル形式で表示する例を示しました。

この記事を通して、LiferayのService Builderに依存せず、既存の外部データベースと柔軟に連携するための基本的な技術を習得できたことでしょう。これにより、Liferay環境を既存のシステムと統合したり、特定の要件に合わせたカスタムデータ表示ポートレットを開発したりする際の選択肢が広がります。

今後は、データベース接続情報の外部化(例:portal-ext.propertiesやOSGi Config Adminによる管理)、JNDIデータソースの利用、プリペアドステートメントによるSQLインジェクション対策など、よりセキュアでメンテナンス性の高い実装方法についても記事にする予定です。

参考資料