Wir arbeiten derzeit an einem Projekt, das eine Oracle 9-Datenbank, Hibernate als ORM-Tool und Spring für unser Abhängigkeitsmanagement verwendet. Unsere Anwendung verarbeitet Millionen von Datensätzen pro Monat, was bedeutet, dass einige Tabellen jeden Monat um etwa 15 Millionen Datensätze anwachsen. Aus Wartungs- und Leistungsgründen ist es unumgänglich, diese Tabellen zu partitionieren. Aber passt die Partitionierung in Hibernate, und wenn ja, wie?
Zuerst werden wir ein wenig über die Grundlagen der Anwendung, die wir besprechen, erklären. Dann werden wir erörtern, welche Partitionierungsstrategie wir gewählt haben und was wir in unserer Anwendung ändern mussten. Schließlich werden wir auf einen Fehler hinweisen, auf den wir in Hibernate bei der Arbeit an der Partitionierung gestoßen sind. Anwendungsgrundlagen Unsere Tabellen haben Sequenzwerte als Primärschlüssel, da die funktionalen Schlüssel nicht immer eindeutig sind oder aus mehreren Spalten mit Abhängigkeiten zwischen ihnen bestehen. Die Klassen und die ihnen zugeordneten Tabellen sind meist Eltern-Kind-Beziehungen. Mit Hibernate ist es ganz einfach, von parent zu child zu navigieren. Rufen Sie einfach parent.getChildren() auf und Hibernate lädt die Kinder über die Join-Spalte, die in unserem Fall der Sequenzwert ist:
Wählen Sie children0_.PARENT_ID as PARENT2_1_, Kinder0_.ID as ID1_, Kinder0_.ID as ID1_0_, children0_.PARENT_ID as PARENT2_1_0_ von CHILD Kinder0_ wobei kinder0_.PARENT_ID=?
Partitionsstrategie Es gibt mehrere Partitionierungsstrategien für Oracle, die beiden am häufigsten verwendeten sind die RANGE-Partitionierung und die HASH-Partitionierung. Gemeinsam mit dem DBA haben wir uns aus mehreren Gründen für die RANGE-Partitionierungsstrategie entschieden:
- Wir verarbeiten logischerweise pro Monat, so dass die Partitionierung nach Jahr und Monat erfolgt. Auf diese Weise bearbeitet Oracle fast immer nur eine Partition auf einmal, was bedeutet, dass es nur eine Partition in den Speicher laden muss. Wenn wir die HASH-Partitionierung wählen würden, wären die Daten über die verschiedenen Partitionen verstreut.
- Da wir die Daten logischerweise pro Monat verarbeiten, können sie nun auch pro Monat gesichert werden. Nach einigen Jahren kann sie fünf Jahre nach dem Monat, in dem sie eingefügt wurde, aktualisiert werden, diese Partition könnte offline genommen werden.
Bei der Bereichspartitionierung fügt die Datenbank Daten in die Partition ein, die auf dem Wert einer Spalte in der Tabelle basieren. Um von der Partitionierung zu profitieren, müssen Sie die Spalte, auf die Sie partitionieren, in Ihre WHERE-Klausel Ihrer SQL-Abfrage aufnehmen. Und genau hier liegt das Problem: Unser FOREIGN KEY liegt immer auf dem PRIMARY KEY der PARENT-Tabelle. In unserem Fall ist dies ein sequenzgenerierter Wert. Wie wir gerade festgestellt haben, möchten wir nach einem Datum und nicht nach einem Sequenzwert partitionieren. Um dies zu unterstützen, haben wir in allen Tabellen, die partitioniert werden müssen, eine Spalte PARTITION_VALUE hinzugefügt. Dies führt zum Hauptpunkt dieses Artikels: Wie können wir die Hibernate-Konfiguration unserer Anwendung manipulieren, damit Hibernate den Partitionswert in alle Where-Klauseln von Abfragen auf partitionierte Tabellen aufnimmt? Die meisten Tabellen, die partitioniert werden müssen, sind um eine einzige Tabelle herum gruppiert (nennen wir diese Tabelle A). Der Wert, der für die Partitionierung verwendet wird, stammt eigentlich nicht aus dieser Tabelle, aber die meisten domänenlogischen Abfragen (Abfragen, die von der Anwendung direkt ausgeführt werden, um die zu verarbeitenden Daten zu erhalten), werden auf dieser Tabelle ausgeführt. Wenn Sie eine domänenlogische Abfrage mit Hibernate ausführen, ist das Hinzufügen der Partitionswertabfrage einfach. Eine Einschränkung der Partitionswerteigenschaft des Domänenobjekts kann mithilfe von Kriterien oder HQL hinzugefügt werden. Hibernate muss möglicherweise zusätzliche Daten abrufen, wenn die Anwendung durch die Verknüpfungen der von der Abfrage zurückgegebenen Entitäten navigiert. Die Abfragen, die Hibernate ausführt, basieren auf einer Fremdschlüsselbeziehung der abhängigen Tabelle (z.B. B) zu der Tabelle, auf die die geladene Entität abgebildet ist. Die grundlegende Abfrage lautet:
Wählen Sie ... von B b0_ wobei b0_.A_ID=?
Die Spalte B.A_ID hat eine Fremdschlüsselrestriktion auf die Spalte A.ID. Um von der Partitionierung zu profitieren, muss die Abfrage wie folgt aussehen:
Wählen Sie ... von B b0_ wobei b0_.A_ID=? und b0_.PARTITION_VALUE = ?
Um dies zu erzwingen, fügen wir die Spalte A.PARTITION_VALUE zur Hibernate Id der Entität hinzu, die der Tabelle A zugeordnet ist. Hibernate verwendet nun beide Spalten, um die zugehörigen Zeilen in verwandten Tabellen zu identifizieren, was zu Abfragen wie der obigen führt. Wir benötigen nun eine Composite-Id, die aus einem Sequenzwert und der Spalte PARTITION_VALUE besteht. Aus irgendeinem Grund unterstützt Hibernate keinen Generator für die Generierung von Composite-id's zur Speicherzeit, so dass die Anwendung gezwungen ist, den id-Wert jeder Entität zu setzen, bevor sie in der Hibernate-Sitzung gespeichert wird (dies ähnelt der Strategie des "zugewiesenen" Generators für einzelwertige ids). Da wir das Rad nicht neu erfinden wollen, haben wir den SequenceHiLoGenerator von Hibernate wiederverwendet (der die Anzahl der sequence.nextval-Aufrufe an Oracle reduziert). Als nächstes haben wir eine Spring FactoryBean erstellt, die den SequenceHiLoGenerator verwendet, um die Sequenzwerte zu erzeugen. In der DAO, die zum Speichern des Parent verwendet wird, wird die Eigenschaft id mit dem neuen Sequenzwert gesetzt, der vom SequenceHiLoGenerator abgerufen wird. Die Spring Bean, die eine Instanz des SequenceHiLoGenerators erzeugt, sieht wie folgt aus:
public class SequenceFactory extends AbstractFactoryBean {
private String dialectName;
private Eigenschaften properties;
geschützter Dialekt-Dialekt;
public Klasse getObjectType() {
return SequenceHiLoGenerator.class;
}
public void afterPropertiesSet() throws Exception {
versuchen {
getDialectFromName();
} catch (ClassCastException e) {
throw new BeanInitializationException("Die angegebene Klasse '" + dialectName + "' erweitert nicht 'org.hibernate.dialect.Dialect'");
} catch (Exception e) {
throw new BeanInitializationException("Die angegebene Klasse '" + dialectName + "' existiert nicht oder kann nicht instanziert werden",e);
}
super.afterPropertiesSet();
}
protected Object createInstance() throws Exception {
SequenceHiLoGenerator generator = createNewSequenceHiLoGenerator();
generator.configure(Hibernate.LONG, properties, getDialectFromName());
Rückgabegenerator;
}
private Dialect getDialectFromName() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
if (Dialekt == null) {
dialect = (Dialect) Class.forName(dialectName).newInstance();
}
Dialekt zurückgeben;
}
/**
* zu Testzwecken erstellt.
*/
protected SequenceHiLoGenerator createNewSequenceHiLoGenerator() {
return new SequenceHiLoGenerator();
}
public void setDialectName(String dialectName) {
this.dialectName = dialectName;
}
public void setProperties(Eigenschaften properties) {
this.properties = properties;
}
}
Diese Bean ist wie folgt mit der DAO verdrahtet:
999 UKC_PARENT_SEQ
Da die SequenceFactory die AbstractFactoryBean erweitert, verwendet Spring die Methode createInstance, um Instanzen des SequenceHiLoGenerators zu erstellen. Die Instanz des SequenceHiLoGenerator wird dann in das Dao injiziert und in der Save-Methode verwendet, um den neuen Sequenzwert zu erzeugen. Die Hiberante-Zuordnung sieht nun wie folgt aus:
999 CHILD_SEQ
Wie Sie sehen können, gibt es eine Klasse ParentId, die die Composite-ID von Parent enthält. Die Child-Klasse verweist nun über die Composite-Id-Spalten auf Parent zurück. In der von Hibernate generierten SQL-Anweisung wird die Spalte PARTITION_VALUE in der Where-Klausel verwendet:
Wählen Sie children0_.PARENT_ID as PARENT2_1_, children0_.PARTITION_VALUE as PARTITION3_1_, Kinder0_.ID as ID1_, Kinder0_.ID as ID1_0_, children0_.PARENT_ID as PARENT2_1_0_, kinder0_.PARTITION_VALUE as PARTITION3_1_0_ von CHILD Kinder0_ wobei kinder0_.PARENT_ID=? und Kinder0_.PARTITION_VALUE=?
Hier haben Sie es! Partitionierung in Oracle mit Hibernate. Fehler in Hibernate Es gibt noch eine weitere Sache, die wir besprechen möchten. In unserer Anwendung haben wir in einem Fall HQL verwendet, um einige Entitäten abzurufen. Wir haben festgestellt, dass die Verwendung von HQL-Abfragen mit einer Einschränkung auf eine Composite-ID nicht funktioniert. Nehmen wir das vorherige Parent-Child-Beispiel und führen die folgende HQL aus:"from Child c where c.parent = :parent", erzeugt Hibernate diese SQL:
Wählen Sie child0_.ID as ID1_, child0_.PARENT_ID as PARENT2_1_, child0_.PARTITION_VALUE as PARTITION3_1_ von CHILD kind0_ wobei ( child0_.PARENT_ID, child0_.PARTITION_VALUE )=?
Das funktioniert offensichtlich nicht. Ich frage mich, ob sie sagen, dass dies eine Funktion ist ïŠ..... :-) Maarten Winkels Lars Vonk
Verfasst von
Lars Vonk
Unsere Ideen
Weitere Blogs
Contact



