Datenstruktur mit JPA

Ich versuche gerade folgende Datenstruktur mittels JPA zu speichern:

Entity Besetellung

package de.fSoftware.basic.model;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.*;

@Entity
@Table
public class Bestellung  implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	private String sessionId;
	
	//@OneToOne(cascade=CascadeType.MERGE)
	private Benutzer benutzer = new Benutzer();
	
	@ManyToMany(cascade=CascadeType.MERGE, fetch = FetchType.EAGER, mappedBy = "produkt")
	private List<WarenkorbItem> produkteWarenkorb1= new ArrayList<WarenkorbItem>();
		
	public Bestellung() {
	
	}

	public long getId(){
		return this.id;
		
	}
	
	public void setId(long aId){
		this.id = aId;
		
	}
	
	public String getSessionId(){
		return this.sessionId;
		
	}
	
	public void setSessionId(String aSessionId){
		this.sessionId = aSessionId;
		
	}
	
	public Benutzer getBenutzer(){
		return this.benutzer;
		
	}
	
	public void setBenutzer(Benutzer aBenutzer){
		this.benutzer = aBenutzer;
		
	}
	
	public List<WarenkorbItem> getProdukteWarenkorb1(){
		return this.produkteWarenkorb1;
		
	}
	
	public void setProdukteWarenkorb1(List<WarenkorbItem> aProdukteWarenkorb){
		this.produkteWarenkorb1 = aProdukteWarenkorb;
		
	}
}

Entity WarenkorbItem:

package de.fSoftware.basic.model;

import java.io.Serializable;
import java.text.DecimalFormat;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;


@Entity
@Table
public class WarenkorbItem implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	private int produktAnzahl;
	
	@ManyToOne(cascade = CascadeType.MERGE)
	private Produkt produkt;
	
	private double posPreis;
	
	public WarenkorbItem() {

	}
	
	public long getId(){
		return this.id;
		
	}
	
	public void setId(long aId){
		this.id = aId;
		
	}
	
	public double getPosPreis(){
		
		
		double produktPreis = this.produkt.getEp();
		double pos = produktPreis * this.produktAnzahl; 
		
	    double roundOff = Math.round(pos * 100.0) / 100.0;

		return roundOff;
		
	}
	
	public void setPosPreis(double aPosPreis){
		this.posPreis = aPosPreis;
		
	}
	
	public int getProduktAnzahl(){
		return this.produktAnzahl;
		
	}
	
	public void setProduktAnzahl(int aProduktAnzahl){
		this.produktAnzahl = aProduktAnzahl;
		
	}
	
	public Produkt getProdukt(){
		return this.produkt;
		
	}
	
	public void setProdukt(Produkt aProdukt){
		this.produkt = aProdukt;
		
	}

}

Entity Produkt:

package de.fSoftware.basic.model;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Date;

import javax.persistence.*;

@Entity
@Table(name="PRODUKT", uniqueConstraints={@UniqueConstraint(
		columnNames = "PRODUKTNAME")})
public class Produkt {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	@ManyToOne(cascade=CascadeType.MERGE)
	private ProduktKategorie produktKategorie;
	
	private double eP;
	
	@Column(name = "PRODUKTNAME")
	private String produktName;	
	private String produktBeschreibung;
	private Date verkaufsStart;
	private int bereitsVerkauft;
	private String bildURL;
	
	public Produkt() {
	
	}

	public long getId(){
		return this.id;
		
	}
	
	public void setId(long aId){
		this.id = aId;
		
	}
	
	
	public ProduktKategorie getProduktKategorie(){
		return this.produktKategorie;
		
	}
	
	public void setProduktKategorie(ProduktKategorie aProduktKategorie){
		this.produktKategorie = aProduktKategorie;
		
	}
	
	public double getEp(){
		return eP;
		
	}
	
	public void setEp(double aEp){
		this.eP = aEp;
		
	}
	
	public String getProduktName(){
		return this.produktName;
		
	}
	
	public void setProduktName(String aProduktName){
		this.produktName = aProduktName;
		
	}
	
	public String getProduktBeschreibung(){
		return this.produktBeschreibung;
		
	}
	
	public void setProduktBeschreibung(String aProduktBeschreibung){
		this.produktBeschreibung = aProduktBeschreibung;
		
	}
	
	public Date getVerkaufsStart(){
		return this.verkaufsStart;
		
	}
	
	public void setVerkaufsStart(Date aVerkaufsStart){
		this.verkaufsStart = aVerkaufsStart;
		
	}
	
	public int getBereitsVerkauft(){
		return this.bereitsVerkauft;
		
	}
	
	public void setBereitsVerkauft(int aBereitsVerkauft){
		this.bereitsVerkauft = aBereitsVerkauft;
		
	}
	
	public String getBildURL(){
		return this.bildURL;
		
	}
	
	public void setBildURL(String aBildURL){
		this.bildURL = aBildURL;
		
	}
}

Hier die Methode um die Daten zu speichern:

	public void saveBestellung(){

		Benutzer currentBenutzer = new Benutzer();
		currentBenutzer.setBenutzerName(benutzer.getBenutzerName());
		currentBenutzer.setPasswort(benutzer.getPasswort());
		currentBenutzer.setEMail(benutzer.getEMail());

		Bestellung currentBestellung = new Bestellung();
		
		currentBestellung.setSessionId(this.sessionId);
		currentBestellung.setBenutzer(currentBenutzer);
		
		ArrayList<WarenkorbItem> currentItems = new ArrayList<WarenkorbItem>();
		currentItems.addAll(produkteWarenkorb)
		
		for(int a=0;a<currentItems.size();a++){
			System.out.println("currentItems Warenkorb: "+currentItems.get(a).getProdukt().getProduktName()
					+" "+currentItems.get(a).getProdukt().getId()+" "+currentItems.get(a).getProduktAnzahl());
		}	
		
		currentBestellung.setProdukteWarenkorb1(currentItems);
		
		try {
		
			String message = bestellungRepository.saveBestellung(currentBestellung);
			
			System.out.println("saveBestellung Message: "+message);
			
		} catch (NotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SystemException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	public String saveBestellung(Bestellung e)throws NotSupportedException, SystemException  {
		
		String message = new String();
		
		utx.begin();
		try {


		entityManager.persist(e);

		message = "Bestellung gespeichert.";
				
		utx.commit();
		
	} catch (Exception ex) {
		message = "Fehler. Bestellung nicht gespeichert.";
		
		ex.printStackTrace();
	}
		
		return message;

Ich möchte damit Bestellinformationen speichern. Ich erhalte so wie es jetzt ist keine Fehlermeldung. Sondern ein ‘Bestellung gespeichert.’ Allerdings werden die WarenkorbItems nicht mitgespeicht.

Kann man sich mit JPA auch das generierte SQL-Statement ausgeben lassen wie bei Hibernate? Eventuell gibt das schon Aufschluss

Gespeichert wird die Bestellung, aber es gibt keine (JPA-)Beziehung Bestellung<-WarenkkorbItem.

Kannst du mir sagen wie eine solche JPA Beziehung auszusehen hat?

Ich dachte die Annotation:

@ManyToMany(cascade=CascadeType.MERGE, fetch = FetchType.EAGER, mappedBy = "produkt")
private List<WarenkorbItem> produkteWarenkorb1= new ArrayList<WarenkorbItem>();

stellt die Beziehung zu WarenkorbItem her?

Im Prinzip schon, und die WarenkorbItems würden evtl. auch gespeichert, wenn es auch noch eine kaskadierte Beziehung Produkt->WarenkorbItem gäbe (Vemutung, habe ich nicht ausprobiert).

Aber eigentlich besteht doch eine 1:n-Beziehung Bestellung->WarenkorbItem, sonst kann man ja nie herausfinden, welche Items zu welcher Bestellung gehören.

Also in die Entity WarenkorbItem einen Foreign Key auf die Bestellung rein (z.B. “BESTELLUNG_ID”):

@JoinColumn(name = "BESTELLUNG_ID")
private Bestellung bestellung;

...

public void setBestellung(Bestellung bestellung){
	this.bestellung = bestellung;
}

Und in der Bestellung:

private List<WarenkorbItem> warenkorb = new ArrayList<WarenkorbItem>();

...

public void addWarenkorbItem(WarenkorbItem warenkorbItem){
	warenkorbItem.setBestellung(this);
	warenkorb.add(warenkorbItem);
}

public void addAll(List<WarenkorbItem> items){
	for(WarenkorbItem item:items){
		addWarenkorbItem(item);
	}
}```

Dann werden die Items auch gespeichert.

Ich habe noch ein generelles Verständisproblem.

Ich habe folgende Methode, um eine Bestellung zu speichern:

		Benutzer currentBenutzer = new Benutzer();
		currentBenutzer.setBenutzerName(benutzer.getBenutzerName());
		currentBenutzer.setPasswort(benutzer.getPasswort());
		currentBenutzer.setEMail(benutzer.getEMail());

		Bestellung currentBestellung = new Bestellung();
		
		currentBestellung.setSessionId(this.sessionId);
		currentBestellung.setBenutzer(currentBenutzer);
		
		for(int a=0;a<produkteWarenkorb.size();a++){
			
			// hier erstelle ProduktKategorie
			ProduktKategorie produktKategorie = new ProduktKategorie();
			produktKategorie.setProduktKategorieName(produkteWarenkorb.get(a).getProdukt().getProduktKategorie().getProduktKategorieName());	

			//hier erstelle Produkt
			Produkt produkt = new Produkt();
			produkt.setProduktName(produkteWarenkorb.get(a).getProdukt().getProduktName());
			produkt.setBildURL(produkteWarenkorb.get(a).getProdukt().getBildURL());
			produkt.setEp(produkteWarenkorb.get(a).getProdukt().getEp());
			produkt.setBereitsVerkauft(produkteWarenkorb.get(a).getProdukt().getBereitsVerkauft());
			produkt.setProduktBeschreibung(produkteWarenkorb.get(a).getProdukt().getProduktBeschreibung());
			produkt.setVerkaufsStart(new GregorianCalendar().getTime());
			produkt.setProduktKategorie(produktKategorie);
			
			//hier erstelle WarenkorbItem
			WarenkorbItem aitem = new WarenkorbItem();
			aitem.setPosPreis(produkteWarenkorb.get(a).getPosPreis());
			aitem.setProduktAnzahl(produkteWarenkorb.get(a).getProduktAnzahl());
			aitem.setBestellung(currentBestellung);
			aitem.setProdukt(produkt);

			currentBestellung.addWarenkorbItem(aitem);
			
		}
		
		try {
			
			String message = bestellungRepository.saveBestellung(currentBestellung);
			
			System.out.println("saveBestellung Message: "+message);
			
		} catch (NotSupportedException e) {
			e.printStackTrace();
			
		} catch (SystemException e) {
			e.printStackTrace();
			
		}
}

Das Speichern einer Bestellung funktioniert soweit schon. Allerdings wird bei jedem Speichervorgang ein neues Produkt erzeugt und in die entsprechende Tabelle ‘Produkt’ eingetragen.

Das hat zur Folge, dass die Produkte Tabelle mit jedem speichern einer Bestellung automatisch mitwächst.

Ich denke ich bräuchte eigentlich eine Referenz auf ein bestehendes Produkt und nicht gleich ein neues Produkt.

Wundert es dich wenn du in Zeile 19 schreibst
Produkt produkt = new Produkt();

dass dann ein neues Produkt in der DB landet?

Woher stammt das Produkt aus folgendem Aufruf
produkteWarenkorb.get(a).getProdukt()?

Was passiert wenn Zeile 33 so aussieht?
aitem.setProdukt(produkteWarenkorb.get(a).getProdukt());

Wenn z.B.
produkteWarenkorb.get(a).getProdukt().getClass() != Prokukt.class,

dann mußt du das Produkt mit Hilfe des entsprechenden EntityManagers anhand der ID oder sonstiger Kriterien aus der DB Laden und der Bestellung hinzufügen.

Ein new Produkt, bedeutet so gut wie immer dass es ein neues Produkt gibt.

Das Speichern der Bestellungen funktioniert soweit. Allerdings bekomme ich eine Fehlermeldung beim Löschen eines Produkts.

Cannot delete or update a parent row: a foreign key constraint fails (`name`.`WarenkorbItem`, CONSTRAINT `FK8011A4384F05CC7` FOREIGN KEY (`produkt_id`) REFERENCES `Produkt` (`id`))

Hier die Entities:

package de.fSoftware.basic.model;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.*;

@Entity
@Table
public class Bestellung  implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	private String sessionId;
	
	private Benutzer benutzer = new Benutzer();
	
	@OneToMany(fetch=FetchType.EAGER, cascade = {CascadeType.DETACH}, mappedBy = "bestellung")
	private List<WarenkorbItem> produkteWarenkorb1 = new ArrayList<WarenkorbItem>();
		
	public Bestellung() {
	
	}

	public void addWarenkorbItem(WarenkorbItem warenkorbItem){
	    warenkorbItem.setBestellung(this);
	    produkteWarenkorb1.add(warenkorbItem);
	}
	 
	public void addAll(List<WarenkorbItem> items){
	    for(WarenkorbItem item:items){
	        addWarenkorbItem(item);
	    }
	}
	
	public long getId(){
		return this.id;
		
	}
	
	public void setId(long aId){
		this.id = aId;
		
	}
	
	public String getSessionId(){
		return this.sessionId;
		
	}
	
	public void setSessionId(String aSessionId){
		this.sessionId = aSessionId;
		
	}
	
	public Benutzer getBenutzer(){
		return this.benutzer;
		
	}
	
	public void setBenutzer(Benutzer aBenutzer){
		this.benutzer = aBenutzer;
		
	}
	
	public List<WarenkorbItem> getProdukteWarenkorb1(){
		return this.produkteWarenkorb1;
		
	}
	
	public void setProdukteWarenkorb1(List<WarenkorbItem> aProdukteWarenkorb){
		this.produkteWarenkorb1 = aProdukteWarenkorb;
		
	}
}
package de.fSoftware.basic.model;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Date;

import javax.persistence.*;

@Entity
@Table
public class Produkt implements Serializable{

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	@ManyToOne(cascade = {CascadeType.DETACH})
	private ProduktKategorie produktKategorie;
	
	private double eP;
	
	private String produktName;	
	private String produktBeschreibung;
	private Date verkaufsStart;
	private int bereitsVerkauft;
	private String bildURL;
	
	public Produkt() {
	
	}

	public long getId(){
		return this.id;
		
	}
	
	public void setId(long aId){
		this.id = aId;
		
	}
	
	
	public ProduktKategorie getProduktKategorie(){
		return this.produktKategorie;
		
	}
	
	public void setProduktKategorie(ProduktKategorie aProduktKategorie){
		this.produktKategorie = aProduktKategorie;
		
	}
	
	public double getEp(){
		return eP;
		
	}
	
	public void setEp(double aEp){
		this.eP = aEp;
		
	}
	
	public String getProduktName(){
		return this.produktName;
		
	}
	
	public void setProduktName(String aProduktName){
		this.produktName = aProduktName;
		
	}
	
	public String getProduktBeschreibung(){
		return this.produktBeschreibung;
		
	}
	
	public void setProduktBeschreibung(String aProduktBeschreibung){
		this.produktBeschreibung = aProduktBeschreibung;
		
	}
	
	public Date getVerkaufsStart(){
		return this.verkaufsStart;
		
	}
	
	public void setVerkaufsStart(Date aVerkaufsStart){
		this.verkaufsStart = aVerkaufsStart;
		
	}
	
	public int getBereitsVerkauft(){
		return this.bereitsVerkauft;
		
	}
	
	public void setBereitsVerkauft(int aBereitsVerkauft){
		this.bereitsVerkauft = aBereitsVerkauft;
		
	}
	
	public String getBildURL(){
		return this.bildURL;
		
	}
	
	public void setBildURL(String aBildURL){
		this.bildURL = aBildURL;
		
	}
}
package de.fSoftware.basic.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table
public class ProduktKategorie implements Serializable{

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	private String produktKategorieName;
	
	public ProduktKategorie() {

	}

	public long getId(){
		return this.id;
		
	}
	
	public void setId(long aId){
		this.id = aId;
		
	}
	
	public String getProduktKategorieName(){
		return this.produktKategorieName;
		
	}
	
	public void setProduktKategorieName(String aProduktKategorieName){
		this.produktKategorieName = aProduktKategorieName;
		
	}
	
}

package de.fSoftware.basic.model;

import java.io.Serializable;
import java.text.DecimalFormat;

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.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;


@Entity
@Table
public class WarenkorbItem implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	private int produktAnzahl;
	
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "BESTELLUNG_ID")
	private Bestellung bestellung;
	
	@ManyToOne(cascade = {CascadeType.DETACH})
	private Produkt produkt;
	
	private double posPreis;
	
	public WarenkorbItem() {

	}
	
	public void setBestellung(Bestellung abestellung){
	    this.bestellung = abestellung;
	}
	
	public long getId(){
		return this.id;
		
	}
	
	public void setId(long aId){
		this.id = aId;
		
	}
	
	public double getPosPreis(){

		double produktPreis = this.produkt.getEp();
		double pos = produktPreis * this.produktAnzahl; 
		
	    double roundOff = Math.round(pos * 100.0) / 100.0;

		return roundOff;

	//	return 10;
		
	}
	
	public void setPosPreis(double aPosPreis){
		this.posPreis = aPosPreis;
		
	}
	
	public int getProduktAnzahl(){
		return this.produktAnzahl;
		
	}
	
	public void setProduktAnzahl(int aProduktAnzahl){
		this.produktAnzahl = aProduktAnzahl;
		
	}
	
	public Produkt getProdukt(){
		return this.produkt;
		
	}
	
	public void setProdukt(Produkt aProdukt){
		this.produkt = aProdukt;
		
	}

}

Kann mir jemand sagen wie ich die Fehlermeldung wegbekomme.

Hier noch die Methode Löschen

	@SuppressWarnings("unchecked")
	public void deleteArtikelEM(long aProduktId) throws IllegalStateException, SecurityException, SystemException, NotSupportedException {
		
			utx.begin();
			try {			
				 Produkt produkt = entityManager.find(Produkt.class, aProduktId);

			entityManager.remove(produkt);
			
			utx.commit();
		} catch (Exception ex) {
			utx.rollback();
			ex.printStackTrace();
		}
	}

Der Warenkorb referenziert Produkte. Daher kann ein Produkt nicht gelöscht werden, wenn es sich noch in irgendeinem Warenkorb befindet.
Um mit dem Produkt auch sämtliche Referenzen auf das Produkt zu löschen, musst du in beim WarenkorbItem die Annotation zur Referenz (ManyToMany?) auf das Produkt anpassen und zwar um cascade = CascadeType.REMOVE.

Du solltest die ganzen RuntimeExceptions nicht zur throws Klausel hinzufügen. Bei der JavaDoc jedoch solltest du für die Exceptions jeweils eine @throws Zeile einfügen.

Warum nicht?

Das Problem an so Shops ist, dass sich das Angebot stetig ändert.
Alleine wenn sich der Preis ändert, ändern sich damit u.U. bereits getätigte Bestellungen etc.

Das möchte man in der Regel nicht haben.

Die einfachste Lösung in solchen Fällen sind Persistente-Datenstrukturen.

D.h. du fügst deinen Prokuten ein boolean Flag zur Verfügung imAngebot.

Statt zu löschen wird dieses nun z.B. auf false gesetzt.

Oder du erstellst dir zwei Listen,

List<Produkt> archivierteProdukte = früherVerfügbareProdukte();```

Statt zu löschen wird ein Produkt einfach aus einer Liste entfernt und der anderen hinzugefügt.

Bei einer Preisänderung kannst du so vorgehen, dass du eine Kopie des Produkts erstellst und dort den neuen Preis setzst.
Danach kommt die Kopie zu produkte und das mit dem alten Preis wird von produkte zu archivierteProdukte verschoben.


Statt einem boolean kann man für ein Produkt z.B. auch zwei Daten (Datum) nehmen, von bis.

Mein Fehler. Ich behaupte das Gegenteil, weil das Hinzufügen der Dokumentation dient. Ich hatte noch im Kopf, dass auch RuntimeExceptions gefangen werden müssen, wenn sie in der Signatur aufgeführt sind - was allerdings nicht richtig ist.
Von daher: meinen vorigen Kommentar zum Thema throws einfach ignorieren :wink: