5 Stimmen

JPA gemapptes Modell liefert Nullelemente mit Composite-Schlüsseln

Ich habe mein Datenmodell mit JPA aufgebaut und verwende den EntityManager 3 von Hibernate für den Zugriff auf die Daten. Ich habe HSQLDB zum Testen verwendet (Junit). Ich verwende diese Konfiguration für andere Klassen und habe keine Probleme gehabt.

Der letzte Tabellenstapel verwendet jedoch einen zusammengesetzten Schlüssel als Primärschlüssel, und ich bin nicht in der Lage, die ausgefüllte Zeile aus der Datenbank abzurufen, wenn sie implementiert ist. Ich erhalte keine Fehlermeldung, die Abfrage gibt einfach Null-Objekte zurück.

Wenn ich zum Beispiel (mit jsql) "FROM Order o" abfrage, um eine Liste aller Bestellungen in der Tabelle zurückzugeben, hat meine list.size() die richtige Anzahl von Elementen (2), aber die Elemente sind null.

Ich hoffe, dass jemand, der einen schärferen Blick hat als ich, erkennen kann, was ich falsch mache. Vielen Dank im Voraus!

Die (vereinfachten) Tabellen sind wie folgt definiert:

CREATE TABLE member (
    member_id INTEGER NOT NULL IDENTITY PRIMARY KEY);

CREATE TABLE orders (
    orders_id INTEGER NOT NULL,
    member_id INTEGER NOT NULL,
    PRIMARY KEY(orders_id, member_id));

ALTER TABLE orders 
    ADD CONSTRAINT fk_orders_member 
    FOREIGN KEY (member_id) REFERENCES member(member_id);

Die Entitäts-POJOs werden definiert durch:

@Entity
public class Member extends Person implements Model<Integer>{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MEMBER_ID", nullable=false)
    private Integer memberId;

    @OneToMany(fetch=FetchType.LAZY, mappedBy="member", cascade=CascadeType.ALL)
    private Set<Order> orderList;
}

@Entity
@Table(name="ORDERS")
@IdClass(OrderPK.class)
public class Order extends GeneralTableInformation implements Model<Integer>{

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ORDERS_ID", nullable=false)
    private Integer orderId;

    @Id
    @Column(name="MEMBER_ID", nullable=false)
    private Integer memberId;

    @ManyToOne(optional=false, fetch=FetchType.LAZY)
    @JoinColumn(name="MEMBER_ID", nullable=false)
    private Member member;

    @OneToMany(mappedBy="order", fetch=FetchType.LAZY)
    private Set<Note> noteList;
}

OrderPK definiert einen Standardkonstruktor und 2 Eigenschaften (orderId, memberId) zusammen mit ihren get/set-Methoden.

public class OrderPK implements Serializable {
private static final long serialVersionUID = 1L;

private Integer orderId;
private Integer memberId;

public OrderPK() {}

public OrderPK(Integer orderId, Integer memberId) {
    this.orderId = orderId;
    this.memberId = memberId;
}

/**Getters/Setters**/

@Override
public int hashCode() {
    return orderId.hashCode() + memberId.hashCode(); 
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (!(obj instanceof OrderPK))
        return false;

    OrderPK other = (OrderPK) obj;
    if (memberId == null) {
        if (other.memberId != null) return false;
    } else if (!memberId.equals(other.memberId))
        return false;
    if (orderId == null) {
        if (other.orderId != null) return false;
    } else if (!orderId.equals(other.orderId))
        return false;
    return true;
}   

}

(Entschuldigung für die Länge)

der EntityManager wird in einer abstrakten Klasse instanziiert, die dann von meinen anderen DAOs erweitert wird

protected EntityManager em;

@PersistenceContext
public void setEntityManager(EntityManager em) {
    this.em = em;
}

und wird durch eine Konfigurationsdatei für den Federkontext konfiguriert

<bean id="entityManagerFactory" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:dataSource-ref="dataSource" 
    p:jpaVendorAdapter-ref="jpaAdapter">
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
    <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml" />
</bean>

Meine Testklasse

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class OrderDaoTest {

    @Autowired
    protected OrderDao dao = null;

    @Test
    public void findAllOrdersTest() {
    List<Order> ol = dao.findAll();
    assertNotNull(ol); //pass
        assertEquals(2, ol.size(); //pass
        for (Order o : ol) {
            assertNotNull(o); //fail
            ...
        } 
    }
}

Wenn ich den Composite-Schlüssel aus der Klasse Order entferne, kann ich Daten abrufen. Ich bin mir nicht sicher, was ich mit meiner Zuordnung oder Konfiguration falsch mache. Jede Hilfe wird sehr geschätzt.

6voto

Tuomas Kujala Punkte 41

Ich hatte das gleiche Problem. Ich musste ein Backend auf einer bestehenden Datenbank erstellen, die einige Tabellen ohne Primärschlüssel und mit löschbaren Spalten hatte (ich weiß!!). Das bedeutet, dass Hibernate-Entitäten für diese Art von Tabellen einen zusammengesetzten Primärschlüssel haben würden, der aus allen Tabellenspalten besteht.

Wenn ich die Tabellenzeilen über die findAll-Methode von JpaRepository abfragte, erhielt ich eine Ergebnisliste, in der einige der Ergebnisse Nullwerte waren, während andere in Ordnung waren.

Ich fand schließlich heraus, dass, wenn ein Feld des zusammengesetzten Schlüssels null ist, es ganze Zeile als null Ergebnis machen würde.

2 Stimmen

Nachdem ich die akzeptierte Antwort ausprobiert hatte, war dies diejenige, die für mich die Lösung war. Es ist also eine wichtige Information, nur Nicht-Null-Werte in Ihrer zusammengesetzten ID zu haben.

4voto

Seth M. Punkte 598

Nachdem ich mich eine Weile mit diesem Problem herumgeschlagen hatte, erfuhr ich, dass ich meine Id-Eigenschaften in der falschen Klasse konfiguriert hatte.

Ursprünglich habe ich orderId und memberId in der Klasse Order konfiguriert

@Entity
@Table(name="ORDERS")
@IdClass(OrderPK.class)
public class Order extends GeneralTableInformation implements Model<Integer>{

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ORDERS_ID", nullable=false)
    private Integer orderId;

    @Id
    @Column(name="MEMBER_ID", nullable=false)
    private Integer memberId;

Ich habe jedoch gelernt, dass Sie, wenn Sie eine IdClass ODER EmbeddedId verwenden, die entsprechenden Feldannotationen für Ihre Id-Spalten in der ID-Klasse vornehmen müssen.

@Entity
@Table(name="ORDERS")

@IdClass(OrderPK.class)
public class Order extends GeneralTableInformation implements Model<Integer>{

    @Id
    private Integer orderId;

    @Id
    private Integer memberId;
}

public class OrderPK implements Serializable {
    private static final long serialVersionUID = 1L;

    @Column(name="ORDERS_ID", nullable=false)
    private Integer orderId;

    @Column(name="MEMBER_ID", nullable=false)
    private Integer memberId;
}

Mit dieser Änderung konnte ich bei meinem Test die erwarteten Ergebnisse erzielen.

0 Stimmen

Ich habe die @IdClass-Methode ausprobiert. Wenn ich jedoch eine neue Zeile einfügen möchte, wird die Meldung "Column 'orderId' cannot be null" ausgegeben. Ich weiß nicht, wie ich die ID automatisch generieren kann, so dass sie nicht null ist?

0voto

Pascal Thivent Punkte 548176

OrderPK definiert einen Standardkonstruktor und 2 Eigenschaften (orderId, memberId) zusammen mit ihren get/set-Methoden.

Ich bin mir nicht sicher, ob das alles erklärt, aber... das ist nicht genug. Aus der JPA 1.0 Spezifikation:

2.1.4 Primärschlüssel und Entitätsidentität

...

Die folgenden Regeln gelten für zusammengesetzte Primärschlüssel.

  • Die Primärschlüsselklasse muss öffentlich sein und einen öffentlichen no-arg-Konstruktor haben.
  • Wird ein eigenschaftsbasierter Zugriff verwendet, müssen die Eigenschaften der Primärschlüsselklasse öffentlich oder geschützt sein.
  • Die Primärschlüsselklasse muss serialisierbar sein.
  • Die Primärschlüsselklasse muss definieren equals y hashCode Methoden. Die Semantik der Wertegleichheit für diese Methoden muss mit der Datenbankgleichheit für die Datenbanktypen übereinstimmen, auf die der Schlüssel abgebildet wird.
  • Ein zusammengesetzter Primärschlüssel muss entweder als einbettbare Klasse dargestellt und abgebildet werden (siehe Abschnitt 9.1.14, "EmbeddedId Annotation") oder er muss auf mehrere Felder oder Eigenschaften der Entitätsklasse dargestellt und abgebildet werden (siehe Abschnitt 9.1.15, "IdClass Annotation").
  • Wenn die zusammengesetzte Primärschlüsselklasse auf mehrere Felder oder Eigenschaften der Entitätsklasse abgebildet wird, müssen die Namen der Primärschlüsselfelder oder -eigenschaften in der Primärschlüsselklasse und denen der Entitätsklasse übereinstimmen und ihre Typen müssen gleich sein.

Könnten Sie bitte Ihre OrderPK Klasse, um der Spezifikation zu entsprechen und es erneut zu versuchen?

Nebenbei bemerkt, frage ich mich, warum Sie die loadTimeWeaver in Ihrer Spring-Konfiguration (da Sie Hibernate verwenden).

Aktualisierung: Ich kann das Problem nicht reproduzieren... bei mir funktioniert es wie erwartet.

0 Stimmen

Meine OrderPK-Klasse sollte diese Anforderungen erfüllen. Ich werde es in einem Update auf die ursprüngliche Frage posten.

0 Stimmen

Ich dachte, dass ein loadTimeWeaver verwendet werden muss. Ich habe ihn entfernt, ohne dass dies irgendwelche negativen Auswirkungen hatte. Ich danke Ihnen.

0 Stimmen

@SethM: Hibernate verlässt sich nicht auf Weaving (im Gegensatz zu z. B. TopLink), aber ich glaube nicht, dass das Teil des Problems ist. Leider kann ich es nicht reproduzieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X