LoginSignup
13
13

More than 5 years have passed since last update.

JPAをClojureから使う

Last updated at Posted at 2015-09-14

JPAは便利そうだけど難しそう。そんなイメージがあるかどうかは分かりませんが、ClojureからClojure-wayで割と簡単に使えるようにしたので、使い方を書き起こしておきます。

事前準備

  1. Entityクラスを用意します.

    @Entity
    public class User implements Serializable {
        @Id
        @GeneratedValue
        private Long id;
    
        private String familyName;
        private String lastName;
    
        // 誌面の都合上、getter/setterは省いてます
    }
    
  2. persistence.xml を書きます.(現在の仕様はPersistenceUnitの名前は"default"固定です)

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.1"
                 xmlns="http://xmlns.jcp.org/xml/ns/persistence"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
        <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
        <properties>
            <property name="eclipselink.target-database" value="org.eclipse.persistence.platform.database.H2Platform"/>
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db;create=true"/>
            <property name="javax.persistence.jdbc.user" value="APP"/>
            <property name="javax.persistence.jdbc.password" value="APP"/>
            <property name="eclipselink.logging.level" value="FINE"/>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
        </properties>
    </persistence-unit>
    </persistence>
    
  3. project.cljにclj-jpaの依存を追加します。

    [clj-jpa "0.1.0"]
    

    使うJPAの依存も追加します。上記persistence.xmlの記述例の場合はH2データベースとEclipseLinkを使っているので↓のようにします。

    [org.eclipse.persistence/org.eclipse.persistence.jpa "2.5.2"]
    [org.jboss.weld.se/weld-se "2.2.14.Final"]
    [com.h2database/h2 "1.4.188"]
    

EntityManager

(require '[clj-jpa.core :refer :all])

(with-entity-manager
  ;;; データベースアクセスコード
  )

この中でEntityManagerを使った操作が可能になります。

トランザクション

(require '[clj-jpa.core :refer :all])

(with-transaction
  ;;; トランザクションコード
  )

with-transactionで囲うと、そこがトランザクション境界になります。JPAのトランザクションと同じく、同一EntityManager下においてネストはできません。

検索

JPAのCriteria APIとJPQL、Native SQLが使えます。

Criteria API

whereオプションで条件式を渡すことができます。

(require '[clj-jpa.entity-manager :as em])

(with-entity-manager
  (em/search User :where (= :family-name "Kawashima")))

検索結果はkebab-caseのマップとして帰ってきます。

[{:family-name "Kawashima" :last-name "Yoshitaka"}]

Entityの関連がある場合は、Delayになっています。

[{:family-name "Kawashima" :groups #object[clojure.lang.Delay 0x6dc30289 {:status :pending, :val nil}]}]

Lazy fetchの場合は読みだそうとしたタイミングでクエリが投げられ取得できます。

=> (count (get-in users [0 :groups 0 :name]))
group1

kormaと同じ条件式をサポートしています。

(with-entity-manager
  (em/search User :where (and (like :last-name "Kawashima")
                              (>= :age 40))

JOINは未サポートです。

JPQL

:jpqlでJPQL文を、:paramsでバインド変数を渡すとJPQLのクエリが実行されます。

(with-entity-manager
  (em/search User
    :jpql "SELECT u FROM User u WHERE u.familyName = :name"
    :params {:name "kawasima"}))

Native SQL

:sqlでネイティブのSQL文を、:paramsでバインド変数を渡すとNative SQLのクエリが実行されます。

(with-entity-manager
  (em/search User
    :sql "SELECT * FROM user WHERE familyname = :name"
    :params {:name "kawasima"}))

更新

(with-entity-manager
  (with-transaction
    (em/merge User {:family-name "Kawashima"
                    :last-name   "Yoshitaka"}))

clj-jpaのAPIを使って検索したMapは、エンティティクラスを省略してmergeを呼ぶことが可能です。

(with-entity-manager
  (with-transaction
    (let [user (em/find User 1)]
      (em/merge (update-in user [:age] inc)))))

Ringで使う

ring用のhandlerが同梱されているので、compojureなどのring上のフレームワークで簡単にJPAを使うことができるようになります。

(require '[clj-jpa.middleware :refer [wrap-entity-manager wrap-transaction]])

(-> handler
    wrap-transaction                                                                                                                                                                                                                                                  
    wrap-entity-manager                                                                                                                                                                                                                                               
    (wrap-defaults site-defaults))

wrap-transactionの方を、wrap-entity-managerの内側に書く必要があります。

まとめ

Clojureで広く使われているDBアクセスライブラリは、kormaあたりかと思います。Clojureの志向的にテーブルの1行はマップで表されるのですが、とはいえデータアクセスレイヤには型があると安心感があります。

clj-jpaは、マップで扱える手軽さはそのままに、DBとのマッピングはEntityクラスでかっちりおこなうというところを目指しています。

JPAがバックグラウンドにあることで、Lazyローディングやスキーマの自動生成などもそのまま利用できることも魅力になるかと思います。

13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13