Spring Tool Suite で Spring MVC から JPA(Java Persistence API) を利用する
Spring Tool Suite の Spring MVC Project テンプレートから、Spring JDBC を使えるようにして、JDBC を JNDI 経由で使えるように、一歩一歩試してきて、ようやく JPAを試す準備がととのった。
Spring MVC Project を作成し、JDBCをJNDI から使用できる状態にしたプロジェクト に対して、Simple Spring JPA Utility Project で作成される、JPAのサンプルコードを移植してみる。
EclipseLink および依存ライブラリの設定
JPA のエンジンとして、EclipseLink を使用するための設定を Maven の pom.xml に行う。
repositories 要素に、EcliipseLink の リポジトリ設定を追加
<repository> <id>EclipseLink Repo</id> <url>http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo</url> <snapshots><enabled>false</enabled></snapshots> </repository>
依存ライブラリの設定
pom.xml に Simple Spring JPA Utility Project で作成される、pom.xml を参考に、依存ライブラリの設定を行う。具体的には、dependencies 要素に、以下を追加
<dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>3.0.0.RELEASE</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.1_3</version> </dependency>
エンティティクラスの実装
Spring JPA Utility Project で作成される、JPAのサンプルコードから、Orderクラス、Itemクラスをコピーしてくる。
Order.java
package info.typea.mvctest.entity; import java.util.Collection; import java.util.LinkedHashSet; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; /** * An order. */ @Entity @Table(name="T_ORDER") public class Order { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private String customer; @OneToMany(cascade=CascadeType.ALL) @JoinColumn(name="ORDER_ID") private Collection<Item> items = new LinkedHashSet<Item>(); /** * @return the customer */ public String getCustomer() { return customer; } /** * @param customer the customer to set */ public void setCustomer(String customer) { this.customer = customer; } /** * @return the items */ public Collection<Item> getItems() { return items; } /** * @param items the items to set */ public void setItems(Collection<Item> items) { this.items = items; } /** * @return the id */ public Long getId() { return id; } }
Item.java
package info.typea.mvctest.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; /** * An item in an order */ @Entity public class Item { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToOne private Order order; private String product; private double price; private int quantity; /** * @return the order */ public Order getOrder() { return order; } /** * @return the product */ public String getProduct() { return product; } /** * @param product the product to set */ public void setProduct(String product) { this.product = product; } /** * @return the price */ public double getPrice() { return price; } /** * @param price the price to set */ public void setPrice(double price) { this.price = price; } /** * @return the quantity */ public int getQuantity() { return quantity; } /** * @param quantity the quantity to set */ public void setQuantity(int quantity) { this.quantity = quantity; } /** * @return the id */ public Long getId() { return id; } }
動作確認のため、適当にDAOクラスを作成
JpaDao.java
package info.typea.mvctest.dao; import info.typea.mvctest.entity.Order; import java.util.List; public interface JpaDao { public void saveOrder(Order order); public List<Order> findByCustomerLike(String customer); }
JpaDaoImpl.java
package info.typea.mvctest.dao; import info.typea.mvctest.entity.Order; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import org.springframework.orm.jpa.support.JpaDaoSupport; public class JpaDaoImpl extends JpaDaoSupport implements JpaDao { public void saveOrder(Order order) { //EntityManagerFactory emf = Persistence.createEntityManagerFactory("application"); EntityManagerFactory emf = getJpaTemplate().getEntityManagerFactory(); EntityManager em = emf.createEntityManager(); EntityTransaction et = em.getTransaction(); et.begin(); em.persist(order); et.commit(); em.close(); } @SuppressWarnings("unchecked") public List<Order> findByCustomerLike(String customer) { //return getJpaTemplate().find("select o from Order o where o.customer like ?1", customer); return getJpaTemplate().find("select o from Order o"); } }
persistance.xml (永続化設定ファイル) の作成
WEB-INF/classes/META-INF/persistence.xml に作成する。
EJB環境、Webコンポーネント環境、JavaSE環境それぞれで、persistance.xml の置き場所が異なるので、注意。
<persistence 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" version="1.0"> <persistence-unit name="application" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <class>info.typea.mvctest.entity.Item</class> <class>info.typea.mvctest.entity.Order</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="eclipselink.logging.level" value="FINEST"/> <property name="javax.persistence.jtaDataSource" value="java:comp/env/jdbc/db2"/> <!-- <property name="eclipselink.ddl-generation" value="create-tables" /> --> <property name="eclipselink.ddl-generation" value="none" /> <property name="eclipselink.ddl-generation.output-mode" value="database" /> </properties> </persistence-unit> </persistence>
JDBC を JNDI に登録
- tc Server のインスタンスを作成
- JDBC ドライバを、tc Server の lib に置く
- context.xml に設定を記述
context.xml にJNDIの記述をしても、適宜初期化されてしまう。。。 tc server のテンプレートを別途作成し、そこに JNDI の設定をして、そのテンプレートを元にインスタンスを作成・・・なんて手順を踏めばうまくいくのかなぁと思ったりしているが、未検証。
プロジェクトエクスプローラーから context.xml を編集しておくと、とりあえず消えなさそうだ。
context.xml に、以下の様な、JNDI リソースを登録
<Context docBase="C:\springsource\vfabric-tc-server-developer-2.5.0.RELEASE\tc_server_1\wtpwebapps\mvctest" path="/mvctest" reloadable="true" source="org.eclipse.jst.jee.server:mvctest"> <Resource name="jdbc/db2" auth="Container" type="javax.sql.DataSource" driverClassName="com.ibm.db2.jcc.DB2Driver" url="jdbc:db2://192.168.10.77:50000/sample" username="db2inst1" password="xxxxx" maxActive="20" maxIdle="10" maxWait="-1"/> </Context>
WEB-INF/web.xml に以下を追記
<resource-ref> <description>DB2 Datasource example</description> <res-ref-name>jdbc/db2</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
各種 Bean の登録
root-context.xml に、以下の Bean を登録
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="persistenceUnitName" value="application" /> <property name="loadTimeWeaver" ref="loadTimeWeaver" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"> <property name="showSql" value="true" /> <property name="generateDdl" value="true" /> </bean> </property> </bean> <bean id="loadTimeWeaver" class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> <bean id="jpaDao" class="info.typea.mvctest.dao.JpaDaoImpl"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/db2"/> <property name="lookupOnStartup" value="false"/> <property name="cache" value="true"/> <property name="proxyInterface" value="javax.sql.DataSource"/> </bean>
Java Agent の登録
InstrumentationLoadTimeWeaver クラスを利用するためには、Java Agent を登録する必要がある。
Java Agent とは、Java のバイトコードを操作する、AOPには欠かせない機能。
Servers ビューから、サーバーをダブルクリックし、エディタから、Open launch configuration を選択する。
Arguments タブから、VM arguments の Variables…ボタンを押下、開いた画面から、Edit Variables… ボタンを押下。
New ボタンを押下し、起動したダイアログに、以下のような内容を設定
項目 | 内容 | 例 | 備考 |
Name | 任意の名前 | javaagent | |
Value | -javaagent:/エージェントへのパス | -javaagent:/Users/piroto/.m2/repository/org/springframework/spring-instrument/3.0.0.RELEASE/spring-instrument-3.0.0.RELEASE.jar | とりあえず、Mavenのリポジトリ内のjarへのパス ※ Users/piroto はユーザーディレクトリ |
最後に、VM arguments に今設定した変数 ${javaagent} を追記する。
MVC コントローラーに処理を記述
package info.typea.mvctest; import info.typea.mvctest.dao.JpaDao; import info.typea.mvctest.dao.Staff; import info.typea.mvctest.dao.StaffDao; import info.typea.mvctest.entity.Item; import info.typea.mvctest.entity.Order; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Handles requests for the application home page. */ @Controller public class HomeController { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); private JpaDao jpadao; public void setJpaDao(JpaDao jpadao) { this.jpadao = jpadao; } /** * Simply selects the home view to render by returning its name. */ @RequestMapping(value="/", method=RequestMethod.GET) public String home() { logger.info("Welcome home!"); return "home"; } @RequestMapping(value="/save_order") public String saveOrder() { logger.info("insert test data!"); Order order = new Order(); order.setCustomer("customer"); Item item = new Item(); item.setPrice(100d); item.setProduct("product"); order.getItems().add(item); this.jpadao.saveOrder(order); logger.info("ORDER id:" + order.getId()); List<Order> os = this.jpadao.findByCustomerLike(""); for (Order o : os) { logger.info("o:id " + o.getId()); } return "home"; } }
/save_order で、適当なオブジェクトを登録して、登録されているオブジェクトをログに出力する記述。
実行
http://localhost:8080/mvctest/save_order を実行
上記を何度か実行。正しく実行されて、登録されたデータが取得できた。
データベースを確認
データも正しくできている。
いじょ。
JPA の仕組みについては、この本に非常に丁寧な説明があります。
その他の項目についての説明も非常にわかりやすい良書。