読者です 読者をやめる 読者になる 読者になる

React.js で作る"Hello React" からのToDoアプリを作成する~その2: ToDoアプリ完成~

前回の記事で取りあえずは"Hello World!!"が表示された。これをベースにToDo アプリを作っていく。hirahiro56.hatenablog.com

下記のようなアプリイメージのものを開発していく。

f:id:hirahiro56:20150516135323j:plain


【Step.1】index.htmlの作成
まずは、index.htmlを準備する。これはHello World の時と何にも変わらない。

<html>
  <head>
  </head>
  <body>
    <div id="app-container"></div>
    <script src="dist/app.js"></script>
  </body>
</html>

【Step.2】TodoListコンポーネントの作成
アプリのルートとなるTodoListコンポーネントを作成する。このコンポーネントは全てのコンポーネントの親玉となるもので、このアプリにおいて状態を監視するべきデータである「残タスク一覧」(実体はオブジェクトのコレクション)を管理する。React.js では状態を管理する必要のあるデータ、つまり、更新されると画面を再描画する必要がるものは全て親玉(ルート)のコンポーネントで管理するように設計する。このような管理するべきデータをstete と呼ぶ。
コードは下記のようになり、app.js として保存する。

var React = require('react');
var Todo = require('./todo');
var InputApp = require('./inputapp');

var generateId = (
	function(){
		var id = 10;
		return function(){
			return (id++);
		}
	}
)();

var TodoList = React.createClass({
 //todos(ToDoリスト)を初期化
  getInitialState() {
    return {
      todos: [
        {id:1, text:"Sample1"},
        {id:2, text:"Sample2"},
        {id:3, text:"Sample3"}
      ]
    };
  },
  //引数のidに一致するデータをtodosから削除
  deleteTodo(id) {
    this.setState({
      todos: this.state.todos.filter((todo) => {
        return todo.id !== id;
      })
    });
  },
  //引数のToDoをToDoリスト(todos)にアペンドする。
  updateToDoList:function(todo){
    var tmptodos = this.state.todos.concat(todo);
    this.setState({todos: tmptodos});
  },
  render() {
    //ToDoリストのデータをMarkdown形式で書き換える。
    var todos = this.state.todos.map((todo) => {
      return <li key={todo.id}><Todo onDelete={this.deleteTodo} todo={todo} /></li>;
    });
    return (
      <div>
        <InputApp onSave={this.updateToDoList} id={generateId()} />
        <ul>{todos}</ul>
      </div>
    );
  }
});

React.render(
  <TodoList />, document.getElementById('app-container')
);

上記コードから、このコンポーネントは、Todo, およびInputAppという子コンポーネントを包括していることが分かると思う。
次のステップで、これらのコンポーネントを実装していくことになる。

【Step.3】InputAppコンポーネントの実装
InputAppコンポーネントの役割は下記2つとなる。
1.フォームの表示
2.フォームの内容(INPUT)を todos に追加する(ToDoリストに追加する)

ソースファイルをinputapp.js とし、下記の通り実装する。

var React = require('react');

var InputApp = React.createClass({
  _onKeyDown:function(e){
    if(e.keyCode === 13){
      var todo = {
        id: '',
        text: ''
      };
      todo.text = e.target.value;
      todo.id = this.props.id;
      this.props.onSave(todo);
      //this.props.onSave(e.target.value);
      e.target.value="";
    }
  },
  render: function(){
      return (
        <div>
          <h2>Input your ToDo</h2>
          <input type="text" onKeyDown={this._onKeyDown} />
        </div>
      );
  }
});

module.exports = InputApp;

【Step.4】ToDoコンポーネントの実装
ToDoコンポーネントの役割としては、下記2つとなる。
1.登録されたToDoの内容を表示する
2.delete ボタン押下時にToDoリスト(todos)から要素を削除する

todo.js として下記の通り実装するが、
削除処理について、実体は親コンポーネント(ToDoList)のdeleteTodoメソッドとなる。つまり、ToDoコンポーネントではToDoリスト(todos)の内容を更新するようなことはせず、親コンポーネントに処理を委譲する(呼び出し & 引数を与える)だけである。React.jsではアプリケーションの状態、所謂state(ToDoリストのtodos[])の管理は親玉(ルート)のコンポーネントに一括で請け負ってもらい、子コンポーネントはstateを持たないように設計することが定石となる。

var React = require('react');

var Todo = React.createClass({
  propTypes: {
    todo: React.PropTypes.shape({
      id: React.PropTypes.number.isRequired,
      text: React.PropTypes.string.isRequired
    }),
    // 削除するための処理をI/Fとして定義
    onDelete: React.PropTypes.func.isRequired
  },
  // 親に処理を委譲する
  _onDelete() {
    this.props.onDelete(this.props.todo.id);
  },
  render() {
    return (
      <div>
        <span>{this.props.todo.text}</span>
        <button onClick={this._onDelete}>delete</button>
      </div>
    );
  }
});

module.exports = Todo;

【Step.5】ビルド
最後にbrowserify/reactifyでビルドを実行するが、本ソースはES6, 7のシンタックスを利用しているため、reactifyのharmony オプションをつけてビルドする。

browserify -t [reactify --harmony ]  src/app.js -o dist/app.js
npm start

上記コマンド実行したら下記URLにアクセスして、ToDoアプリの挙動を確認できる。

http://localhost:8080/

React.js で作る"Hello React" からのToDoアプリを作成する~その1:取りあえず"Hello React"~

※本稿は開発環境にnpm が入っていることを前提とする。

【Step.1】下ごしらえ
任意のフォルダを作成し、その下にpackage.json を下記の内容で配置する。

{
  "name": "devtodo",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "start": "http-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "http-server": "^0.7.4"
  }
}

本ディレクトリ直下にsrcフォルダとdistフォルダも作成しておく。

【Step.2】必要なツールを色々とインストール(npmでサクッと)
上記フォルダの下において、npm innstall を実行し、ツール類(ReactとかBrowserifyとかHTTPサーバーとか)をインストールする。

npm install
npm install -g browserify
npm install react
npm install reactify

【Step.3】 Hello World!!の作成
PJフォルダ直下にindex.htmlを作成する。

<html>
  <head>
  </head>
  <body>
    <div id="app-container"></div>
    <script src="dist/todoapp.js"></script>
  </body>
</html>

【Step.4】 コンポーネントの作成
srcフォルダ以下にtodoapp.jsx.js というファイル名で、
TodoApp コンポーネントを下記の通り作成する。

var React = require('react');

var TodoApp = React.createClass({
    render: function(){
      return (
        <div>
          <h2>Hello React!!</h2>
        </div>
      );
    }
});

React.render(
  <TodoApp />,document.getElementById('app-container')
);

【Step.5】 ビルド by browserify
上記作成したら、PJフォルダ以下で下記のコマンドでビルドを実行し、
ビルド結果得られたjs をdistフォルダ以下に出力する。

browserify -t reactify src/todoapp.jsx.js -o dist/todoapp.js

【Step.6】 稼働確認
下記のコマンドでhttp-serverを起ち上げ、http://localhost:8080 へアクセスする。

npm start

Play Framework で作る簡易認証システム(その3: ユーザ情報の表示)

前回に引き続き簡易認証システムを作っていく。
現状のつぶやき機能では誰が投稿したのかどうかわからないし、現在入っているユーザが誰なのかも分からない。
そこで、ログインしているユーザ情報と投稿者の情報を画面に表示するようにしよう。
"/inputchat"のリクエストを処理するApplication.inputchat()メソッドを下記の通り変更する。
セッション情報から"username"の内容を取得して、それをView に渡す。

■Application.inputchat()メソッド

  @Security.Authenticated(models.Secured.class)
    public static Result inputchat() {
      	//フォームを定義
		Form<PostChatForm> postChatForm = new Form(PostChatForm.class);

		//grpchatテーブルの内容をselectし、Listに格納する。
		List<Grpchat> grpchatAllRec = Grpchat.find.all();

		//セッション情報"username"を取得
		String username = session().get("username");

            return ok(inputchat.render("InputChatForm",postChatForm, grpchatAllRec, username));
    }

続いてViewを下記の通り編集する。
・コントローラからusernameを受け取る。
・フォームにおいて、hiddenでユーザ名を持たす。

■inputchat.scala.html

@(message: String, postchatform: Form[forms.PostChatForm], grpchatList:List[Grpchat],username: String)

@import helper._

This resource is @message.

こんにちは♪ <br>
@username さん

@form(routes.Application.postchat()) {
	@inputText(postchatform("postMessage"))
	<input type="hidden" name="username" value= "@username" >
}

@for(chatrec <- grpchatList){
	@chatrec.message / @chatrec.postdate [@chatrec.username]<br>
}

最後に上記フォームが投稿されたときに呼び出されるApplication.postchat()を編集する。
変更内容としては、フォームのusernameフィールドの内容をController 側で取得して
Grpchat オブジェクトに格納している点である。

■Application.postchat()

    public static Result postchat(){
    	//フォームの内容を取得して、PostChatFormクラスのインスタンスに格納する。
    	Form<PostChatForm> postChatForm = new Form(PostChatForm.class).bindFromRequest();

    	//フォームの内容をpostMessageに代入する。
    	String postMessage = postChatForm.get().getPostMessage();
    	String postUsername = postChatForm.get().getUsername();

    	//Grpchat オブジェクトをインスタンス化
    	Grpchat grpchatRecd = new Grpchat();

    	//Grpchat オブジェクトのmessageフィールドにフォームで取得した内容をセットする
    	grpchatRecd.setMessage(postMessage);

    	//Grpchat オブジェクトのusernameフィールドにフォームで取得した内容をセットする
    	grpchatRecd.setUsername(postUsername);

    	//テーブルにデータを登録
    	grpchatRecd.save();

        //return ok(postchat.render(postMessage));
		return redirect("/inputchat");
    }

ここまでできたら、
http://localhost:9000/inputchat
にアクセスして、grpchat テーブルのusernameカラムに投稿者のアカウント名が登録され、
投稿者、およびログインしているユーザ名が表示されることを確認しよう。

Play Framework で作る簡易認証システム(その2: ログイン機能の実装)

前回から引き続き、簡易認証システムシステムを開発していく。
今回は登録したユーザ情報に基づいてログインできるような機能を開発していく。

1. ログイン画面表示用URL"/login"のリクエストを処理するController の
まずは、下記の通りroutesから登録しよう。

■routes

GET     /login                      controllers.Application.login()

次にコントローラ(Application.login)を実装していくが、その前にFormクラスを用意しよう。
ログイン画面ではアカウント名(username)とパスワード(password)を持つのでForm クラスは下記となる。
ここで、validate()メソッドが定義されているが、ユーザ/パスワードの組み合わせでDBを参照し、
正しいかどうかを判断してくれるようになる。
このクラスがFormのコンストラクタに渡されると、実際にフォームをPOSTする前に、
このvalidate()メソッドを実行し、ユーザ/パスワードの組み合わせが正しいかどうかを判断してくれるのである。

■forms/LoginForm.java

package forms;

import play.data.validation.*;
import play.db.ebean.*;
import java.security.NoSuchAlgorithmException;
import models.*;

public class LoginForm {

	   @Constraints.Required
	   private String username;
	   @Constraints.Required
	   private String password;

	    public String getUsername() {
	        return username;
	    }

	    public void setUsername(String username) {
	        this.username = username;
	    }

	    public String getPassword() {
	        return password;
	    }

	    public void setPassword(String password) {
	        this.password = password;
	    }

	  public String validate() throws NoSuchAlgorithmException{
		if(authenticate(username,password)==null){
			return "Invalid user or password";
		}
		return null;
	  }

	  public static User authenticate(String username, String password) throws java.security.NoSuchAlgorithmException{
		Model.Finder<Long, User> find = new Model.Finder<Long, User>(Long.class, User.class);
			return find.where().eq("username", username).eq("password", password).findUnique();
	  }

}

続いて、"/login"にアクセスした際に呼び出されるコントローラ(Application.login())は下記の通りになる。

■Application.login()

public static Result login(){
	Form<LoginForm> loginForm = new Form(LoginForm.class);
        return ok(login.render(loginForm));
}

2. ログイン画面用のView の実装
シンプルに下記の通り。

@(loginForm: Form[forms.LoginForm])

@import helper._

@form(routes.Application.authenticate) {
	@inputText(userForm("username"))
	@inputText(userForm("password"))
	<br>
	<button id="submit" type="submit" value="Submit" >Login</button>
}

3. セッション情報の付与
上記2.のフォームをPOSTした際に呼び出すコントローラ authenticate メソッドを下記の通り実装する。

■Application.authenticate()

    public static Result authenticate() {
        Form<LoginForm> loginForm = form(LoginForm.class).bindFromRequest();
        if (loginForm.hasErrors()) {
            return badRequest(login.render(loginForm));
        } else {
        	session().clear();
            session("username", loginForm.get().getUsername());
            String returnUrl = ctx().session().get("returnUrl");
            if(returnUrl == null || returnUrl.equals("") || returnUrl.equals(routes.Application.login().absoluteURL(request()))) {
                returnUrl = "/inputchat";
            }
            return redirect(returnUrl);
        }
    }

ここでのポイントは下記2行だけで、ここで、セッション"username" にフォームに入力した値(username)を格納している。つまり、セッション情報を搭載しているだけである。

session().clear();
session("username", loginForm.get().getUsername());

3. 認証ロジックの実装
次に認証の肝であるクラスを実装する。models以下に下記クラスを準備する。

■models/Secured.java

package models;

import play.mvc.Http.Context;
import play.mvc.Result;
import play.mvc.Security.Authenticator;

public class Secured extends Authenticator {
	@Override
	public String getUsername(Context ctx){
		return ctx.session().get("username");
	}

	@Override
	public Result onUnauthorized(Context ctx){
		String returnUrl = ctx.request().uri();
		if(returnUrl == null){
			returnUrl="/";
		}
		ctx.session().put("returnUrl", returnUrl);
		return redirect(controllers.routes.Application.login());
	}

}

このクラスはAuthenticator を継承したもので、getUsernameとonUnauthorized という
二つのメソッドをOveride する必要がある。
開発者は、getUsernameメソッドで認証OKとする条件を実装する必要がある。
ここでは、セッションから"username" の値が取得できたら認証OKとしている。
また、onUnauthorizedメソッドには認証NGだった場合の処理を実装するが、"/login"へリダイレクトされていることが分かる。

3. アノテーションの追加
最後に閲覧制限をかける画面を指定するためにアノテーションをControllerに設定する。
Application.inputchat()メソッドに"@Security.Authenticated(models.Secured.class)" アノテーションを追加すればよい。これで"/inputchat" にアクセスした時、セッション情報"username"が載っていないとはじかれるようになる。
つまり、Securedクラスでセッション情報"username"を取得できた場合にのみ、指定のリクエスト"/inputchat"を受け入れられるようになる。

■Application.java

@Security.Authenticated(models.Secured.class)
public static Result inputchat() {
     //中略
}

Play Framework で作る簡易認証システム(その1: アカウント登録)

今回はPlay Framework で簡易認証システムを作っていく。
ログインした人だけ、つまりアカウントを持つ人だけにチャットの内容を見せるような仕組みを作りたいわけである。
何はともあれ、アカウント情報を保持するテーブルが必要だ。

1. アカウント情報テーブルの作成
models 以下に下記の通り、User.javaを作成しよう。アカウント情報として、id(自動採番), username(アカウント名), mail(メールアドレス), image(画像), password(パスワード)を持つようにする。

package models;

import javax.persistence.*;

import play.db.ebean.*;
import play.data.validation.Constraints.*;

@Entity
public class User extends Model {
	@Id
	public Long id;

	@Required
	public String username;

	@Email
	public String mail;

	public String image;

	@Required
	public String password;

	public static Finder<Long, User> find =
			new Finder<Long, User>(Long.class, User.class);

	@Override
	public String toString(){
		return ("[id:" + id + ", username:" + username + ", mail:" + mail +
				", tel:" + tel + "]");
	}

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUserame(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    public String getImage() {
        return image;
    }
}

2. ユーザ情報登録機能画面の開発(View の作成)
ユーザ情報を登録する画面を作っていく。本画面にアクセスするパスは"/signup" とするにで、routesを下記の通りとする。

GET     /signup                     controllers.Application.signup()

続いて本URL"/signup"をリクエストされたときに呼び出されるコントローラsignupメソッドを実装する。

■Application.java

	public static Result signup(){
		//Userクラスの各フィールド(id, username, mail...)と同じ属性を持つFormオブジェクトを生成
		Form<User> userForm = new Form(User.class);
		
		return ok(signup.render("Sign up",userForm));
	}

ここで、ちょっとした小技を使っている。

		//Userクラスの各フィールド(id, username, mail...)と同じ属性を持つFormオブジェクトを生成
		Form<User> userForm = new Form(User.class);

userForm というFormクラスをインスタンス化したオブジェクトを作成するが、このときコンストラクタの引数として"User.class"を渡している。
どういう意味かというと、「User.classの持つフィールドを属性として持つForm オブジェクトを作成してね」という意味である。
以前フォームを利用したときに"PostChatForm"というフォームクラスを用意してから、
コントローラでフォームを作成したが、今回はフォームクラスを定義していない。
今回ユーザから求めるインプットは、アカウント名, メールアドレス, 画像, パスワードとなるが、これはUserクラスのフィールドと同じで、フォームの内容とテーブルの内容が1:1(※)で対応している。このようなとき、つまりフォームの内容をそのままテーブルに登録したいときにこの小技が使えるのだ。便利なので覚えておいて欲しいのだが、これはあくまでも小技なので、通常通りForm クラスを定義してから実装してもよい。
(※)厳密には1:1で対応しなくてもよいのだが、詳細は今後順次触れていくことにする。

return ok(signup.render("Sign up",userForm));
||>

続いて、上記メソッドで呼び出されるView の実装をしていく。

3. Viewの実装

■views/signup.scala.html
>|scala|
@(msg: String, userForm: Form[User])


@form(routes.Application.register, 'class -> "form-horizontal", 'enctype -> "multipart/form-data") {
	@inputText(form1("username"))
	@inputText(form1("mail"))
	@inputText(form1("password"))
    <button id="submit" type="submit" value="Submit" class="btn btn-primary">Sing up</button>
}

4. 登録用コントローラ(Application.render())の実装
上記フォームを投稿した際に呼びだされるApplication.render()メソッドを実装する。
まずは、忘れずにroutesに登録しておこう。

■routes

GET     /register                     controllers.Application.register()

続いてController(Application.register())を実装する。

■controllers/Application.java

	public static Result register(){

		//投稿されたフォームの内容をuserFormオブジェクトとしてインスタンス化
		Form<User> userForm = new Form(User.class).bindFromRequest();
		//もし、フォームの内容にエラーが無いようであれば
		if(!userForm.hasErrors()){
			//エンティティモデルであるUser オブジェクトにフォームの内容を登録する。
			User userRecd = userForm.get();
			//レコードの登録
			userRecd.save();
			return redirect("/inputchat");
		}else{
			return ok(signup.render("ERROR",userForm));
		}
	}


特に特異なことはしていない。
・素直にフォームから値を取得して、Formオブジェクト化

Form<User> userForm = new Form(User.class).bindFromRequest();

・Userオブジェクトをインスタス化してオブジェクトを保存する。

User userRecd = userForm.get();
//レコードの登録
userRecd.save();

これができたら、ここまでの動作を確認してみよう。ユーザ登録したあと、MySQLのWorkbench を起動してレコードが登録されているかどうか確認して欲しい。
http://localhost:9000/signup

Play Framework でつぶやきアプリを開発(その3: 超簡易つぶやきアプリを完成させよう)

今回は、超簡易つぶやきアプリを完成させてみよう。
まずは、前回の記事でテーブルに投入したレコードを/inputchatにアクセスしたときに表示できようにしたいと思う。

1. レコードを取得する。
コントローラのApplication.inputchat()メソッドを下記の通り変更する。

    public static Result inputchat() {

    	//フォームを定義
		Form<PostChatForm> postChatForm = new Form(PostChatForm.class);
		
		//grpchatテーブルの内容をselectし、Listに格納する。
		List<Grpchat> grpchatAllRec = Grpchat.find.all();
		
        return ok(inputchat.render("InputChatForm",postChatForm, grpchatAllRec));
    }

前回の記事で説明を割愛したが、Grpchatのfindフィールドはこのように使う。
このfind フィールドによりレコード(MySQL)の内容をList化(Java)することができるのである。

		//grpchatテーブルの内容をselectし、Listに格納する。
		List<Grpchat> grpchatAllRec = Grpchat.find.all();

そして、このList化したオブジェクトを下記でView側に渡す。

        return ok(inputchat.render("InputChatForm",postChatForm, grpchatAllRec));

2. View側で表示する。
View 側ではController側から渡されたリストを受けて(grpchatList)、それらをfor文で回す(@for(chatrec <- grpchatList){***})
ようにすればOKだ。

@(message: String, postchatform: Form[forms.PostChatForm], grpchatList:List[Grpchat])

@import helper._

This resource is @message.

@form(routes.Application.postchat()) {
	@inputText(postchatform("postMessage"))
}

@for(chatrec <- grpchatList){
	@chatrec.message / @chatrec.postdate<br>
}

最後にController側をちょっと弄って、投稿した後に/inputchat にリダイレクトされるようにしておく。

■Application.postchat()

    public static Result postchat(){
        //中略
        //return ok(postchat.render(postMessage));
	return redirect("/inputchat");
    }


ここまでできたら、http://localhost:9000/inputchatにアクセスして、動作を確認してみよう。
超簡易的なチャット機能ができたはずだ。

Play Framework でつぶやきアプリを開発(その2: テーブルにデータを投入しよう)

現状のアプリケーション(http://localhost:9000/inputchat)は、フォームに入力した内容を
単にecho(オウム返し)するだけの機能である。
このオウム返しの内容を先ほど作成したgrpchat テーブルに登録するように改修していこう。
オウム返しアプリがつぶやきアプリに昇華する第一歩だ。

まずは、入力画面(/inputchat)でからフォームを入力してEnter を押下した際に呼び出されるコントローラのメソッドApplication.postchat()を下記の通り改修する。


■Application.java

package controllers;

import play.*;
import play.mvc.*;
import play.mvc.Http.Context;
import models.*;

import views.html.*;
import play.mvc.Http.MultipartFormData;
import play.data.*;
import static play.data.Form.*;
import java.util.*;
import forms.*;

public class Application extends Controller {

    public static Result index() {
        return ok(index.render("Your new application is ready."));
    }

    public static Result inputchat() {

    	//フォームを定義
		Form<PostChatForm> postChatForm = new Form(PostChatForm.class);

        return ok(inputchat.render("InputChatForm",postChatForm));
    }

    public static Result postchat(){
    	//フォームの内容を取得して、PostChatFormクラスのインスタンスに格納する。
    	Form<PostChatForm> postChatForm = new Form(PostChatForm.class).bindFromRequest();

    	//フォームの内容をpostMessageに代入する。
    	String postMessage = postChatForm.get().getPostMessage();
    	
    	//Grpchat オブジェクトをインスタンス化
    	Grpchat grpchatRecd = new Grpchat(); 
    	
    	//Grpchat オブジェクトのmessageフィールドにフォームで取得した内容をセットする
    	grpchatRecd.setMessage(postMessage);
    	
    	//テーブルにデータを登録
    	grpchatRecd.save();
    	
    	
        return ok(postchat.render(postMessage));
    }


}

postchat()メソッドに下記3ステップを追加しただけである。

    	//Grpchat オブジェクトをインスタンス化
    	Grpchat grpchatRecd = new Grpchat(); 
    	
    	//Grpchat オブジェクトのmessageフィールドにフォームで取得した内容をセットする
    	grpchatRecd.setMessage(postMessage);
    	
    	//テーブルにデータを登録
    	grpchatRecd.save();

できたら、http://localhost:9000/inputchatにアクセスして、フォームを投稿してみよう。

投稿できたら、MySQLのWorkbenchを起動し、grpchat テーブルをselect してみよう。
すると、投稿した内容がレコードとして登録されているはずだ。
一点疑問に思ってほしいのだが、grpchatのidフィールドに数値がセットされていると思う。
値はmessage以外セットしていないのに、なぜだろう?

実はこれもEbean の機能の一つとなる。
Grpchat.javaを見てみると、idフィールドに@Idというアノテーションが付与されているのが分かる。
つまり、このアノテーションがあると、レコードが挿入される度に自動的に値を採番してくれるようになる、MySQLのAUTO_INCREMENTオプションが付与された形でCREATE文を発行してくれるのである。

このようにEbean の機能によりMySQLJava オブジェクトがシームレスに同期されていることが分かるだろうか。