# Hibernate - N:1 Beziehung



## Studdi23 (1. Sep 2010)

Hallo zusammen,

arbeite mich zur Zeit in Hibernate ein und versuche mich an einigen Beispielen aus einem Buch. Und zwar habe ich zwei annotierte Klassen "Pizza" und "Order", die in einer N zu 1 Beziehung stehen, d.h. mehere Pizzen werden einer Bestellung zugeordnet und zu jeder Bestellung gehört genau eine Pizza.
Die Pizza-Klasse besitzt eine Fremdschlüsselspalte der Order-Klasse.
Nun bedeutet das für mich das eine Pizza jeweils zwei unterschiedlichen Bestellungen zugeordnet werden kann. Wenn ich also eine Pizza mit dem Namen "Salami" habe dann kann ich sie der Bestellung mit der ID=1 und der ID=2 hinzufügen. Z.B. möchte ich mittels Java-Code folgendes in die Datenbank mappen:

```
PIZZA_ID  name      ORDERS_FK
          
1         Salami        1
2         Thunfisch     1
3         Napoli        1
4         Salami        2
```
Sobald ich jedoch das gleiche Pizza-Objekt einer neuen Bestellung hinzufüge, wird der ORDER_FK dieses Objekts mit dem von der neuen Bestellung überschrieben. Beispielsweise ändert sich der Wert von ORDER_FK der Salami-Pizza mit ID=1 von 1 in 2. Das ganze sieht dann so aus:


```
PIZZA_ID  name      ORDERS_FK
          
1         Salami        2
2         Thunfisch     1
3         Napoli        1
```
Ich komme einfach nicht dahinter woran das liegen könnte. Brauche ich für ein korrektes Ergebnis eine Many-To-Many Relation?  Hier ist mein Java-Code:


```
public class TestExample {
	final static Logger logger = LoggerFactory.getLogger(TestExample.class);

	public static void main(String[] args) {

		Transaction tx = null;
                Pizza salami = new Pizza();
		salami.setName("Salami");

                Pizza thunfisch = new Pizza();
		thunfisch.setName("Thunfisch");

                Pizza napoli = new Pizza();
                napoli.setName("Napoli");

                Order order = new Order();
		order.setCreationTime(new Date());
		order.setState(OrderState.NEW);
                order.addPizza(salami );
		order.addPizza(thunfisch );

                Order order2 = new Order();
		order.setCreationTime(new Date());
		order2.setState(OrderState.NEW);
                order.addPizza(napoli);
                order.addPizza(salami);

                Session session = SessionFactoryUtil.getInstance().getCurrentSession();
		try {
			tx = session.beginTransaction();
                        session.save(pizza);
			session.save(pizza2);
                        session.save(order);
			session.save(order2
                        tx.commit();
		} catch (RuntimeException e) {
			if (tx != null && tx.isActive()) {
				try {
					tx.rollback();
				} catch (HibernateException e1) {
					logger.debug("Error rolling back transaction");
				}
				throw e;
			}
		} finally {
			SessionFactoryUtil.getInstance().close();
		}
	}
}
```


```
@Entity
@Table(name = "pizza")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Pizza implements Cloneable, Serializable {

    private static final double BASE_PRICE = 3.0;

    private Integer id;

    private String name;

    private Order order;

   private List<Topping> toppings;

   public Pizza() {
        this.toppings = new ArrayList<Topping>();
    }

    public Pizza(Integer aPizzaId) {
        this();
        this.id = aPizzaId;
    }

    public Pizza(String aName, List<Topping> theToppings) {
        this.name = aName;
        this.toppings = theToppings;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "PIZZA_ID")
    public Integer getId() {
        return id;
    }

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

    /**
     * Der Name der Pizza.
     */
    public String getName() {
        return name;
    }

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

    @ManyToOne
    @JoinColumn(name = "ORDERS_FK")
    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "pizza_topping", joinColumns = @JoinColumn(name = "PIZZA_FK"), inverseJoinColumns = @JoinColumn(name = "TOPPING_FK"))
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public List<Topping> getToppings() {
        return toppings;
    }

    public void setToppings(List<Topping> toppings) {
        this.toppings = toppings;
    }

    public void addTopping(Topping topping) {
        this.toppings.add(topping);
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[").append(super.toString());
        sb.append(", id: ").append(id);
        sb.append(", name: ").append(name);
        sb.append(", toppings: [");
        for (Topping top : toppings) {
            sb.append(top).append(",");
        }
        if (order != null) {
            sb.append(", order: ").append(order.getId()).append(", ")
                    .append(order.getState());
        } else {
            sb.append(", order: null");
        }
        sb.append("]]");

        return sb.toString();
    }

    @Override
    public Object clone() {
        return new Pizza(name, toppings);
    }

   @Transient
    public double getPrice() {
        double price = BASE_PRICE;
        for (Topping topping : toppings) {
            price += topping.getPrice();
        }
        return price;
    }

}
```


```
@Entity
@Table(name = "orders", uniqueConstraints = @UniqueConstraint(columnNames = {
        "CUSTOMER_FK", "CREATIONTIME" }))
@NamedNativeQuery(name = "orders.get_orders", query = "? = call orders.get_orders( ? )", resultClass = Order.class, callable = true)
public class Order implements Serializable {
    private Integer id;

    private Customer customer;

    private List<Pizza> pizzas = new ArrayList<Pizza>();

    /** eine neue Bestellung hat den Status NEW */
    private OrderState state = OrderState.NEW;

    private Date creationTime = new Date();

    public Order() {
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ORDER_ID")
    public Integer getId() {
        return id;
    }

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

    @ManyToOne(optional = true)
    @JoinColumn(name = "CUSTOMER_FK")
    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    public List<Pizza> getPizzas() {
        return pizzas;
    }

    public void setPizzas(List<Pizza> pizzas) {
        this.pizzas = pizzas;
    }

    @Enumerated(EnumType.STRING)
    public OrderState getState() {
        return state;
    }

    public void setState(OrderState aState) {
        this.state = aState;
    }

    @Transient
    public double getPrice() {
        double price = 0.0;
        for (Pizza pizza : pizzas) {
            price += pizza.getPrice();
        }
        return price;
    }

    public boolean addPizza(Pizza pizza) {
        if (state != OrderState.NEW) {
            return false;
        }
        pizzas.add(pizza);
        pizza.setOrder(this);
        return true;
    }

    public void removePizza(Pizza pizza) {
        pizzas.remove(pizza);
        pizza.setOrder(null);
    }

    @Temporal(TemporalType.TIMESTAMP)
    public Date getCreationTime() {
        return creationTime;
    }

    public void setCreationTime(Date creationTime) {
        this.creationTime = creationTime;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        final Order order = (Order) o;

        if (creationTime != null ? !creationTime
                .equals(order.creationTime)
                : order.creationTime != null) return false;
        if (customer != null ? !customer.equals(order.customer)
                : order.customer != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result;
        result = (customer != null ? customer.hashCode() : 0);
        result = 29
                * result
                + (creationTime != null ? creationTime.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "id: " + id + ", state: " + state + ", pizzas: "
                + pizzas + ", customer: " + customer
                + ", creationTime: " + creationTime;
    }

    public boolean containsPizza(int aPizzaId) {
        for (Pizza pizza : this.pizzas) {
            if (pizza.getId() == aPizzaId) {
                return true;
            }
        }
        return false;
    }

    public boolean finishOrder() {
        boolean stateChangedOK = false;
        if (state == OrderState.NEW) {
            state = OrderState.OPEN;
            stateChangedOK = true;
        }
        return stateChangedOK;
    }

}
```


----------



## Cage Hunter (2. Sep 2010)

Also wenn man sich das so überlegt...ja, eine Many-to-Many-Relation wäre in Order angebracht...
Du hast zwar eigentlich nur mehrere Pizzen in Order, aber von Pizza aus gesehen gibt es mehrere Orders, denen eine Pizza zugeordnet ist 
Ich hätte das mit dem FK weggelassen, das liest sich besser und Hibernate mapped das sowieso, von daher unnötig...meine Meinung jedenfalls

Wenn du's aber so lassen möchtest, dann bau doch einfach nen Counter in Pizza ein 
Schon zwecks Redundanz wäre das wohl eine bessere Lösung, einfach ein Integer oder sowas, das hochgezählt wird, wenn 2 Salamis bestellt werden


----------



## Studdi23 (2. Sep 2010)

Hi. Danke erstmal für die Antwort. Ich denke ein Counter macht nur Sinn wenn ich eine Pizza zweimal oder mehr der gleichen Bestellung zuordnen will. Hatte auch schon darüber nachgedacht ein solches Attribut nachträglich einzufügen. Das erklärt aber nicht wieso ich einer zweiten Bestellung nicht die selbe Pizza übergeben kann. Wenn ich zwei unterschiedliche Kunden habe dann muss ich ja erstens wissen welche Pizzen zu seiner Bestellung gehören, was eine ORDER_FK zwingend notwendig macht und zweitens könnte dieser Kunde ja auch die gleiche Pizza bestellen, zunächst mal unabhängig von der Anzahl. Es sollte also theoretisch möglich sein bei einer N:1 Beziehung redundante Daten, in meinem Fall mehrere gleiche Pizza-Objekte, jeweils einer einzigen eindeutigen Bestellung zuzuordnen ohne auf eine Many-To-Many Beziehung zurückgreifen zu müssen oder etwa nicht? Kann auch sein das ich einen Denkfehler hab


----------



## Gast2 (2. Sep 2010)

1. Dein TestExample stimmt ja hinten und vorne nicht  order2 wird nie etwas hinzugefügt...
2. gibts es pizza und pizza2 nicht...
3. Wenn du eine 1:N Beziehung hast musst auch die Salami Pizza 2mal anlegen. Sonst wird die alte Pizza einfach einer anderen Bestellung zugeordnet...

Also ich würde jedesmal ein neues PizzaObjekt machen, da in jeder Bestellung auch eine neue Pizza reinkommt, da bei Sonderwünschen ddas Objekt sich immer verändern kann. Pizzas sind ja keine Stammdaten wie Belage...


----------



## Studdi23 (2. Sep 2010)

ja sorry, hab den code noch mal schnell auf dieser seite geändert um klarere variablennamen zu vergeben und dabei einige änderungen vergessen. selbstverständlich werden die pizzen der order hinzugefügt und das mappen in die datenbank funktioniert auch einwandfrei.


"Also ich würde jedesmal ein neues PizzaObjekt machen, da in jeder Bestellung auch eine neue Pizza reinkommt, da bei Sonderwünschen ddas Objekt sich immer verändern kann. Pizzas sind ja keine Stammdaten wie Belage... "

Auf die Idee bin ich ja auch gekommen, nur hätte ich gerne gewußt warum es nicht möglich ist die selbe Pizza für neue Bestellungen zu nutzen und stattdessen diese immer neu erstellen muss, ist nicht gerade elegant die Lösung. Ich weiß das man mittels der Methode "evict()"  ein Hibernate-Objekt explizit von einer Session lösen kann damit dieses nicht mehr von Hibernate überwacht wird. Sollte es danach nicht möglich sein, das bereits erzeugte Objekt neu zu persistieren? Ein einfacher Aufruf dieser Methode hat allerdings nicht die gewünschte Wirkung erzielt. Wenn es nicht anders geht würde ich es dann doch noch mal mit einer "viele-zu-viele-Beziehung" probieren.


----------



## Gast2 (2. Sep 2010)

Du du kannst die gleiche Pizza benutzen dann brauchst du aber eine m:n beziehung und dann wird eine extra Tabelle angelegt dann funktioniert das auch... bei einer 1:N Beziehung kann deine Pizza halt nur einmal in einer Bestellung vorkommen...

Und wenn du diese Ordnung willst musst du nunmal eine neue Pizza erstellen, da es auch ein neues Objekt ist, siehst du ja an der ID...


```
PIZZA_ID  name      ORDERS_FK
          
1         Salami        1
2         Thunfisch     1
3         Napoli        1
4         Salami        2
```


----------



## tuttle64 (2. Sep 2010)

SirWayne hat gesagt.:


> Du du kannst die gleiche Pizza benutzen dann brauchst du aber eine m:n beziehung und dann wird eine extra Tabelle angelegt dann funktioniert das auch... bei einer 1:N Beziehung kann deine Pizza halt nur einmal in einer Bestellung vorkommen...




solange der order als member in der pizza-klasse vorhanden ist, kommt Studdi23 um eine 1:N nicht drum herum. um das zu lösen, muss auch der order aus der pizza-klasse verschwinden.


----------



## Gast2 (2. Sep 2010)

Sag ja wenn er sowas vor hat muss er seine 1:N beziehung auflösen...


----------



## Studdi23 (2. Sep 2010)

Alles klar vielen Dank für eure hilfreichen Antworten.

Viele Grüße
Mario


----------

