業務屋だってことりんを愛でたい
先日、日本ことりんユーザー会(自称)の勉強会に参加してきました。
モバイル系開発者さんが多く、話題もAndroidの中で動くことりんかわいい!ってな感じで業務屋さんのぼくは完全アウェーでしたが、業務屋さんだってことりんかわいいしたいのです。
というわけでJavaFX-JPA のスタンドアロンアプリを作ってみました。
青写真
超高機能なメニューエディタを作ります。
メニューと値段の一覧を見ることができ、さらに追加、削除までできてしまいます。
まずはビルドの準備
ビルドはgradleで行います。
build.gradleにことりんの動く準備をします
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.8.11'
}
}
apply plugin: "kotlin"
repositories {
mavenCentral()
}
dependencies {
compile 'org.jetbrains.kotlin:kotlin-stdlib:0.8.11'
compile 'org.apache.openjpa:openjpa-all:2.3.0'
compile 'com.h2database:h2:1.4.179'
}
今回作るアプリはことりんがJPAを使ってH2からデータを読み書きします。
次にディレクトリたちを用意していきます。GradleでKotlinを動かすためには、ソースのルートディレクトリが「kotlin」である必要があるようなので、こんな感じの構成にしました。
データベース関連のソースと設定をします。
今回はMenu
エンティティが1つだけあるアプリなので、エンティティクラスとDAOクラスを作ります。
package entity
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.NamedQuery
import javax.persistence.Table
[Entity]
[Table(name = "MENUS")]
[NamedQuery(name = "Menu.findAll", query = "SELECT menu FROM Menu menu")]
public class Menu() {
[Id]
public var id : Int = 0
public var name : String = ""
public var price : Int = 0
}
package entity
import javax.persistence.EntityManager
import javax.persistence.Persistence
class MenuDAO() {
val em :EntityManager? = Persistence.createEntityManagerFactory("kotlin")!!.createEntityManager()
fun findAllMenu(): List<Menu> {
return em?.createNamedQuery("Menu.findAll", Menu().javaClass)?.getResultList() as List<Menu>
}
fun create(menu: Menu): Unit {
em?.getTransaction()?.begin()
em?.persist(menu)
em?.getTransaction()?.commit()
}
fun delete(menu: Menu?): Unit {
em?.getTransaction()?.begin()
em?.remove(menu)
em?.getTransaction()?.commit()
}
}
エンティティクラスを定義し、CRUDを担当するDAOクラスです。JPAを使ってDBとマッピングしています。
で、次にそのDB周りを作ります。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="kotlin" transaction-type="RESOURCE_LOCAL">
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
<class>entity.Menu</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.url"
value="jdbc:h2:file:./db/test.db;INIT=RUNSCRIPT FROM 'db/create.sql'\;RUNSCRIPT FROM 'db/init.sql'"/>
<property name="javax.persistence.jdbc.user" value=""/>
<property name="javax.persistence.jdbc.password" value=""/>
</properties>
</persistence-unit>
</persistence>
先ほど作ったMenuクラスをエンティティとして使うよ宣言と、データソースに関する設定を行っています。今回の設定ではH2DBはプロジェクトのdb
ディレクトリ以下に作られます。さらにURLの指定で起動時にdb/create.sql
とdb/init.sql
を読み込むようにしています。
DROP TABLE IF EXISTS MENUS;
CREATE TABLE MENUS (
ID INTEGER NOT NULL AUTO_INCREMENT
, NAME VARCHAR2(20)
, PRICE NUMBER
, PRIMARY KEY (ID)
);
INSERT INTO MENUS (
ID,
NAME,
PRICE)
SELECT
ID,
NAME,
PRICE
FROM
CSVREAD('./db/MENUS.csv', NULL, 'UTF-8');
init.sql
ではdb/MENUS.csv
を初期データとして読み込ませるようにしています。
ID,NAME,PRICE
1,牛丼,280
2,牛乳,100
3,ハラミ丼,400
テスト
ここまででCSVで作ったデータがH2にロードされてJPA経由でことりんが読み出してくれるところまで完成したので、動作確認をします。
以下のようなテストコードを作成して実行します。
import entity.MenuDAO
fun main(args:Array<String>) {
MenuDAO().findAllMenu().forEach {
menu -> println("${menu.name} --- \\${menu.price}")
}
}
ことりんのエントリポイントのシグニチャはfun main(args:Array<String>)
です。で、先ほど作ったDAO経由から全Menuを取得して、それを順次出力します。
ことりんではスクリプト系言語のように変数を文字列中に展開してくれるのでJavaのString.format("%s", str)
よりもお手軽に文字列が組み立てられます。
出力結果は以下の通りです。
(前略)
牛丼 --- \280
牛乳 --- \100
ハラミ丼 --- \400
Process finished with exit code 0
無事DBと疎通がとれました。
GUI
GUIにはJavaFXを使います。
JavaFXはSwingに替わる新しいGUIライブラリで、リッチクライアントを簡単に作れます。個人的には自由なレイアウトマネージャとコントローラーの強制分離が気に入っています。
まず、画面をデザインします。画面デザインはfxmlファイルというxmlファイルで行いますが、OracleがJavaFX Scene Builderなるものを配布しており、VB感覚でGUIを作り込んでいけます。
ここではMain.fxml
という名前で画面を作ります。
Swingでレイアウトマネージャと戦ってた時代はなんだったんだと思うくらい楽ちんになりました。
出力されたfxmlは以下の通りです。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.Pane?>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="414.0"
prefWidth="396.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="gui.MenuController">
<children>
<TextField fx:id="menuText" layoutX="14.0" layoutY="14.0" prefHeight="31.0" prefWidth="151.0"/>
<TextField fx:id="priceText" layoutX="171.0" layoutY="14.0" prefHeight="31.0" prefWidth="151.0"/>
<Button layoutX="328.0" layoutY="14.0" mnemonicParsing="false" onAction="#add" text="追加"/>
<TableView fx:id="menuTable" layoutX="14.0" layoutY="57.0" prefHeight="308.0" prefWidth="364.0">
<columns>
<TableColumn fx:id="menuCol" prefWidth="251.0" text="メニュー"/>
<TableColumn fx:id="priceCol" prefWidth="102.0" text="お値段"/>
</columns>
</TableView>
<Button layoutX="328.0" layoutY="373.0" mnemonicParsing="false" onAction="#delete" text="削除"/>
</children>
</Pane>
これに対するコントローラークラスを作ります。
package gui
import javafx.fxml.FXML
import javafx.scene.control.TextField
import javafx.scene.control.TableView
import javafx.scene.control.TableColumn
import javafx.scene.control.cell.PropertyValueFactory
import javafx.collections.FXCollections
import entity.Menu
import entity.MenuDAO
class MenuController() {
val dao: MenuDAO = MenuDAO()
[FXML] var menuText: TextField? = null
[FXML] var priceText: TextField? = null
[FXML] var menuTable: TableView<Menu>? = null
[FXML] var menuCol: TableColumn<Menu, String>? = null
[FXML] var priceCol: TableColumn<Menu, Int>? = null
[FXML] fun initialize(): Unit {
menuCol?.setProperty("name")
priceCol?.setProperty("price")
refresh()
}
fun refresh() {
val menus = FXCollections.observableList(dao.findAllMenu())
menuTable?.setItems(menus)
}
fun add(): Unit {
val menu: Menu = Menu()
menu.name = menuText?.getText() as String
menu.price = priceText?.getText()?.toInt() as Int
dao.create(menu)
refresh()
}
fun delete(): Unit {
val menu: Menu? = menuTable?.getSelectionModel()?.getSelectedItem()
dao.delete(menu)
refresh()
}
fun <T, S> TableColumn<S, T>.setProperty(property: String) {
setCellValueFactory(PropertyValueFactory<S, T>(property))
}
}
JavaFXのコントローラーはAspxのコードビハインドのように動作します。swingではごちゃごちゃにならざるを得なかったイベントハンドラを完全にびゅーから独立させて書く事ができます。
このコントローラーでは、初期表示時にメニューを表示し、追加ボタンでデータ追加、削除ボタンで削除をしています。それぞれ、先に作成したDAOを呼び出しているだけです。
最後にFXMLファイルから画面を起動させて動作確認をします。
package gui
import entity.MenuDAO
import javafx.application.Application
import javafx.stage.Stage
import javafx.fxml.FXMLLoader
import javafx.scene.Scene
import javafx.scene.Parent
fun main(args : Array<String>) {
val dao : MenuDAO = MenuDAO()
dao.findAllMenu()?.forEach { menu -> println(menu?.name) }
Application.launch(MenuView().javaClass)
}
class MenuView() : Application() {
override fun start(stage: Stage?) {
val parent: Parent? = FXMLLoader.load(getClass().getResource("/Main.fxml"))
stage?.setTitle("メニュー")
stage?.setScene(Scene(parent))
stage?.show()
}
}
mainがfxmlを読み出し、FXMLローダーという人がコントローラーをインスタンス化してくれます。
これを実行すると青写真で紹介したような画面が起動するはずなので、思う存分データの追加、削除ができます。
ちなみに、この説明ではH2の設定で毎起動時にDBが初期かされてしまうので、動作確認をとるときはMETA-INF/persistence.xml
のurlを<property name="javax.persistence.jdbc.url" value="jdbc:h2:file:./db/test.db"/>
と書き換えてください。
ことりん
今回はことりんからもちゃんとDBアクセスができてGUIが作れるという確認がメインで、とくにことりんであることのメリットをいかせていません。それどころかnull安全の副作用でそこらかしこに?
がついてしまいました。これは既存のJava部分を呼び出すには仕方ないのでしょうか?まだまだことりんを触り始めたばかりなのでこの辺りはまだまだよくわかりません。
ただ自身でやってる業務で使えることはわかったので、バックオフィスで使う便利ツールなどをことりんで作ってみて、じわじわと征服していこうかと野望を抱いています。
さいごに
今回の成果物はhttps://github.com/pepepe/kotlinにあげてあるので、興味があればのぞいてみてください。