ManyToManyのメモ


テーブル

create table movie (
id identity,
name varchar(50),
primary key (id)
);
create table actor (
id identity,
name varchar(50),
primary key (id)
);
create table movie_actor (
movie_id bigint,
actor_id bigint,
primary key (movie_id, actor_id)
);

Movie.class

package sample;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

@Entity
public class Movie implements Serializable {

    private static final long serialVersionUID = 1L;
    
    private int id;
    private String name;
    private List actors = new ArrayList();
    
    public Movie() {
    }

    public Movie(String name) {
    	this.name = name;
    }
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String title) {
        this.name = title;
    }
    
    @ManyToMany(
    		targetEntity=sample.Actor.class, 
    		cascade=CascadeType.ALL, 
    		fetch=FetchType.LAZY
    )
    @JoinTable(
    	name="movie_actor",
        joinColumns={@JoinColumn(name="movie_id")},
        inverseJoinColumns={@JoinColumn(name="actor_id")}
    )
    public List getActors() {
        return actors;
    }
    
    public void setActors(List actors) {
        this.actors = actors;
    }
    
    public void addActor(Actor actor) {
        actors.add(actor);
    }
}

Actor.class

package sample;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

@Entity
public class Actor implements Serializable {

    private static final long serialVersionUID = 1L;
    
    private int id;
    private String name;
    private List movies = new ArrayList();
    
    public Actor() {
    }

    public Actor(String name) {
    	this.name = name;
    }

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @ManyToMany(
    		cascade=CascadeType.ALL, 
    		fetch=FetchType.LAZY,
    		mappedBy="actors")
    public List getMovies() {
        return this.movies;
    }

    public void setMovies(List movies) {
        this.movies = movies;
    }

    public void addMovies(Movie movie) {
        this.movies.add(movie);
    }

}

persistence.xml



	
		org.hibernate.ejb.HibernatePersistence
		sample.Actor
		sample.Movie
		
			
			
			
			
			
			
			
			
		
	



Main.class

package sample;

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

public class ManyToManyExample {
	
	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hibernate");
		EntityManager em = emf.createEntityManager();
		
	    final EntityTransaction tx = em.getTransaction();
	    tx.begin();

	    Actor brad = new Actor("Brad Pitt");
	    Actor orlando = new Actor("Orlando Bloom");
	    
	    em.persist(brad);
	    em.persist(orlando);

	    Movie troy = new Movie("Troy");
	    Movie babel = new Movie("Babel");
	    Movie pirates = new Movie("Pirates of Caribbean:The Curse of the Black Pearl");
	    
	    em.persist(troy);
	    em.persist(babel);
	    em.persist(pirates);
	    
	    troy.addActor(brad);
	    troy.addActor(orlando);
	    babel.addActor(brad);
	    pirates.addActor(orlando);
	    
	    em.merge(troy);
	    em.merge(babel);
	    em.merge(pirates);
	    
// ↓ 	関連テーブルの登録が行なわれない。
//	    以下のコードで登録をする場合、ActorのmappedByを削除し、
//	    代わりに@JoinTableを定義してやる必要がある。
//	    
//	    brad.addMovies(troy);
//	    brad.addMovies(babel);
//	    orlando.addMovies(troy);
//	    orlando.addMovies(pirates);
//	    
//	    
//	    em.merge(brad);
//	    em.merge(orlando);
	    
		tx.commit();
		
		em.close();
		emf.close();
	}

}

EntityManager.remove()のメモ

削除の動作を確認しようと次のようなコードを実行したところ、ObjectDeletedExceptionという例外がthrowされた。

Remove.class

package sample;

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

public class RemoveExample {
	
	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hibernate");
		EntityManager em = emf.createEntityManager();
		
	    final EntityTransaction tx = em.getTransaction();
	    tx.begin();

	    Actor brad = new Actor("Brad Pitt");
	    Actor orlando = new Actor("Orlando Bloom");
	    
	    em.persist(brad);
	    em.persist(orlando);

	    Movie troy = new Movie("Troy");
	    
	    troy.addActor(brad);
	    troy.addActor(orlando);
	    
	    em.persist(troy);
	    
	    em.remove(brad);

		tx.commit();
		
		em.close();
		emf.close();
	}

}

例外

Exception in thread "main" javax.persistence.RollbackException: Error while commiting the transaction
	at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:71)
	at sample.RemoveExample.main(RemoveExample.java:32)
Caused by: org.hibernate.ObjectDeletedException: deleted entity passed to persist: [sample.Actor#]
	at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:90)
	at org.hibernate.impl.SessionImpl.firePersistOnFlush(SessionImpl.java:644)
	at org.hibernate.impl.SessionImpl.persistOnFlush(SessionImpl.java:636)
	at org.hibernate.engine.CascadingAction$9.cascade(CascadingAction.java:323)
	at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
	at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
	at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
	at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
	at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:131)
	at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:122)
	at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65)
	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
	at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
	at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
	at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
	... 1 more

どうも「em.remove(brad);」を呼び出したはいいが、troyインスタンス(Movie)がbradインスタンス(Actor)の参照を保持したままになっているのがいけないらしい。

ということで、まずMovieクラスにActorを削除する為のメソッドを追加。

Movie.class(抜粋)

    public boolean removeActor(Actor actor) {
        return actors.remove(actor);
    }

そして、「em.remove(brad);」を呼び出したあとで、bradインスタンス(Actor)の参照をtroyインスタンス(Movie)から削除するように修正。

Remove.class(抜粋)

	    em.remove(brad);
	    troy.removeActor(brad);

実行後、期待通り「Brad Pitt」は登録されず、「Orlando Bloom」と「Troy」が登録されることを確認。

SELECT * FROM ACTOR ;

ID NAME
54 Orlando Bloom

SELECT * FROM MOVIE_ACTOR ;

MOVIE_ID ACTOR_ID
65 54

SELECT * FROM MOVIE;

ID NAME
65 Troy