1. 序章

JavaLiteは、アプリケーションを構築するときにすべての開発者が処理しなければならない一般的なタスクを簡素化するためのフレームワークのコレクションです。

このチュートリアルでは、単純なAPIの構築に焦点を当てたJavaLiteの機能を見ていきます。

2. 設定

このチュートリアル全体を通して、単純なRESTfulCRUDアプリケーションを作成します。 そのために、はActiveWebとActiveJDBC を使用します。これは、JavaLiteが統合する2つのフレームワークです。

それでは、始めて、必要な最初の依存関係を追加しましょう。

<dependency>
    <groupId>org.javalite</groupId>
    <artifactId>activeweb</artifactId>
    <version>1.15</version>
</dependency>

ActiveWebアーティファクトにはActiveJDBCが含まれているため、個別に追加する必要はありません。 最新のactivewebバージョンはMavenCentralにあります。

必要な2番目の依存関係は、データベースコネクタです。 この例では、MySQLを使用するため、次を追加する必要があります。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.45</version>
</dependency>

繰り返しになりますが、最新の mysql-connector-java 依存関係は、MavenCentralにあります。

追加する必要がある最後の依存関係は、JavaLiteに固有のものです。

<plugin>
    <groupId>org.javalite</groupId>
    <artifactId>activejdbc-instrumentation</artifactId>
    <version>1.4.13</version>
    <executions>
        <execution>
            <phase>process-classes</phase>
            <goals>
                <goal>instrument</goal>
            </goals>
        </execution>
    </executions>
</plugin>

最新のactivejdbc-instrumentationプラグインは、MavenCentralにもあります。

これらすべてを整え、エンティティ、テーブル、マッピングを開始する前に、サポートされているデータベースの1つが稼働していることを確認します。 前に述べたように、MySQLを使用します。

これで、オブジェクトリレーショナルマッピングを開始する準備が整いました。

3. オブジェクトリレーショナルマッピング

3.1. マッピングとインストルメンテーション

メインエンティティとなるProductクラスの作成から始めましょう。

public class Product {}

また、それに対応するテーブルを作成しましょう

CREATE TABLE PRODUCTS (
    id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
    name VARCHAR(128)
);

最後に、 Productクラスを変更して、マッピングを実行できます。

public class Product extends Model {}

org.javalite.activejdbc.Modelクラスを拡張するだけで済みます。 ActiveJDBCは、データベースからDBスキーマパラメーターを推測します。 この機能のおかげで、ゲッターやセッター、または注釈を追加する必要はありません。

さらに、ActiveJDBCは、ProductクラスをPRODUCTSテーブルにマップする必要があることを自動的に認識します。 これは、英語の語尾変化を利用して、モデルの単数形をテーブルの複数形に変換します。 そして、はい、それは例外でも機能します。

マッピングを機能させるために必要な最後の1つがあります。インストルメンテーション。インストルメンテーションは、ActiveJDBC に必要な追加の手順であり、Productクラスをあたかも持っているかのように操作できます。ゲッター、セッター、およびDAOのようなメソッド。

インストルメンテーションを実行すると、次のようなことができるようになります。

Product p = new Product();
p.set("name","Bread");
p.saveIt();

また:

List<Product> products = Product.findAll();

これがactivejdbc-instrumentationプラグインの出番です。 pomにはすでに依存関係があるため、ビルド中にクラスがインストルメントされていることがわかります。

...
[INFO] --- activejdbc-instrumentation:1.4.11:instrument (default) @ javalite ---
**************************** START INSTRUMENTATION ****************************
Directory: ...\tutorials\java-lite\target\classes
Instrumented class: .../tutorials/java-lite/target/classes/app/models/Product.class
**************************** END INSTRUMENTATION ****************************
...

次に、これが機能していることを確認するための簡単なテストを作成します。

3.2. テスト

最後に、マッピングをテストするために、次の3つの簡単な手順に従います。データベースへの接続を開き、新しい製品を保存して取得します。

@Test
public void givenSavedProduct_WhenFindFirst_ThenSavedProductIsReturned() {
    
    Base.open(
      "com.mysql.jdbc.Driver",
      "jdbc:mysql://localhost/dbname",
      "user",
      "password");

    Product toSaveProduct = new Product();
    toSaveProduct.set("name", "Bread");
    toSaveProduct.saveIt();

    Product savedProduct = Product.findFirst("name = ?", "Bread");

    assertEquals(
      toSaveProduct.get("name"), 
      savedProduct.get("name"));
}

空のモデルとインストルメンテーションを用意するだけで、これらすべて(およびそれ以上)が可能になることに注意してください。

4. コントローラー

マッピングの準備ができたので、アプリケーションとそのCRUDメソッドについて考え始めることができます。

そのために、HTTPリクエストを処理するコントローラーを利用します。

ProductsControllerを作成しましょう。

@RESTful
public class ProductsController extends AppController {

    public void index() {
        // ...
    }

}

この実装により、ActiveWebは index()メソッドを次のURIに自動的にマップします。

http://<host>:<port>/products

で注釈が付けられたコントローラー @RESTful 異なるURIに自動的にマップされるメソッドの固定セットを提供します。 CRUDの例に役立つものを見てみましょう。

コントローラ方式 HTTPメソッド URI
作成 作成() 役職 http:// host:port / products
1つ読む 見せる() 得る http:// host:port / products / {id}
すべて読む 索引() 得る http:// host:port / products
アップデート アップデート() 置く http:// host:port / products / {id}
消去 破壊する() 消去 http:// host:port / products / {id}

そして、この一連のメソッドを ProductsController に追加すると、次のようになります。

@RESTful
public class ProductsController extends AppController {

    public void index() {
        // code to get all products
    }

    public void create() {
        // code to create a new product
    }

    public void update() {
        // code to update an existing product
    }

    public void show() {
        // code to find one product
    }

    public void destroy() {
        // code to remove an existing product 
    }
}

ロジックの実装に移る前に、構成する必要のあるいくつかのことを簡単に見ていきます。

5. 構成

ActiveWebは主に規則に基づいており、プロジェクト構造はその一例です。 ActiveWebプロジェクトは、事前定義されたパッケージレイアウトに従う必要があります

src
 |----main
       |----java.app
       |     |----config
       |     |----controllers
       |     |----models
       |----resources
       |----webapp
             |----WEB-INF
             |----views

確認する必要のある特定のパッケージが1つあります-app.config

そのパッケージ内に、次の3つのクラスを作成します。

public class DbConfig extends AbstractDBConfig {
    @Override
    public void init(AppContext appContext) {
        this.configFile("/database.properties");
    }
}

このクラスは、必要なパラメーターを含むプロジェクトのルートディレクトリにあるプロパティファイルを使用して、データベース接続を構成します。

development.driver=com.mysql.jdbc.Driver
development.username=user
development.password=password
development.url=jdbc:mysql://localhost/dbname

これにより、マッピングテストの最初の行で行ったものが自動的に置き換えられて接続が作成されます。

app.configパッケージ内に含める必要がある2番目のクラスは次のとおりです。

public class AppControllerConfig extends AbstractControllerConfig {
 
    @Override
    public void init(AppContext appContext) {
        add(new DBConnectionFilter()).to(ProductsController.class);
    }
}

このコード 構成したばかりの接続をコントローラーにバインドします。

3番目のクラスアプリのコンテキストを構成します

public class AppBootstrap extends Bootstrap {
    public void init(AppContext context) {}
}

3つのクラスを作成した後、構成に関する最後のことは、[X116X] webapp /WEB-INFディレクトリの下にweb.xmlファイルを作成することです。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns=...>

    <filter>
        <filter-name>dispatcher</filter-name>
        <filter-class>org.javalite.activeweb.RequestDispatcher</filter-class>
        <init-param>
            <param-name>exclusions</param-name>
            <param-value>css,images,js,ico</param-value>
        </init-param>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>dispatcher</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

構成が完了したので、先に進んでロジックを追加できます。

6. CRUDロジックの実装

製品クラスが提供するDAOのような機能を使用すると、基本的なCRUD機能を非常に簡単に追加できます。

@RESTful
public class ProductsController extends AppController {

    private ObjectMapper mapper = new ObjectMapper();    

    public void index() {
        List<Product> products = Product.findAll();
        // ...
    }

    public void create() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        Product p = new Product();
        p.fromMap(payload);
        p.saveIt();
        // ...
    }

    public void update() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        String id = getId();
        Product p = Product.findById(id);
        p.fromMap(payload);
        p.saveIt();
        // ...
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        // ...
    }

    public void destroy() {
        String id = getId();
        Product p = Product.findById(id);
        p.delete();
        // ...
    }
}

簡単ですよね? ただし、これはまだ何も返していません。 そのためには、いくつかのビューを作成する必要があります。

7. ビュー

ActiveWebはFreeMarkerをテンプレートエンジンとして使用し、そのすべてのテンプレートは src / main / webapp / WEB-INF /viewsの下に配置する必要があります。

そのディレクトリ内で、ビューを products (コントローラーと同じ)というフォルダーに配置します。 _product.ftlという最初のテンプレートを作成しましょう。

{
    "id" : ${product.id},
    "name" : "${product.name}"
}

この時点で、これがJSON応答であることは明らかです。 もちろん、これは1つの製品でのみ機能するため、先に進んでindex.ftlという別のテンプレートを作成しましょう。

[<@render partial="product" collection=products/>]

これにより、基本的に products という名前のコレクションがレンダリングされ、それぞれが_product.ftlでフォーマットされます。

最後に、コントローラーからの結果を対応するビューにバインドする必要があります。

@RESTful
public class ProductsController extends AppController {

    public void index() {
        List<Product> products = Product.findAll();
        view("products", products);
        render();
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        view("product", p);
        render("_product");
    }
}

最初のケースでは、 productslistをproductsという名前のテンプレートコレクションに割り当てています。

次に、ビューを指定していないため、index.ftlが使用されます。

2番目の方法では、ビューの要素productにproductp を割り当て、レンダリングするビューを明示的に指定します。

ビューmessage.ftlを作成することもできます。

{
    "message" : "${message}",
    "code" : ${code}
}

そして、それをProductsControllerのメソッドのいずれかから呼び出します。

view("message", "There was an error.", "code", 200);
render("message");

最後のProductsControllerを見てみましょう。

@RESTful
public class ProductsController extends AppController {

    private ObjectMapper mapper = new ObjectMapper();

    public void index() {
        view("products", Product.findAll());
        render().contentType("application/json");
    }

    public void create() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        Product p = new Product();
        p.fromMap(payload);
        p.saveIt();
        view("message", "Successfully saved product id " + p.get("id"), "code", 200);
        render("message");
    }

    public void update() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        p.fromMap(payload);
        p.saveIt();
        view("message", "Successfully updated product id " + id, "code", 200);
        render("message");
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        view("product", p);
        render("_product");
    }

    public void destroy() {
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        p.delete();
        view("message", "Successfully deleted product id " + id, "code", 200);
        render("message");
    }

    @Override
    protected String getContentType() {
        return "application/json";
    }

    @Override
    protected String getLayout() {
        return null;
    }
}

この時点で、アプリケーションが完成し、実行する準備が整いました。

8. アプリケーションの実行

Jettyプラグインを使用します。

<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.4.8.v20171121</version>
</plugin>

MavenCentralで最新のjetty-maven-pluginを検索します。

これで準備が整いました。アプリケーションを実行できます

mvn jetty:run

いくつかの製品を作成しましょう。

$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Water"}'
{
    "message" : "Successfully saved product id 1",
    "code" : 200
}
$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Bread"}'
{
    "message" : "Successfully saved product id 2",
    "code" : 200
}

.. それらを読みます:

$ curl -X GET http://localhost:8080/products
[
    {
        "id" : 1,
        "name" : "Water"
    },
    {
        "id" : 2,
        "name" : "Bread"
    }
]

.. それらの1つを更新します。

$ curl -X PUT http://localhost:8080/products/1 
  -H 'content-type: application/json' 
  -d '{"name":"Juice"}'
{
    "message" : "Successfully updated product id 1",
    "code" : 200
}

…更新したばかりのものを読んでください:

$ curl -X GET http://localhost:8080/products/1
{
    "id" : 1,
    "name" : "Juice"
}

最後に、1つを削除できます。

$ curl -X DELETE http://localhost:8080/products/2
{
    "message" : "Successfully deleted product id 2",
    "code" : 200
}

9. 結論

JavaLiteには、開発者がアプリケーションを数分で起動して実行するのに役立つツールがたくさんあります。 ただし、規則に基づいてコードを作成すると、コードがよりクリーンでシンプルになりますが、クラス、パッケージ、およびファイルの名前と場所を理解するには時間がかかります。

これはActiveWebとActiveJDBCの紹介にすぎず、 Webサイトで他のドキュメントを見つけ、Githubプロジェクトで製品アプリケーションを探してください。