SQLite (SQLite3) で JPA
JPA (Java Persistence API) でも SQLite を使いたい!ということで試してみる。いちいちデータベースサーバとか用意したり起動したり面倒くさいし。
$ java -version Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8 java version "1.6.0_20" Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065) Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01-279, mixed mode) $ mvn -v Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8 Apache Maven 2.2.0 (r788681; 2009-06-26 22:04:01+0900) Java version: 1.6.0_20 Java home: /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home Default locale: ja_JP, platform encoding: UTF-8 OS name: "mac os x" version: "10.6.4" arch: "x86_64" Family: "mac"
まず pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.jpa</groupId> <artifactId>hibernate</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Hibernate JPA Example</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <inherited>true</inherited> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <downloadJavadocs>true</downloadJavadocs> <downloadSources>true</downloadSources> </configuration> </plugin> </plugins> </build> <dependencies> <!-- SQLite3のJDBCドライバ。Central Repository での最新版 3.6.20 だと不具合あり --> <!-- see: http://code.google.com/p/xerial/issues/detail?id=54 --> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.6.20.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.5.3-Final</version> <scope>compile</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>0.9.24</version> <type>jar</type> <scope>compile</scope> </dependency> <!-- Hibernateから自動解決されるバージョンだと上の logback で問題があるので指定 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> <type>jar</type> <scope>compile</scope> </dependency> </dependencies> <repositories> <repository> <id>Xerial.org</id> <name>Xerial.org Repo</name> <url>http://www.xerial.org/maven/repository/artifact/</url> </repository> <repository> <id>JBoss.org</id> <name>JBoss.org Repo</name> <url>https://repository.jboss.org/nexus/content/groups/public/</url> </repository> </repositories> </project>
HibernateはDBごとの違いをdialect(方言)で吸収しているが、HibernateにはSQLite3用のDialectが無いようなので(EclipseLinkにも無い)、自分で作成する。一番近いのはおそらく MySQL なので、MySQL用の Dialect をもとに差異がある部分のみオーバライド。非常に手抜きで、このエントリのデモが動作する程度しか確認していない。
src/main/java/com/example/jpa/hibernate/SQLite3Dialect.java
package com.example.jpa.hibernate; import org.hibernate.dialect.MySQL5Dialect; public class SQLite3Dialect extends MySQL5Dialect { public SQLite3Dialect() { super(); } /** * INSERT後のID取得用SQL。 */ @Override public String getIdentitySelectString() { return "select last_insert_rowid()"; } /** * 自動採番ID用のカラム定義。 * デフォルトだと Long 型は BIGINT になってしまう。 */ @Override public String getIdentityColumnString() { return "integer"; } /** * IDカラムにデフォルトの型を使用するかどうか。 * {@link #getIdentityColumnString()} の定義のみを使いたいので false。 * true のままだと、"id bigint integer" のような定義になってしまう。 */ @Override public boolean hasDataTypeInIdentityColumn() { return false; } /** * タイムスタンプ取得用の関数名。 * デモでは使わない。これでいいのかも不明。 */ @Override public String getCurrentTimestampSQLFunctionName() { return "datetime('now')"; } /** * タイムスタンプ取得用のSQL。 * デモでは使わない。これでいいのかも不明。 */ @Override public String getCurrentTimestampSelectString() { return "select datetime('now')"; } }
JPA管理のエンティティの共通親クラス
src/main/java/com/example/jpa/hibernate/BasicEntity.java
package com.example.jpa.hibernate; import java.util.Date; import javax.persistence.*; @MappedSuperclass public class BasicEntity { @Id @GeneratedValue private Long id; @Version private Long version; private Date createdAt; private Date updatedAt; public Long getId() { return id; } public Long getVersion() { return version; } public void setVersion(Long version) { this.version = version; } public Date getCreatedAt() { return createdAt; } public Date getUpdatedAt() { return updatedAt; } @PrePersist public void onCreate() { createdAt = new Date(); onUpdate(); } @PreUpdate public void onUpdate() { updatedAt = new Date(); } }
テスト用の Customer クラス
src/main/java/com/example/jpa/hibernate/Customer.java
package com.example.jpa.hibernate; import javax.persistence.*; @Entity @NamedQueries({ @NamedQuery(name = Customer.FIND_ALL, query = "SELECT c FROM Customer c") }) public class Customer extends BasicEntity { public static final String FIND_ALL = "findAllCustomers"; private String name; public Customer() { super(); } public Customer(String name) { this(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return String.format( "id=%d, name=%s, version=%d, created_at=%s, updated_at=%s", getId(), getName(), getVersion(), getCreatedAt(), getUpdatedAt()); } }
JPAの設定。さっき作った Dialect を使うように指定する。
src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8" ?> <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_2_0.xsd" version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="testPu" transaction-type="RESOURCE_LOCAL"> <class>com.example.jpa.hibernate.Customer</class> <properties> <property name="javax.persistence.jdbc.driver" value="org.sqlite.JDBC" /> <property name="javax.persistence.jdbc.url" value="jdbc:sqlite:test.db" /> <property name="hibernate.dialect" value="com.example.jpa.hibernate.SQLite3Dialect" /> <!-- createdAt (Java) <=> created_at (DB) のような naming strategy --> <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show-sql" value="true" /> <property name="hibernate.format-sql" value="true" /> </properties> </persistence-unit> </persistence>
テスト用のメインクラス。引数ひとつでその名前の Customer を追加、引数ふたつ(名前、ID)でそのIDの Customer の名前を更新する。
src/main/java/com/example/jpa/hibernate/Customer.java
package com.example.jpa.hibernate; import javax.persistence.*; public class App { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("testPu"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); Customer customer; tx.begin(); if (args.length >= 2) { customer = em.find(Customer.class, Long.valueOf(args[1])); } else { customer = new Customer(); } customer.setName(args[0]); em.persist(customer); tx.commit(); Customer found = em.find(Customer.class, customer.getId()); System.out.println("PERSISTED OBJECT: " + found); TypedQuery<Customer> query = em.createNamedQuery(Customer.FIND_ALL, Customer.class); for (Customer c : query.getResultList()) { System.out.println("OBJECTS IN TABLE: " + c); } em.close(); emf.close(); } }
これで、pom.xml のあるフォルダから
mvn exec:java -Dexec.mainClass=com.example.jpa.hibernate.App -Dexec.args="山田太郎"
とやると
OBJECTS IN TABLE: id=1, name=山田太郎, version=0, created_at=Mon Jul 19 18:51:12 JST 2010, updated_at=Mon Jul 19 18:51:12 JST 2010
のようにデータが作成され、
mvn exec:java -Dexec.mainClass=com.example.jpa.hibernate.App -Dexec.args="上田次郎 1"
とやると
OBJECTS IN TABLE: id=1, name=上田次郎, version=1, created_at=2010-07-19 18:51:12.448, updated_at=Mon Jul 19 18:53:11 JST 2010
のように更新されましたとさ*1。
EclipseLink でも上と同じようなことを試したけど、
- 「主キーカラムだけ型のマッピングを変える」ということができないため、id に @Column(columnDefinition = "INTEGER") をつけなきゃいけない
- @GeneratedValue のデフォルトである strategy = GenerationType.AUTO だと、Hibernateのような「DBごとに適した方法」ではなく、「ID管理用テーブルを作って使う」方式が固定で選択され、しかもデフォルトを変更する方法がない。
等々、だめな部分が目立った。
で、あとで気づきましたが、既に Dialect は存在するようで。