Löschen wird (ohne Fehler) verweigert

Hallo,

ich habe ein Entity mit dem ich einen Tree aufspannen kann:

@Entity
public class Category{
	
        @Id @GeneratedValue
	private long id;
	private String name;
	private int type;
	@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
	@JoinTable(
			   name = "Category_Childs", 
			   joinColumns = @JoinColumn(name = "category_id"), 
			   inverseJoinColumns = @JoinColumn(name = "child_id")
			)
	private Category parent;

	@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
	private List<Category> childCategory = new ArrayList<Category>(); 

Ein Solcher Baum sieht z.B. so aus:


Node A
 |
 --Node A1
 |
 --Node A2
   

Num möchte ich Node A1 löschen. Folgende Funktion nutze ich dafür:

    public void deleteObject(Object o){

    	EntityTransaction tx = em.getTransaction();
    	tx.begin();
    	em.remove(o);
    	tx.commit();
    }

Mein Problem:
Die Node A1 wird nicht gelöscht. Ich bekomme aber auch keinen Fehler oder überhaupt irgendeine Consolenausgabe von Hibernate zu dem remove (Consolen ausgaben an sich sind natürlich eingestellt). Allerdings wird die funktion deleteObject aufgerufen und im Object o wird die richtige Node übergeben. Stelle ich nun bei der Variable parent von @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}) auf @ManyToOne(cascade = CascadeType.ALL) funktioniert das löschen.

Natürlich wird nun die komplette Tree gelöscht da alle Parents und deren Childs gelöscht werden.

Wo liegt das Problem? Wie muss ich die Annotationen richtig setzen damit die Node inklusive ihrer Childs aber NICHT der parent gelöscht wird? Finde in dem Code den fehler nicht.

Gruß
CrommCruach

Das Cascading ist bei der @OneToMany-Beziehung richtig aufgehoben, bei der @ManyToOne cascade komplett weglassen, dann sollte es funktionieren.

Das hat leider nicht zum gewünschten Erfolg geführt. Hab die DB (H2-Database) mal nun mit genaueren Logausgaben gestartet. Beim Ausführen des Actionlisteners der den Löschvorgang auslöst kommt nur folgende ausgabe im Log.


08-26 10:28:01 jdbc[2]:
/*SQL */COMMIT;
08-26 10:28:01 jdbc[2]:
/*SQL */COMMIT;

Ich versuch das mal nachzubauen, hab aber grad ein Problem mit dem @GeneratedValue und der H2-DB.

So, jetzt. H2 1.3.154 und Hibernate 3.3.

Die Tabelle für die Baumstruktur:

[sql]create table CATEGORY (ID BIGINT not null auto_increment, PARENT_ID BIGINT null, NAME VARCHAR( 100),primary key (ID));[/sql]

Die Entity-Klasse:

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity(name = "Category")
@Table(name = "CATEGORY")
public class Category {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
    private final List<Category> children = new ArrayList<Category>();

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME")
    private String name;

    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    private Category parent;

    public Category() {
        this( "" );
    }

    public Category( String name ) {
        super();
        this.name = name;
    }

    public void addChild( Category child ) {
        child.setParent( this );
        children.add( child );
    }

    public List<Category> getChildren() {
        return children;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Category getParent() {
        return parent;
    }

    public void removeChild( Category child ) {
        child.setParent( null );
        children.remove( child );
    }

    public void setId( Long id ) {
        this.id = id;
    }

    public void setName( String name ) {
        this.name = name;
    }

    public void setParent( Category parent ) {
        this.parent = parent;
    }

}```

Nach dem Befüllen mit dieser Methode

```    private void populate() {

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        Category root = new Category( "A" );
        Category c;

        c = new Category( "A1" );
        root.addChild( c );
        c.addChild( new Category( "A1.1" ) );
        c.addChild( new Category( "A1.2" ) );
        c.addChild( new Category( "A1.3" ) );

        c = new Category( "A2" );
        root.addChild( c );
        c.addChild( new Category( "A2.1" ) );
        c.addChild( new Category( "A2.2" ) );
        c.addChild( new Category( "A2.3" ) );
        toDelete = c;

        c = new Category( "A3" );
        root.addChild( c );
        c.addChild( new Category( "A3.1" ) );
        c.addChild( new Category( "A3.2" ) );
        c.addChild( new Category( "A3.3" ) );

        em.merge( root );

        tx.commit();
    }

sieht die Tabelle so aus:

sql> select * from CATEGORY;
ID                  |PARENT_ID           |NAME
22                  |null                |A
23                  |22                  |A1
24                  |23                  |A1.1
25                  |23                  |A1.2
26                  |23                  |A1.3
27                  |22                  |A2
28                  |27                  |A2.1
29                  |27                  |A2.2
30                  |27                  |A2.3
31                  |22                  |A3
32                  |31                  |A3.1
33                  |31                  |A3.2
34                  |31                  |A3.3
(13 rows, 51 ms)

Jetzt löschen wir den Knoten A2:


        EntityTransaction tx = em.getTransaction();
        tx.begin();

        Query query = em.createQuery( "select c from Category c where c.name = 'A2'" );
        Category c = ( Category )query.getSingleResult();
        em.remove( c );

        tx.commit();
    }

mit dem Ergebnis

sql> select * from CATEGORY;
ID                  |PARENT_ID           |NAME
22                  |null                |A
23                  |22                  |A1
24                  |23                  |A1.1
25                  |23                  |A1.2
26                  |23                  |A1.3
31                  |22                  |A3
32                  |31                  |A3.1
33                  |31                  |A3.2
34                  |31                  |A3.3
(9 rows, 45 ms)

Ich denke das Problem liegt an meinem PersistenceManager

Das ist die Version in der nicht gelöscht wird und auch keine Errors kommen:

public class PersistenceManager{
	
	private static PersistenceManager persistenceManager = null;
	
	private static EntityManagerFactory emf = null;
	private static EntityManager em = null;
	
	private PersistenceManager(){
    	
		emf = Persistence.createEntityManagerFactory("JPAConfig");
		em = emf.createEntityManager();
    }
 
    /**
     * Creates an Instance of this Class
     * 
     * @return Application 
     */
    public static PersistenceManager getInstance(){
        if (persistenceManager == null)
        	persistenceManager = new PersistenceManager();
        return persistenceManager;
    }
    
    /**
     * Persists an Object
     * 
     * @param o Object to persist
     */
    public void persistObject(Object o){
    	
    	EntityTransaction tx = em.getTransaction();
    	tx.begin();
    	em.merge(o);
    	tx.commit();
    }
    
    /**
     * Delets an Object
     * 
     * @param o Object to delete
     */
    public void deleteObject(Category o){

    	EntityTransaction tx = em.getTransaction();
    	tx.begin();
    	em.remove(o);
    	tx.commit();
    }
    
    /**
     * Close the PersistenceManager
     */
    public static void close(){
    	
    	if(em != null){
    		em.close();
    	}
    	if(emf != null){
    		emf.close();
    	}
    }
}

In der folgenden Version hab ich mal für jeden Commit einen neuen EntityManager erzeugt. Auch hier wird nicht gelöscht allerdings bekomme ich hier den Fehler:


java.lang.IllegalArgumentException: Removing a detached instance

Aber dieser Fehler dürfte doch eigentlich gar nicht kommen, da ich doch mit merge vor dem dafür sorge das die Category auf keinen Fall detached ist?!

public class PersistenceManager{
	
	private static PersistenceManager persistenceManager = null;
	
	private static EntityManagerFactory emf = null;
	private static EntityManager em = null;
	
	private PersistenceManager(){
    	
		emf = Persistence.createEntityManagerFactory("JPAConfig");
		em = emf.createEntityManager();
    }
 
    /**
     * Creates an Instance of this Class
     * 
     * @return Application 
     */
    public static PersistenceManager getInstance(){
        if (persistenceManager == null)
        	persistenceManager = new PersistenceManager();
        return persistenceManager;
    }
    
    /**
     * Persists an Object
     * 
     * @param o Object to persist
     */
    public void persistObject(Object o){
    	
    	em.close();
    	em = emf.createEntityManager();
    	EntityTransaction tx = em.getTransaction();
    	tx.begin();
    	em.merge(o);
    	tx.commit();
    }
    
    /**
     * Delets an Object
     * 
     * @param o Object to delete
     */
    public void deleteObject(Category o){

    	this.persistObject(o);
    	
    	em.close();
    	em = emf.createEntityManager();
    	EntityTransaction tx = em.getTransaction();
    	tx.begin();
    	em.remove(o);
    	tx.commit();
    }
    
    /**
     * Close the PersistenceManager
     */
    public static void close(){
    	
    	if(em != null){
    		em.close();
    	}
    	if(emf != null){
    		emf.close();
    	}
    }
}

Aber dieser Fehler dürfte doch eigentlich gar nicht kommen, da ich doch mit merge vor dem dafür sorge das die Category auf keinen Fall detached ist?!

Das merge findet aber in einem anderen Persistenzkontext (=EntityManager) statt, da ja in

persistObject(o)

eine andere Instanz von EntityManager erzeugt wird.

So sollte es gehen:

em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.merge(o);
em.remove(o);
tx.commit();

btw, irgendwie kann ich keinen Java-Code einfügen.

    public void deleteObject(Category o){

    	em.close();
    	em = emf.createEntityManager();
    	EntityTransaction tx = em.getTransaction();
    	tx.begin();
    	em.merge(o);
    	em.remove(o);
    	tx.commit();
    	em.close();
    }

Auch hier bekomm ich den detached-Error. Des Weiteren müsste es nicht reichen wenn ich wie in der Ausgangsklasse im Konstruktor einmal die Factory und den EntityManager definiere und dann als Klassenvariable nutze? Wunder mich das ich in dieser ersten Version einen detached Error bekomme. Hab zur Sicherheit gerade wieder ein CascadingType.Merge an den parent gesetzt (zum testen ob der vielleicht detached ist) leider auch dort kein Erfolg… wieder kam der Detached-Error.

BTW: Einfach selber die java Tags setzen dann wirds als Javacode angezeigt :slight_smile:

[JAVA ] [ /JAVA]

Ah, thx.

Hier mal meine vollständige Testklasse. Funktioniert dieses Beispiel?

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

import pwm.domain.Category;

public class Test {

    public static void main( String[] args ) {

        new Test().run();
    }

    private EntityManager em;
    private EntityManagerFactory emf;

    private void delete() {

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        Query query = em.createQuery( "select c from Category c where c.name = 'A2'" );
        Category c = ( Category )query.getSingleResult();
        em.remove( c );

        tx.commit();
    }

    private void populate() {

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        Category root = new Category( "A" );
        Category c;

        c = new Category( "A1" );
        root.addChild( c );
        c.addChild( new Category( "A1.1" ) );
        c.addChild( new Category( "A1.2" ) );
        c.addChild( new Category( "A1.3" ) );

        c = new Category( "A2" );
        root.addChild( c );
        c.addChild( new Category( "A2.1" ) );
        c.addChild( new Category( "A2.2" ) );
        c.addChild( new Category( "A2.3" ) );

        c = new Category( "A3" );
        root.addChild( c );
        c.addChild( new Category( "A3.1" ) );
        c.addChild( new Category( "A3.2" ) );
        c.addChild( new Category( "A3.3" ) );

        em.merge( root );

        tx.commit();
    }

    private void run() {
        setup();
        populate();
        delete();
    }

    private void setup() {
        emf = Persistence.createEntityManagerFactory( "pwm" );
        em = emf.createEntityManager();
    }
}

Ja diese beispiel funktioniert einwandfrei. Auch bei meinem Code funktioniert das persistieren und abfragen einwandfrei nur das löschen nicht…

Verwendest Du noch die JoinTable (siehe 1. Post) oder ist die Baumstruktur auf einer Tabelle abgebildet?
JoinTable ist für eine Baumstruktur nicht notwendig, macht es viel zu kompliziert.

Mein Entity sieht aktuell so aus:

@Entity
public class Category{
	
	 @Id @GeneratedValue
	 private long id;
	 private String name;
	 private int type;
	 @ManyToOne
	 private Category parent;  
	 @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
	 private List<Category> childCategory = new ArrayList<Category>();

Wenn ich das Entity so ändere, dass auch beim Parent ein cascading.All steht funktioniert das Löschen. Nur wird hier natürlich alles gelöscht (Der komplette Tree).

@Entity
public class Category{
	
	 @Id @GeneratedValue
	 private long id;
	 private String name;
	 private int type;
	 @ManyToOne(cascade = CascadeType.ALL)
	 private Category parent;  
	 @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
	 private List<Category> childCategory = new ArrayList<Category>();

Genau deshalb ist @ManyToOne(cascade = CascadeType.ALL) wenig sinnvoll.

Der auf den ersten Blick einzige Unterschied zu meinem Beispiel ist doch das zusätzliche Attribut private int type;
Vorschlag: erweitere das (funktionierende!) Beispiel nach und nach, bis es Deine Anforderungen erfüllt.

Ich lese denn Tree halt mit Hibernate aus der DB aus und damit wird in einer SWT-GUI ein Tree aufgespannt. Dort kann ich dann auch alle Elemente einen Rechtsklick machen und auf löschen klicken. Dann wird das entprechende Element an den PersistenceManager zum löschen übergeben. Das das richtige Element übergeben wird hab ich schon geprüft…

Ich wollte damit nur sagen, dass das Löschen ja wie gewünscht funktioniert, wenn das Cascading an der @OneToMany-Beziehung (und nur dort) annotiert ist. Und es muss irgendwelche Unterschiede dazu in der Implementierung, die nicht funktioniert, geben. Also: vergleichen und so wie in dem funktionierenden Beispiel abändern. Vllt. hakt’s ja an irgendwas Subtilem, wie z.B. den Annotationen @Table, @Column usw.

Mein Remove im PersistenceManager kann ja anscheinend nicht das Problem sein funktioniert ja wenn bei beiden CascadingType.All steht. Die Category.java hab ich nun eins zu eins von dir kopiert. Beim Debuggen wurde angezeigt das an die Remove die richtige Entity übergeben wird. Trotzdem löscht er nicht. Bin mit meinem Latein am Ende.

Ohne den gesamten Code zu sehen ist die Ferndiagnose natürlich schwierig.

So ich habe das ganze nun nochmal versucht. Allerdings ohne Gui (weil ich dachte die gui behindert iwas). Das folgende ist der komplette Code. Mein Programm besteht nur aus den 3 Klassen. Das Hinzufügen funktioniert einwandfrei nur das Löschen geht immern och nicht.

Hier mein Entity

@Entity
public class Category implements Serializable{
	
	@Id @GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	private String name;
	private int type;
	@ManyToOne
	private Category parent;
	@OneToMany(mappedBy="parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	private List<Category> children = new ArrayList<Category>();
	
	public Category(){
		
	}
	
	public Category(String name){
		this();
		this.setName(name);
	}
	
	public Category(String name, int type){
		this(name);
		this.setType(type);
	}
	
	public long getId() {
		return id;
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	public int getType() {
		return type;
	}
	
	public void setType(int type) {
		this.type = type;
	}
	
	public Category getParent() {
		return parent;
	}

	public void setParent(Category parent) {
		this.parent = parent;
	}

	public void addChild(Category child){
		child.setParent(this);
		children.add(child);
	}
}

PerstenceManager

public class PersistenceManager{
	
	private static PersistenceManager instance = null;
	
	private static EntityManagerFactory emf = null;
	
	
	private PersistenceManager(){
		emf = Persistence.createEntityManagerFactory("JPAConfig");
	}
	
    public static PersistenceManager getInstance() {
        if (instance == null) {
            instance = new PersistenceManager();
        }
        return instance;
    }
    
    public <T> T persistObject(T object){
    	
    	EntityManager em = emf.createEntityManager();
    	
    	em.getTransaction().begin();
    	T result = em.merge(object);
    	em.getTransaction().commit();
    	em.close();
    	
    	return result;
    }
    
    public <T> void removeObjectById(Class<T> c, long id){
    	
    	EntityManager em = emf.createEntityManager();
    	
    	T object = em.find(c, id);
    	
    	em.getTransaction().begin();
    	em.remove(object);
    	em.getTransaction().commit();
    	em.close();
    	
    }
}

Und die Main

public class Launcher{
	
	public static void main(String[] args){
		
		PersistenceManager pm = PersistenceManager.getInstance();
		
		Category c = new Category("Test 1", 1);
		Category c2 = new Category("Test 1_1", 1);
		c.addChild(c2);
		pm.persistObject(c);
		pm.removeObjectById(Category.class, 2);
	}
}

Der Fehler ist immer noch der gleiche: Das Object wird nicht gelöcht (Es gibt aber auch keinen Fehler). Die Id 2 ist der Child c2 (und der bekommt auch immer die Id2)

Hallo, nicht dass Du denkst, ich antworte nicht mehr: bin grad erst aus dem Urlaub zurück. Ich schau mir das in den nächsten Tagen mal an.

Beim Löschen in dieser Form gibt es sehr wohl einen Fehler (beim em.getTransaction().commit()):

...
Caused by: org.hibernate.ObjectDeletedException: deleted entity passed to persist:```

Klar, die Methode

```    public <T> void removeObjectById(Class<T> c, long id){
       
        EntityManager em = emf.createEntityManager();
       
        T object = em.find(c, id);
       
        em.getTransaction().begin();
        em.remove(object);
        em.getTransaction().commit();
        em.close();
       
    }

löscht ja nur den Kindknoten, ohne ihn aber bei seinem Elternknoten “abzumelden”.
Deshalb in der Category die Methode

        child.setParent( null );
        children.remove( child );
    }

rein und das Löschen (im Persistencemanager) mit


        EntityManager em = emf.createEntityManager();

        Category child = em.find( Category.class, childId );
        Category parent = child.getParent();
        if( parent != null ) {
            parent.removeChild( child );
        }
        em.getTransaction().begin();
        em.remove( child );
        em.getTransaction().commit();
        em.close();
    }