Introdcution¶
本ドキュメントではJPAにおけるデータベースアクセスの代表的なパターンにおける実装サンプルを記述する。
- 単テーブルにおけるデータ検索
- 複数件を取得するデータ検索
- 単テーブルにおけるデータ追加
- 単テーブルにおけるデータ更新
- 単テーブルにおけるデータ削除
- OneToManyの関連を持つテーブルにおけるデータ検索
- OneToManyの関連を持つテーブルにおけるデータ追加
- OneToManyの関連を持つテーブルにおけるデータ更新
- OneToManyの関連を持つテーブルにおけるデータ削除
- ManyToManyの関連を持つテーブルにおけるデータ検索
- ManyToManyの関連を持つテーブルにおけるデータ追加
- ManyToManyの関連を持つテーブルにおけるデータ更新
- ManyToManyの関連を持つテーブルにおけるデータ削除
前提事項¶
実装サンプルは以下のプロジェクト構成としている。
- test-parent :共通的なDependencyManagementやPluginManagement、Propertyの定義を行うプロジェクト
- test-javaee6-ear :earファイルの構成を設定するためのプロジェクト
- test-javaee6-ejb :Stateless Session Beanにて実装したビジネスロジックやデータベースアクセスロジック実装を含んだプロジェクト
- test-domain :各ビジネスロジックやデータベースアクセスに関するインターフェースを含んだプロジェクト。基本JavaEEに非依存。
- test-javaee6-web :JSFをベースに構成されている画面コンポーネントを含んだプロジェクト。Google Material Design Liteを使用。
各画面とロジックの対応関係¶
test-javaee6-webプロジェクトには、各テーマごとに、対応するページを作成している。JBossを起動した後、以下のURLを実行すると各機能の実行ページに移る。
Todo
SampleプロジェクトをGitHubにアップする。
サンプルで使用するデータベースのテーブル構成は以下の通り。
- User
No Table Column Attribute Entity Class property 1 DUSER USER_ID VARCHAR(8) User userId 2 DUSER USER_NAME VARCHAR User userName 3 DUSER LAST_UPDATED_DATE_AND_TIME DATE User lastUpdatedDateAndTime 4 DUSER IS_LOGIN BOOLEAN User lastUpdatedDateAndTime 5 DUSER VER INT User ver
- Credential
No Table Column Attribute Entity Class property 1 CREDENTIAL USER_ID VARCHAR(8) CredentialPK userId 2 CREDENTIAL CREDENTIAL_ID VARCHAR(4) CredentialPK credentialId 3 CREDENTIAL CREDENTIAL_TYPE VARCHAR Credential credentialType 4 CREDENTIAL CREDENTIAL_KEY VARCHAR Credential credentialKey 5 CREDENTIAL EXPIRED_DATE_AND_TIME DATE Credential expiredDateAndTime 6 CREDENTIAL VER INT Crednetial ver
No Table Column Attribute Entity Class property 1 USER_ID VARCHAR(8) EmailPK userId 2 EMAIL_ID INT EmailPK emailId 3 VARCHAR 4 VER INT ver
- Address
No Table Column Attribute Entity Class property 1 ADDRESS USER_ID VARCHAR(8) AddressPK userId 2 ADDRESS ADDRESS_NO INT AddressPK addressNo 3 ADDRESS ZIP_CODE CHAR(8) Address zipCode 4 ADDRESS ADDRESS VARCHAR Address address 5 ADDRESS ADDRESS_DATAILS VARCHAR Address addressDetails
- PHONE
No Table Column Attribute Entity Class property 1 PHONE USER_ID VARCHAR(8) PhonePK userId 2 PHONE PHONE_NO INT PhonePK phoneNo 3 PHONE PHONE_NUMBER VARCHAR(11) Phone PhoneNumber 4 VER INT Phone ver
- GROUP
No Table Column Attribute Entity Class property 1 GROUP GROUP_ID VARCHAR(10) Group groupId 2 GROUP GROUP_NAME VARCHAR Group groupName 3 GROUP VER INT Group Ver
- AFFILIATION
No Table Column Attribute Entity Class property 1 AFFILIATION USER_ID VARCHAR(8) Affiliation userId 2 AFFILIATION GROUP_ID VARCHAR(10) Affiliation groupId 3 AFFILIATION VER INT Affiliation Ver
データベースアクセスロジックに関する共通事項¶
各エンティティごとに重複した実装を省略するために、本実装サンプルではGenericDAOパターンを採用する。 GenericDAOパターンとは、JPAエンティティクラスとそのキーとなるクラスを型パラメータにもつ、 データベースアクセスに関わる振る舞いを実装したクラスである。
test-domain org.debugroom.test.domain.repository.GenericDAO
1 2 3 4 5 6 7 8 9 10 11 12 | package org.debugroom.test.domain.repository;
import java.io.Serializable;
// 各エンティティに対応する型パラメータTとキーに対応する型パラメータIDをもつ。
public interface GenericDao<T, ID extends Serializable> {
//各エンティティに対応する共通的な操作を定義する。
public T find(ID id);
public void persist(T entity);
public void merge(T entity);
public void remove(T entity);
}
|
test-javaee6-ejb org.debugroom.test.domain.repository.impl.jpa.GenericDaoImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package org.debugroom.test.domain.repository.impl.jpa;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import org.debugroom.test.domain.repository.GenericDao;
@SuppressWarnings("unchecked")
public abstract class GenericDaoImpl<T extends Serializable,
ID extends Serializable> implements GenericDao<T, ID>{
Class<T> clazz;
@PersistenceContext
EntityManager entityManager;
// 型パラメータで指定されたエンティティクラスを設定する。
public GenericDaoImpl(){
ParameterizedType parameterizedType = (ParameterizedType)getClass().getGenericSuperclass();
this.clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
//シンプルなテーブル操作では、JPAにおけるCriteria APIを使ったデータアクセス操作を実装。
@Override
public T find(ID id) {
return (T)entityManager.find(clazz, id);
}
@Override
public void persist(T entity) {
entityManager.persist(entity);
}
@Override
public void merge(T entity) {
entityManager.merge(entity);
}
@Override
public void remove(T entity) {
entityManager.remove(entity);
}
}
|
GenericDAOを継承し、各エンティティに対応するインターフェース及び、データアクセスロジッククラスを作成する。例として、エンティティクラスUserに対するデータアクセスクラスを以下に示す。
test-domain org.debugroom.test.domain.repository.UserRepository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package org.debugroom.test.domain.repository;
import java.util.List;
import org.debugroom.test.domain.model.User;
//GenericDaoクラスを継承する。追加で必要なインターフェースを定義する。
public interface UserRepository extends GenericDao<User, String> {
public User findOne(String userId);
public List<User> findAll();
public Long count();
public boolean exists(String userId);
public boolean save(User user);
public boolean update(User user);
public boolean delete(User user);
}
|
test-javaee6-ejb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | package org.debugroom.test.domain.repository.impl.jpa;
import java.util.Date;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import org.debugroom.test.domain.model.User;
import org.debugroom.test.domain.repository.UserRepository;
// Stateless Session Beanとして実装する。トランザクションの管理はEJBコンテナに任せる。
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class UserRepositoryImpl extends GenericDaoImpl<User, String> implements UserRepository{
// 各エンティティの操作に対してGenericDaoのメソッドを使用して実装するパターン。
@Override
public User findOne(String userId) {
return find(userId);
}
// JPQLを利用して実装するパターン
@SuppressWarnings("unchecked")
@Override
public List<User> findAll() {
Query query = entityManager.createQuery("From User u");
return (List<User>)query.getResultList();
}
@Override
public boolean exists(String userId) {
Query query = entityManager.createQuery("select count(u) from User u where u.userId = :userId");
query.setParameter("userId", userId);
if(query.getResultList().size() == 0){
return false;
}
return true;
}
@Override
public Long count() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(User.class)));
Query query = entityManager.createQuery(criteriaQuery);
return (Long)query.getSingleResult();
}
//JPAのCriteriaAPIを使って実装するパターン
@Override
public boolean save(User user) {
if(entityManager.contains(user)){
return false;
}
entityManager.persist(user);
return true;
}
@Override
public boolean update(User user) {
User updateUser = find(user.getUserId());
if(null == updateUser){
return false;
}
updateUser.setUserName(user.getUserName());
updateUser.setLogin(user.isLogin());
updateUser.setLastUpdatedDateAndTime(new Date());
return true;
}
@Override
public boolean delete(User user) {
User deleteUser = find(user.getUserId());
if(null == deleteUser){
return false;
}
remove(deleteUser);
return true;
}
}
|
本サンプルでのデータベースアクセスに関する実装は基本上記の構成となることに留意すること。