読者です 読者をやめる 読者になる 読者になる

Eclipse+JSF+JPAで作るアプリ(8)―ライフサイクルとCascadeType

今回はほとんどの内容が(6)と(7)と同じか、そのおさらいです。
先にトピックの解説を行い、後半に参考までにコード全体を載せます。

目新しい点は、

  • CascadeTypeとオブジェクトのライフサイクルのおさらい
  • JPQLでの、ネストしたオブジェクトの属性値の指定

です。

次回からは、いよいよJSF編です。
ここまでは下準備といったところです。

映画(Movie)、ユーザ(User)と貸出履歴(LendHistory)のライフサイクル

オブジェクトのライフサイクルを考えるときは、構造と各オブジェクトのCRUDの4点を機械的に網羅してユースケースを考えていきます。そうすることで仕様や検討に漏れがなくなります。(バージョンを積むシステムの場合は、V-up、V-downにCRUDを合わせたの6点で網羅します。)
今回作るアプリケーションでは、

  • 貸出履歴クラスは、親に、ユーザと映画を持ちます。
  • 貸出履歴クラスは、レビュー(感想)やレーティング(5つ星など)の属性を持っていて、他の利用者から参照できるようになっています。


さて、貸出履歴は、ユーザ、映画、それ自身のCRUD操作が行われたときにどうなるべきかをアプリケーションとして考えます。

オブジェクト/操作 Create Reference Update Delete
映画 特になし 映画ごとの履歴も参照したい 特になし 履歴も削除
ユーザ 特になし ユーザごとの履歴も参照したい 特になし 他のユーザのため履歴は残す
履歴 「ユーザ」が「映画」を借りて作成 全体も参照/検索したい 後からレビュー等を更新 管理者が削除するケース

履歴のライフサイクルに関しては以下のような仕様が決まりました。

  1. 履歴の作成は、ユーザが映画を借りるときに作成される。
  2. 履歴の参照は、誰でも、自分のもの、映画ごと、全体の参照ができる。
  3. 履歴の更新は、ユーザは自分の履歴は更新できる。
  4. 履歴は基本消さない。ただし、管理者だけ消せるようにする。
  5. ユーザが削除(映画の利用者が退会等)しても履歴は残るべきです。なぜなら他のユーザが『借りる』ことができるし、レビュー等を参照したいからです。
  6. 映画が削除された場合、履歴は別に残してもよいですが、『借りる』ということができません。履歴は見れるけど借りることはできないのは悲しいだけなので削除されるようにします。

以上のように履歴は映画の子供となりました。ですのでLendHistoryManagerというクラスは作成せず、MovieManagerのみで操作できるよう実装します。

CascadeTypeの設定

CascadeTypeには、ALL,PERSIST,MERGE,REFRESH,REMOVE,DETACHというタイプがあります。ALL以外は、それぞれ親のオブジェクトに対してEntityManagerの対応するメソッドが呼ばれた際に子のオブジェクトにもそのメソッドが伝播します。

上記5と6の仕様を満たすために、Userの場合は、CascadeTypeを指定せず(PERSISTとMERGEはつけてもよいのでしょうが、現在のAPIと仕様上は不要です)、Movieの場合は、CascadeTypeをALLに設定します。

User.java

	@OneToMany(mappedBy="lendUser")
	List<LendHistory> lendHistories;

Movie.java

	@OneToMany(mappedBy="movie", cascade=CascadeType.ALL )
	List<LendHistory> lendHistories;

MovieManagerのソースコード

UserManagerと違う目新しい点は、以下の「履歴を取得する際に、映画のタイトルでソートする。」ということがJPQLでは簡単にできるという点です。同じことをSQLで書くより大幅に簡略化されています。

h.movie.titleで、LendHistory.getMovie().getTitle()というAPIを呼んでいることになります。

	em.createQuery("select h from LendHistory h order by h.movie.title asc", LendHistory.class);


それでは、すべてのソースコードです。

  • MovieのCRUDが createMovie, findXxx, updateMovie, removeMovieのメソッドです。
  • LendHistoryのCRUDが、lendMovie, returnMovie, findLendHistory, updateLendHistory, removeLendHistoryのメソッドです。
  • LendHistory lendMovie( Movie movie, User user )が、「1 履歴の作成は、ユーザが映画を借りるときに作成される。」という仕様にマッチしたコードです。
package sample.yourlibrary.logic;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

import sample.yourlibrary.entity.LendHistory;
import sample.yourlibrary.entity.Movie;
import sample.yourlibrary.entity.User;

public class MovieManager {
	private static EntityManager getEm()
	{
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("YourLibrary");
		EntityManager em = emf.createEntityManager();
		return em;
	}
	
	public static Movie createMovie( String title )
	{
		Movie movie = new Movie();
		movie.setTitle(title);
		EntityManager em = getEm();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		em.persist(movie);
		tx.commit();
		em.close();
		return movie;
	}
	
	public static Movie findById( long id )
	{
		EntityManager em = getEm();
		Movie movie = em.find(Movie.class, id);
		em.clear();
		em.close();
		return movie;
	}
	
	public static List<Movie> findAll()
	{
		EntityManager em = getEm();
		TypedQuery<Movie> q = em.createQuery("select m from Movie m order by m.title asc", Movie.class);
		List<Movie> result = q.getResultList();
		em.close();
		return result;
	}
	
	public static Movie findByTitle( String title )
	{
		EntityManager em = getEm();
		TypedQuery<Movie> q = em.createQuery("select m from Movie m where m.title=:title", Movie.class);
		q.setParameter("title", title);
		List<Movie> result = q.getResultList();
		em.close();
		if( result.size() > 0 )
			return result.get(0);
		return null;
	}
	
	public static Movie updateMovie( Movie movie )
	{
		EntityManager em = getEm();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		if(!em.contains(movie) )
			movie = em.merge(movie);
		tx.commit();
		em.close();
		return movie;
	}
	
	public static boolean removeMovie( Movie movie )
	{
		EntityManager em = getEm();
		Movie find = em.find(Movie.class, movie.getId());
		if( find == null )
			return false;
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		em.remove(find);
		tx.commit();
		em.close();
		return true;
	}
	
	public static LendHistory lendMovie( Movie movie, User user )
	{
		LendHistory history = new LendHistory();
		history.setLendDate(Date.from( LocalDateTime.now().toInstant(ZoneOffset.UTC)));
		EntityManager em = getEm();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		em.persist(history);
		if(!em.contains(movie))
			movie = em.merge(movie);
		movie.setIsLent(true);
		movie.addLendHistory(history);
		if(!em.contains(user))
			user = em.merge(user);
		user.addLendHistory(history);
		tx.commit();
		em.close();
		return history;
	}
	
	public static LendHistory updateLendHistory( LendHistory history )
	{
		EntityManager em = getEm();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		if( !em.contains(history) )
			history = em.merge(history);
		tx.commit();
		em.close();
		return history;
	}
	
	public static LendHistory returnMovie( LendHistory history )
	{
		EntityManager em = getEm();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		if( !em.contains(history) )
			history = em.merge(history);
		history.setReturnDate(Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)));
		Movie movie = history.getMovie();
		if( !em.contains(movie) )
			movie = em.merge(movie);
		movie.setIsLent(false);
		tx.commit();
		em.close();
		return history;
	}

	public static boolean removeLendHistory( LendHistory history )
	{
		EntityManager em = getEm();
		LendHistory find = em.find(LendHistory.class, history.getId());
		if( find == null )
			return false;
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		em.remove(find);
		tx.commit();
		em.close();
		return true;
	}
	
	public static LendHistory findLendHistoryById( long id )
	{
		EntityManager em = getEm();
		LendHistory history = em.find(LendHistory.class, id);
		em.clear();
		em.close();
		return history;
	}
	
	public static List<LendHistory> findAllLendHistory()
	{
		EntityManager em = getEm();
		TypedQuery<LendHistory> q = em.createQuery("select h from LendHistory h order by h.movie.title asc", LendHistory.class);
		List<LendHistory> result = q.getResultList();
		em.close();
		return result;
	}
}

MovieManagerTestのソースコード

テストケースのソースコードです。
あまり目新しい点はありません。
最後のほうで、Userを消しても履歴が消えないこと、Movieを消すと履歴が消えることのライフサイクルのチェックをしています。

package sample.yourlibrary.logic;

import static org.junit.Assert.*;

import java.util.Date;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import sample.yourlibrary.entity.LendHistory;
import sample.yourlibrary.entity.Movie;
import sample.yourlibrary.entity.User;

public class MovieManagerTest {

	@Before
	public void setUp() throws Exception {
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void testCRUDforMovie() {
		//検索
		List<Movie> list1st = MovieManager.findAll();
		int list1stSize = list1st.size();
		
		//新規作成
		Movie movie = MovieManager.createMovie("movie1title");
		assertEquals("movie1title", movie.getTitle());
		assertFalse(movie.getIsLent());
		//ID検索
		Movie find1 = MovieManager.findById(movie.getId());
		assertEquals("movie1title", find1.getTitle());
		
		//更新
		find1.setCategory("movie1category");
		find1.setOutline("movie1outline");
		find1.setIsLent(true);
		MovieManager.updateMovie(find1);
		
		//タイトル検索
		find1 = MovieManager.findByTitle("movie1title");
		assertEquals("movie1title", find1.getTitle());
		assertEquals("movie1category", find1.getCategory());
		assertEquals("movie1outline", find1.getOutline());
		assertTrue( find1.getIsLent() );
		
		//追加の件数確認
		List<Movie> list2nd = MovieManager.findAll();
		assertEquals(list1stSize+1, list2nd.size() );
		
		//削除
		MovieManager.removeMovie(find1);
		find1 = MovieManager.findByTitle("movie1title");
		assertNull( find1 );
		
		//削除の件数確認
		List<Movie> list3rd = MovieManager.findAll();
		assertEquals(list1stSize, list3rd.size() );
	}

	@Test
	public void testCRUDforLendHistory() {
		
		Movie movie = null;
		User user = null;
		try
		{
			//movieの新規作成
			movie = MovieManager.createMovie("movie2title");
			//userの新規作成
			user = UserManager.createUser("user2", "user2");
			
			//検索
			List<LendHistory> list1st = MovieManager.findAllLendHistory();
			int list1stSize = list1st.size();
			
			//作成(貸出)
			LendHistory history1 = MovieManager.lendMovie(movie, user);
			assertEquals(movie.getId(), history1.getMovie().getId());
			assertEquals(user.getId(), history1.getLendUser().getId());
			assertNotNull(history1.getLendDate());

			//追加の件数確認
			List<LendHistory> list2nd = MovieManager.findAllLendHistory();
			int list2ndSize = list2nd.size();
			assertEquals(list1stSize+1, list2ndSize);
			
			//更新
			history1.setReview("nice");
			history1.setStarRating(5);
			history1.setDueDate(new Date());
			MovieManager.updateLendHistory(history1);
			LendHistory history2 = MovieManager.findLendHistoryById(history1.getId());
			assertEquals("nice", history2.getReview());
			assertEquals(new Double(5), new Double(history2.getStarRating()) );
			assertNotNull(history2.getDueDate());
			
			//Movie側からの取得
			Movie movie1 = MovieManager.findById(movie.getId());
			assertEquals(history1.getId(), movie1.getLendHistories().get(0).getId());
			//User側からの取得
			User user1 = UserManager.findById(user.getId());
			assertEquals(history1.getId(), user1.getLendHistories().get(0).getId());
			
			//返却
			history2 = MovieManager.returnMovie(history2);
			assertNotNull(history2.getReturnDate());
			
			//2件目の作成
			LendHistory history3 = MovieManager.lendMovie(movie, user);
			assertEquals(movie.getId(), history3.getMovie().getId());
			assertEquals(user.getId(), history3.getLendUser().getId());
			assertNotNull(history3.getLendDate());

			//1件削除
			MovieManager.removeLendHistory(history1);
			List<LendHistory> list3rd = MovieManager.findAllLendHistory();
			int list3rdSize = list3rd.size();
			assertEquals(list1stSize+1, list3rdSize);
			
			//Userを削除した際に削除されていないことの確認
			UserManager.removeUser(user);
			user = null;
			List<LendHistory> list4th = MovieManager.findAllLendHistory();
			int list4thSize = list4th.size();
			assertEquals(list1stSize+1, list4thSize);
			
			//Movieを削除した際に削除されることの確認
			MovieManager.removeMovie(movie);
			movie = null;
			List<LendHistory> list5th = MovieManager.findAllLendHistory();
			int list5thSize = list5th.size();
			assertEquals(list1stSize, list5thSize);
		}
		finally
		{
			//movieの削除
			if( movie != null )
				MovieManager.removeMovie(movie);
			//userの削除
			if( user != null)
				UserManager.removeUser(user);
		}
	}
}

以上です。次回からは、やっとJSFです。