ManyToMany löscht zugewiesene Dateien

Andyx1975

Mitglied
Hallo,

ich habe EclipseLink auf die Version 2.5.1 aktualisiert, seitdem habe ich das Problem, dass das Löschen meiner ManyToMany Releations nicht mehr funktioniert. Ich habe zwei Entity Klassen... einmal die Klasse DTour und DSupplier.

Beziehung in DTour:
Code:
@ManyToMany( fetch = FetchType.LAZY )
    @JoinTable( name = "assignments_tours_suppliers", joinColumns = @JoinColumn( name = "tour_id", referencedColumnName = "tour_id" ), inverseJoinColumns = @JoinColumn( name = "supplier_id", referencedColumnName = "supplier_id" ) )
    private List<DSupplier> suppliers;


Beziehung in DSupplier:
Code:
@ManyToMany( mappedBy = "suppliers", fetch = FetchType.LAZY )
    private List<DTour> tours;


Die Methode zum löschen
Code:
@DELETE
    @Path( "delete/supplier/{id}" )
    @Produces( MediaType.APPLICATION_XML )
    public DSupplier deleteSupplier( @PathParam( "id" ) long id )
    {

        DSupplier result = null;
        EntityManager em = factory.createEntityManager();

        try
        {
            EntityTransaction entr = em.getTransaction();
            boolean committed = false;
            entr.begin();
            try
            {
                result = em.find( DSupplier.class, id );
                em.remove( result );
                entr.commit();
                committed = true;
            }
            finally
            {
                if ( !committed )
                    entr.rollback();
            }
        }
        finally
        {
            em.close();
        }

        return result;
    }

Früher war es so, wenn ich einen Supplier der Tour zugeordnet hatte, konnte ich den Supplier aufgrund der Zuweisung nicht mehr löschen. Jetzt lässt er sich löschen und die Zuweisung wird aus dem JOIN Table einfach gelöscht. Wie kann ich das unterbinden?

Danke und Gruß
Andy
 

saftmeister

Nutze den Saft!
Nur damit ich es richtig verstehe: Du willst, dass ein Datensatz in der Kreuz-Tabelle bestehen bleibt, obwohl der Primärschlüssel, auf den der Fremdschlüssel zeigt, nicht mehr existiert?
 

Andyx1975

Mitglied
Vielen Dank erstmal, dass du dich der Sache annimmst... Also im Grunde möchte ich den folgenden Zustand erreichen:

1. Der Supplier ist der Tour zugewiesen und wenn der Benutzer versucht, den Supplier zu löschen, soll das Löschen des Suppliers nicht vollzogen werden, da der Supplier der Tour zugewiesen ist.

2. Löscht der Benutzer die Tour, so soll die Tour gelöscht werden, die Beziehung zwischen beiden aufgehoben werden... der Supplier soll aber nicht gelöscht werden.

Danke und Gruß
Andy
 

saftmeister

Nutze den Saft!
Ok, welche Datenbank liegt der App zugrunde? Könnte in der Tabelle ein Delete kaskadierend eingestellt sein? Bei MySQL bspw könnte man den Fremdschlüssel als "ON DELETE SET NULL" definieren.

Ansonsten würde ich den üblichen Verdächtigen (cascade) dafür untersuchen:

Java:
@ManyToMany( mappedBy = "suppliers", fetch = FetchType.LAZY, cascade=....)
private List<DTour> tours;
 

Andyx1975

Mitglied
Also ist ne postgresql 9.3 DB. Mit cascade bin ich nicht weit gekommen bis jetzt. Die Tour Seite verhält sich ja korrekt. Wenn muss ich was auf der Supplier Seite machen... oder?
 

saftmeister

Nutze den Saft!
Was hast du mit cascade versucht? Ich würde mal schauen, was passiert, wenn der Typ auf CascadeType.MERGE, PERSIST und REFRESH steht.

Auch bei PostgreSQL gibt es die Constraint-Option "ON DELETE SET NULL".
 

Andyx1975

Mitglied
Die Contraints sehen wie folgt aus:

Code:
CREATE TABLE assignments_tours_suppliers
(
  supplier_id bigint NOT NULL,
  tour_id bigint NOT NULL,
  CONSTRAINT assignments_tours_suppliers_pkey PRIMARY KEY (supplier_id, tour_id),
  CONSTRAINT fk_assignments_tours_suppliers_supplier_id FOREIGN KEY (supplier_id)
      REFERENCES suppliers (supplier_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_assignments_tours_suppliers_tour_id FOREIGN KEY (tour_id)
      REFERENCES tours (tour_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE
);
ALTER TABLE assignments_tours_suppliers
  OWNER TO postgres;
 

saftmeister

Nutze den Saft!
Hmm, um ehrlich zu sein, ich hab noch zu wenig Erfahrung mit JPA um deine Frage beantworten zu können, ohne es selbst auszuprobieren. Ich muss erstmal ein Projekt aufsetzen und es selbst testen. Ich wäre aber davon ausgegangen, dass es mit den CascadeType-Annotationsattributen zu bewerkstelligen wäre. Interessant finde ich, dass es erst seit dem Update auf EclipseLink 2.5.1 so ist. Welche Version hattest du den vorher im Einsatz?
 

saftmeister

Nutze den Saft!
So, ich hab jetzt mal bisschen rum experimentiert. Muss zugeben, dass ich von Hibernate etwas verwöhnt bin und reines JPA über Entity-Manager nur vom Lesen her bekannt war. Aber im Anhang findest du meine Solution, die funktioniert. Anschließend ist nur eine Tour und ein Supplier in der DB vorhanden.

SQL:
--
-- PostgreSQL database dump
--

-- Dumped from database version 9.3.4
-- Dumped by pg_dump version 9.3.4
-- Started on 2014-06-14 22:07:16

SET statement_timeout = 0;
SET lock_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;

--
-- TOC entry 176 (class 3079 OID 11750)
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
--

CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;


--
-- TOC entry 1965 (class 0 OID 0)
-- Dependencies: 176
-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:
--

COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';


SET search_path = public, pg_catalog;

SET default_tablespace = '';

SET default_with_oids = false;

--
-- TOC entry 173 (class 1259 OID 16423)
-- Name: assignments_tours_suppliers; Type: TABLE; Schema: public; Owner: testing; Tablespace:
--

CREATE TABLE assignments_tours_suppliers (
  id integer NOT NULL,
  tour_id integer,
  supplier_id integer
);


ALTER TABLE public.assignments_tours_suppliers OWNER TO testing;

--
-- TOC entry 175 (class 1259 OID 16440)
-- Name: seq_assignment; Type: SEQUENCE; Schema: public; Owner: testing
--

CREATE SEQUENCE seq_assignment
  START WITH 1
  INCREMENT BY 1
  NO MINVALUE
  NO MAXVALUE
  CACHE 1;


ALTER TABLE public.seq_assignment OWNER TO testing;

--
-- TOC entry 174 (class 1259 OID 16438)
-- Name: seq_supplier; Type: SEQUENCE; Schema: public; Owner: testing
--

CREATE SEQUENCE seq_supplier
  START WITH 1
  INCREMENT BY 50
  NO MINVALUE
  NO MAXVALUE
  CACHE 1;


ALTER TABLE public.seq_supplier OWNER TO testing;

--
-- TOC entry 171 (class 1259 OID 16403)
-- Name: seq_tour; Type: SEQUENCE; Schema: public; Owner: testing
--

CREATE SEQUENCE seq_tour
  START WITH 1
  INCREMENT BY 50
  NO MINVALUE
  NO MAXVALUE
  CACHE 1;


ALTER TABLE public.seq_tour OWNER TO testing;

--
-- TOC entry 172 (class 1259 OID 16411)
-- Name: supplier; Type: TABLE; Schema: public; Owner: testing; Tablespace:
--

CREATE TABLE supplier (
  id integer NOT NULL,
  name character varying
);


ALTER TABLE public.supplier OWNER TO testing;

--
-- TOC entry 170 (class 1259 OID 16395)
-- Name: tour; Type: TABLE; Schema: public; Owner: testing; Tablespace:
--

CREATE TABLE tour (
  id integer NOT NULL,
  name character varying
);


ALTER TABLE public.tour OWNER TO testing;

--
-- TOC entry 1955 (class 0 OID 16423)
-- Dependencies: 173
-- Data for Name: assignments_tours_suppliers; Type: TABLE DATA; Schema: public; Owner: testing
--

COPY assignments_tours_suppliers (id, tour_id, supplier_id) FROM stdin;
\.


--
-- TOC entry 1966 (class 0 OID 0)
-- Dependencies: 175
-- Name: seq_assignment; Type: SEQUENCE SET; Schema: public; Owner: testing
--

SELECT pg_catalog.setval('seq_assignment', 1, false);


--
-- TOC entry 1967 (class 0 OID 0)
-- Dependencies: 174
-- Name: seq_supplier; Type: SEQUENCE SET; Schema: public; Owner: testing
--

SELECT pg_catalog.setval('seq_supplier', 1951, true);


--
-- TOC entry 1968 (class 0 OID 0)
-- Dependencies: 171
-- Name: seq_tour; Type: SEQUENCE SET; Schema: public; Owner: testing
--

SELECT pg_catalog.setval('seq_tour', 1601, true);


--
-- TOC entry 1954 (class 0 OID 16411)
-- Dependencies: 172
-- Data for Name: supplier; Type: TABLE DATA; Schema: public; Owner: testing
--

COPY supplier (id, name) FROM stdin;
1902   Hans Müller
\.


--
-- TOC entry 1952 (class 0 OID 16395)
-- Dependencies: 170
-- Data for Name: tour; Type: TABLE DATA; Schema: public; Owner: testing
--

COPY tour (id, name) FROM stdin;
1552   Nach Hamburg
\.


--
-- TOC entry 1842 (class 2606 OID 16427)
-- Name: PK_ASSIGN_TOUR_SUPPL; Type: CONSTRAINT; Schema: public; Owner: testing; Tablespace:
--

ALTER TABLE ONLY assignments_tours_suppliers
  ADD CONSTRAINT "PK_ASSIGN_TOUR_SUPPL" PRIMARY KEY (id);


--
-- TOC entry 1840 (class 2606 OID 16420)
-- Name: PK_SUPPLIER_ID; Type: CONSTRAINT; Schema: public; Owner: testing; Tablespace:
--

ALTER TABLE ONLY supplier
  ADD CONSTRAINT "PK_SUPPLIER_ID" PRIMARY KEY (id);


--
-- TOC entry 1838 (class 2606 OID 16422)
-- Name: PK_TOUR_ID; Type: CONSTRAINT; Schema: public; Owner: testing; Tablespace:
--

ALTER TABLE ONLY tour
  ADD CONSTRAINT "PK_TOUR_ID" PRIMARY KEY (id);


--
-- TOC entry 1844 (class 2606 OID 16433)
-- Name: FK_ASSIGN_SUPPL_ID; Type: FK CONSTRAINT; Schema: public; Owner: testing
--

ALTER TABLE ONLY assignments_tours_suppliers
  ADD CONSTRAINT "FK_ASSIGN_SUPPL_ID" FOREIGN KEY (supplier_id) REFERENCES supplier(id);


--
-- TOC entry 1843 (class 2606 OID 16428)
-- Name: FK_ASSIGN_TOUR_ID; Type: FK CONSTRAINT; Schema: public; Owner: testing
--

ALTER TABLE ONLY assignments_tours_suppliers
  ADD CONSTRAINT "FK_ASSIGN_TOUR_ID" FOREIGN KEY (tour_id) REFERENCES tour(id);


--
-- TOC entry 1964 (class 0 OID 0)
-- Dependencies: 5
-- Name: public; Type: ACL; Schema: -; Owner: postgres
--

REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM postgres;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC;


-- Completed on 2014-06-14 22:07:16

--
-- PostgreSQL database dump complete
--

EDIT: Hab grad gemerkt, dass die Beziehungstabelle leer war, weil ich vergessen hatte, den Supplier auch an die beiden Touren anzuhängen. Nach dem ich das jetzt eingebaut habe, habe ich das Verhalten nachstellen können. Ich werde mal mit den gleichen POs das in Hibernate nachbauen.
 
Zuletzt bearbeitet:

saftmeister

Nutze den Saft!
So, ich habe das Problem verstanden und eine Lösung. Erstmal das Verständnis:

- Du hast eine Bi-Direktionale Verbindung von Supplier und Tour. Wenn du eine Tour löschen willst, holst du zunächst das Tour-Objekt aus der Datenbank (z.B. mittels Suche via Conditions oder by id). Dann führst du ein Delete auf das Objekt aus. Aber beim Laden des Objekts wird die Relation zu Supplier berücksichtig (offenbar auch, wenn FetchType.LAZY eingestellt ist). Da der CascadeType auf ALL steht (wegen rekursiven Speichern von Supplier aus), werden auch alle Supplier die an der Tour hängen gelöscht. Und das wiederum sorgt dafür, dass alle Touren, die an allen Suppliern dran hängen gelöscht werden.

Mein Vorgehen ist jetzt folgendes: Hole die Tour aus der DB. Setze die Supplier-Liste der gefundenen Tour auf null und speicher die Tour. Dann kannst du die Tour gefahrenlos löschen. Hier mein Code:

Java:
  TourDAO tourDAO = new TourDAO();

  List<Tour> results = tourDAO.findByName("Nach München");

  for (Tour result : results)
  {
  System.out.println(result);
  result.setSuppliers(null);
  tourDAO.save(result);
  tourDAO.delete(result);
  }