"Paging" bei OneToMany

Hallo zusammen,

ich versuche gerade Hibernate zu verstehen. Die Standardfälle für OneToMany habe ich schon verstanden. Allerdings habe ich eine Anforderung, bei der ich überhaupt nicht weiß, ob bzw. wie man das mit Hibernate umsetzt.

Es geht um eine OneToMany-Beziehung, in der es eine große Anzahl von Kind-Objekten gibt (im Extremfall hoher 5-stellige Anzahl). Über eine ReST-API soll ein “Paging” implementiert werden, d.h. der Vater und ein kleiner Teil seiner Kinder sollen zurückgegeben werden. Wie holt man die Daten aus der Datenbank, ohne direkt alle Kinder im Speicher zu haben?

Beispiel: Vater hat viele Kinder verschiedenen Alters. Wie kriege ich den Vater mit den beiden ältesten Kindern?

Ich könnte mir vorstellen, dass das über JPQL geht?!? Aber das verstehe ich noch nicht genau bzw. habe ich noch nicht zum Laufen gebracht. Ich bitte um Unterstützung :slight_smile:

Gibt es sonst etwas zu beachten beim Umgang mit sehr vielen Kind-Objekten?

Hier mal ein konkretes Beispiel

Main.java

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {
	private static final EntityManagerFactory entityManagerFactory;
	static {
		try {
			entityManagerFactory = Persistence
					.createEntityManagerFactory("test");

		} catch (Throwable ex) {
			System.err.println("Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static void main(String[] args) {
		EntityManager em = getEntityManager();
		try {

			Child child1 = new Child("A", 11);
			Child child2 = new Child("B", 12);
			Child child3 = new Child("C", 13);
			Child child4 = new Child("D", 14);
			Child child5 = new Child("E", 15);
			Father father = new Father("Father", child1, child2, child3,
					child4, child5);
			persistFather(em, father);
			Father persistedFather = findFather(em, father.getId());
			printFather(persistedFather);
		} finally {
			em.close();
		}
	}

	private static Father findFather(EntityManager em, long id) {
		Father fatherFound = null;
		try {
			em.getTransaction().begin();
			fatherFound = em.find(Father.class, id);
			em.getTransaction().commit();
		} catch (Exception e) {
			em.getTransaction().rollback();
		}
		return fatherFound;
	}

	private static void persistFather(EntityManager em, Father father) {
		try {
			em.getTransaction().begin();
			em.persist(father);
			em.getTransaction().commit();
		} catch (Exception e) {
			em.getTransaction().rollback();
		}
	}

	private static void printFather(Father fatherFound) {
		System.out.println("Father " + fatherFound.getFirstName() + " has "
				+ fatherFound.getChildren().size() + " sons");
		for (Child child : fatherFound.getChildren()) {
			System.out.println("- " + child.getFirstName() + ", "
					+ child.getAge() + " years old");
		}
	}

	public static EntityManager getEntityManager() {
		return entityManagerFactory.createEntityManager();
	}
}```

**Father.java**

```import java.util.Collection;
import java.util.LinkedList;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

@Entity
public class Father {
	@Id
	@GeneratedValue
	private long id;

	private String firstName;

	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinColumn(name = "father_id_fk")
	private Collection<Child> children;

	public Father() {
		children = new LinkedList<>();
	}

	public Father(String firstName, Child... children) {
		this();
		this.firstName = firstName;
		for (Child child : children) {
			this.children.add(child);
		}
	}

	public long getId() {
		return id;
	}

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

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public Collection<Child> getChildren() {
		return children;
	}

	public void setChildren(Collection<Child> children) {
		this.children = children;
	}

}```

**Child.java**
```import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Child {
	@Id
	@GeneratedValue
	private long id;

	private String firstName;

	private int age;

	public Child(String firstName, int age) {
		this.firstName = firstName;
		this.age = age;
	}

	public long getId() {
		return id;
	}

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

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}```

**persistence.xml**
[XML]<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
	version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
	<persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
		<properties>
			<property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
			<property name="javax.persistence.jdbc.url"
				value="jdbc:postgresql://localhost:5432/hibernatetest" />
			<property name="javax.persistence.jdbc.user" value="user" />
			<property name="javax.persistence.jdbc.password" value="password" />
			<property name="hibernate.hbm2ddl.auto" value="create" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
		</properties>
	</persistence-unit>
</persistence>[/XML]

mit dem Mapping dürfte das nichts zu tun haben, außer dem Punkt per Lazy Loading die Children nur bei Bedarf dazuzuladen


(nur mal ein Link, schreibt eigentlich mehr über Eager, sofort dazu laden…, aber Suchanfrage dürfte klar sein, falls Thema nicht bereits klar)

wobei, vielleicht gibt es auch noch die Möglichkeit, die gemappten Objekte zu sortieren:
One To Many With Order By Setting : One To Many Mapping*«JPA«*Java Tutorial
dann wäre in Kombination womöglich etwas erreicht…

Sortierung geht aber nur einmal, andere Wünsche dann ausgeschlossen


Query also,
dann ist die Frage allgemein, auch SQL, genausogut ‘wie die beiden höchsten Einträge einer Tabelle oder einer Berechnung finden?’

[sql]select * from Table a where
(select count() from Table b where b.xy <= a.xy) >= (select count() from Table ) -1
[/sql]
scheint zu gehen,
Achtung: wenn 3 oder mehr die höchste gleiche Anzahl haben, dann kommen auch 3 oder mehr

edit:
[sql]select * from Table a where
(select count(*) from Table b where b.xy >= a.xy) <= 2
[/sql]
sieht eleganter aus, liefert aber bei 3 gleichen nix, hmm…, höhere Grenze ist nie sicher außer wieder Tabellengröße,
wenn garantiert unterschiedliche Werte, weil z.B. nach Id statt Alter, dann könnte man es wagen

Danke für die Links. Das hat mir zumindest mal bestätigt, dass ich nicht komplett auf dem Holzweg bin. Der erste Link beschreibt das Problem ziemlich gut. Es ist beruhigend zu sehen, dass man nicht alleine ist :wink:

Der Unterschied zwischen Lazy-Loading und Eager ist mir bekannt. Auch, dass standardmäßig immer alle" bzw. “irgendwann alle” Children geladen werden. Dann hört es aber schon fast auf mit meinen Kenntnissen über JPA/Hibernate. Das fehlende Verständnis über ORM-Frameworks im Vergleich zu JDBC ist auch mein (übergeordnetes) Hauptproblem. Ich habe das Gefühl, dass man bei ORM-Frameworks komplett anders denken muss.

Das “One To Many With Order By Setting” muss ich mal näher anschauen. Die feste Sortierung scheint mir im ersten Moment unpassend. Muss aber die Use-Cases nochmal anschauen. Wenn ich dann mit setFirstResult/setMaxResult die Anzahl der Datensätze einschränke, bezieht sich das dann auf die Väter? Oder auf die Kinder? Oder auf die Anzahl der Zeilen, die durch den JOIN entstehen? Das habe ich definitiv noch nicht verstanden.

Die EntityGraphs klangen im ersten Moment sehr interessant. Muss ich mir definitiv mal anschauen, ob/wie man da Einschränkungen machen kann.

Mit “normalem” SQL bin ich ziemlich fit. Ich glaube aber, dass da zu spezifische Statements notwendig sind, die nicht in einen generischen Persistence-Service passen. Da scheint mir JPQL und namedQueries eher geeignet.

JPQL bietet sich natürlich an, nur der Einfachheit halber von SQL gesprochen, Querys sind dieselben,

Queries schlicht zu sortieren und setFirstResult/setMaxResult darauf bzw. Iterator und Abbruch ist natürlich noch schlauer dabei,
Wirkung bei individuellen Queries ist einfach: bezieht sich genau auf die Ergebnisse,

falls unklar ist was an Ergebnissen der Query bei einem Join usw. herauskommt, dann das vor Einsatz von solchen Zusätzen testen…


so wie du setFirstResult/setMaxResult in einem Zug bei “One To Many With Order By Setting” nennst kann ich nicht unbedingt folgen,
aber will mal vorsichtshalber nicht ausschließen dass das irgendwas sinnvolles ist

die Links waren übrigens wirklich nur auf die Schnelle mit einfachen Suchen ‘OneToMany Lazy Loading’ usw. geholt, ohne dass ich sie besonders empfehlen kann,
brauchst darauf nicht speziell zu bauen, ist alles und anderes leicht zu finden