Wie Spring Data Jdbc Tabellen verbindet

In diesem Beitrag werden wir uns ansehen, wie Spring Data Jdbc SQL-Abfragen erstellt, um verwandte Entitäten abzurufen.



Der Beitrag ist für Anfänger gedacht und enthält keine super kniffligen Dinge.





Ich lade alle zum Demo-Tag des Online-Kurses „Java Developer. Professionell " . Im Rahmen der Veranstaltung werde ich Sie ausführlich über das Kursprogramm informieren und Ihre Fragen beantworten.





Ein sehr großer Teil der Lösung wie Hibernate wird verwendet, weil Dies ist sehr praktisch für die Arbeit mit verschachtelten Objekten.



Beispielsweise gibt es eine Klasse RecordPackage. Eines der Felder dieser Klasse ist eine Sammlung von untergeordneten (oder verschachtelten) Objekten: Datensätze.



Wenn Sie Jdbc verwenden, müssen Sie viel Routinecode schreiben. Nur wenige Leute mögen es, weshalb sie teilweise Hiberhate verwenden.



Mit Hibernate können Sie eine Methode gleichzeitig aufrufen, um ein RecordPackage mit allen untergeordneten Datensätzen abzurufen.



Einerseits möchte ich eine Methode verwenden, um das gesamte Objekt abzurufen, und andererseits möchte ich mich nicht mit dem Monster im Ruhezustand anlegen.



Mit Spring Data Jdbc können Sie das Beste aus den beiden Welten (oder zumindest etwas Akzeptables) herausholen.



Betrachten Sie zwei Fälle:



  • Eins-zu-Viele-Beziehung
  • Eins-zu-eins-Beziehung




Es sind diese Verbindungen, die in der Praxis am häufigsten vorkommen.



Den vollständigen Code der Beispiele finden Sie auf GitHub, hier werde ich nur das Minimum angeben.

Zunächst ist anzumerken, dass Spring Data Jdbc kein magisches Werkzeug ist, um ein Problem zu lösen. Es hat sicherlich seine Nachteile und Grenzen.

Für eine Reihe typischer Aufgaben ist dies jedoch eine perfekt geeignete Lösung.



Eins-zu-viele-Beziehung



Als reales Beispiel können Sie Folgendes betrachten: den Header eines Pakets einiger Daten und die in diesem Paket enthaltenen Datenleitungen. Beispielsweise ist Datei ein Paket und Dateizeilen sind Datenzeilen, die in dieses Paket eingehen.



Die Struktur der Tabellen ist wie folgt:



create table record_package
(
    record_package_id bigserial    not null
        constraint record_package_pk primary key,
    name              varchar(256) not null
);

create table record
(
    record_id         bigserial    not null
        constraint record_pk primary key,
    record_package_id bigint       not null,
    data              varchar(256) not null
);

alter table record
    add foreign key (record_package_id) references record_package;




zwei Tabellen: record_package(Header eines bestimmten Pakets) und record(im Paket enthaltene Datensätze).

Wie diese Beziehung im Java-Code angezeigt wird:



@Table("record_package")
public class RecordPackage {
    @Id
    private final Long recordPackageId;
    private final String name;

    @MappedCollection(idColumn = "record_package_id")
    private final Set<Record> records;
….
}




Hier sind wir daran interessiert, eine Eins-zu-Viele-Beziehung zu definieren. Dies wird mit Anmerkungen codiert @MappedCollection.



Diese Annotation hat zwei Parameter:



idColumn - das Feld, über das die Verbindung hergestellt wird

; keyColumn - das Feld, nach dem die Datensätze in der untergeordneten Tabelle geordnet sind.



Diese Bestellung ist gesondert zu erwähnen. In diesem Beispiel spielt es für uns keine Rolle, in welcher Reihenfolge die untergeordneten Datensätze in die Datensatztabelle eingefügt werden, aber in einigen Fällen kann dies wichtig sein. Für eine solche Reihenfolge hat die Datensatztabelle ein Feld wie record_no. Dies ist das Feld, das in die keyColumn der MappedCollection-Annotation geschrieben werden muss. Wenn Sie einfügen ausführen, generiert Spring Data Jdbc die Werte dieses Felds. Zusätzlich zur Anmerkung muss Set <Record >durch List ersetzt werden<Aufzeichnung >, die ziemlich logisch und verständlich ist. Die explizit angegebene Reihenfolge der untergeordneten Zeilen wird bei der Bildung der Auswahl berücksichtigt, aber darauf werden wir später zurückkommen.



Wir haben also die Verbindungen identifiziert und sind bereit, sie auszuprobieren.



Wir erstellen verwandte Entitäten und beziehen sie von der Basis:



  var record1 = new Record("r1");
  var record2 = new Record("r2");
  var record3 = new Record("r3");

   var recordPackage = new RecordPackage( "package", Set.of(record1, record2, record3));
   var recordPackageSaved = repository.save(recordPackage);

   var recordPackageLoaded = repository.findById(recordPackageSaved.getRecordPackageId());




Beachten Sie, dass wir nur eine Methode aufrufen müssen repository.findById, um eine Instanz RecordPackagemit einer gefüllten Sammlung von Datensätzen abzurufen.



Natürlich interessiert uns, welche Art von SQL-Abfrage ausgeführt wurde, um die verschachtelte Sammlung von Datensätzen zu erhalten.



Im Vergleich zu Hibernate ist Spring Data Jdbc für seine Einfachheit gut. Es kann leicht debuggt werden, um die Hauptpunkte aufzudecken.



Nach einer kleinen Untersuchung im Paket org.springframework.data.jdbc.core.convert finden wir die DefaultDataAccessStrategy- Klasse . Diese Klasse ist für das Generieren von SQL-Abfragen basierend auf Klasseninformationen verantwortlich. Jetzt in dieser Klasse interessieren wir uns für die Methode
Iterable <Object> findAllByPath




Oder genauer die Linie:



String findAllByProperty = sql(actualType) 
    .getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());


Hier wird die erforderliche SQL-Abfrage aus dem internen Cache abgerufen.



In unserem Fall sieht es so aus:



SELECT "record"."data" AS "data", "record"."record_id" AS "record_id", "record"."record_package_id" AS "record_package_id" 
FROM "record" 
WHERE "record"."record_package_id" = :record_package_id


Alles ist klar und vorhersehbar.



Wie würde es aussehen, wenn wir die Reihenfolge der Datensätze in der untergeordneten Tabelle verwenden würden? Offensichtlich wäre eine Bestellung bis erforderlich.



Fahren wir mit der BasicRelationalPersistentProperty-Klasse des Pakets org.springframework.data.relational.core.mapping fort. Diese Klasse verfügt über eine Methode, die bestimmt, ob der Abfrage eine Reihenfolge hinzugefügt werden soll oder nicht.



	
public boolean isOrdered() {
  return isListLike();
}


und



private boolean isListLike() {
  return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}




isCollectionLike überprüft, ob wir wirklich eine "Sammlung" (einschließlich eines Arrays) haben.

Und von der Bedingung ! Set.class.isAssignableFrom (this.getType ()); Es wird deutlich, dass Set nicht zufällig verwendet wird, sondern um unnötiges Sortieren auszuschließen. Und eines Tages werden wir absichtlich List verwenden, um das Sortieren zu ermöglichen.



Ich denke, eins zu viele ist mehr oder weniger klar. Fahren wir mit dem nächsten Fall fort.



Eins-zu-eins-Beziehung



Nehmen wir an, wir haben eine solche Struktur.



create table info_main
(
    info_main_id bigserial    not null
        constraint info_pk primary key,
    main_data    varchar(256) not null
);

create table info_additional
(
    info_additional_id bigserial    not null
        constraint additional_pk primary key,
    info_main_id       bigint       not null,
    additional_data    varchar(256) not null
);

alter table info_additional
    add foreign key (info_main_id) references info_main;


Es gibt eine Tabelle mit grundlegenden Informationen zu einem bestimmten Objekt (info_main) und zusätzliche Informationen (info_additional).



Wie kann dies im Code dargestellt werden:



@Table("info_main")
public class InfoMain {
    @Id
    private final Long infoMainId;
    private final String mainData;

    @MappedCollection(idColumn = "info_main_id")
    private final InfoAdditional infoAdditional;
}




Auf den ersten Blick sieht es aus wie der erste Eins-zu-Viele-Fall, aber es gibt einen Unterschied. Dieses Mal ist das Kind wirklich ein Objekt, keine Sammlung wie im vorherigen Fall.



Der Code zum Testen sieht folgendermaßen aus:



  var infoAdditional = new InfoAdditional("InfoAdditional");

  var infoMain = new InfoMain("mainData", infoAdditional);

  var infoMainSaved = repository.save(infoMain);
  var infoMainLoaded = repository.findById(infoMainSaved.getInfoMainId());


Mal sehen, welcher SQL-Ausdruck diesmal generiert wird. Zu diesem Zweck graben wir die findById-Methode an den Speicherort:



Package org.springframework.data.jdbc.core.convert class DefaultDataAccessStrategy . Wir sind mit dieser Klasse bereits vertraut, jetzt interessieren wir uns für die Methode.



public <T> T findById(Object id, Class<T> domainType)




Wir sehen, dass die folgende Anforderung aus dem Cache abgerufen wird:



SELECT "info_main"."main_data" AS "main_data", "info_main"."info_main_id" AS "info_main_id", "infoAdditional"."info_main_id" AS "infoadditional_info_main_id", "infoAdditional"."additional_data" AS "infoadditional_additional_data", "infoAdditional"."info_additional_id" AS "infoadditional_info_additional_id" 
FROM "info_main" 
LEFT OUTER JOIN "info_additional" "infoAdditional" 
ON "infoAdditional"."info_main_id" = "info_main"."info_main_id" 
WHERE "info_main"."info_main_id" = :id




Im Moment passt die linke äußere Verbindung zu uns, aber was ist, wenn nicht. Wie bekomme ich innere Verbindung?

Das Erstellen funktionaler Joins befindet sich im Paket org.springframework.data.jdbc.core.convert , Klasse SqlGenerator , Methode:
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns)




Wir interessieren uns für dieses Fragment:



		
for (Join join : joinTables) {
  baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
}




Wenn Sie Tabellen verknüpfen müssen, gibt es eine Option nur mit einem linken äußeren Join.

Es scheint, dass eine innere Verbindung noch nicht möglich ist.



Fazit



Wir haben zwei typische Fälle behandelt, in denen Sie Tabellen in Spring Data Jdbc verknüpfen können.

Grundsätzlich eignet sich die jetzt verfügbare Funktionalität gut zur Lösung praktischer Probleme, obwohl es unkritische Einschränkungen gibt.



Den vollständigen Text des Beispiels finden Sie hier .



Und hier ist eine Videoversion dieses Beitrags.






All Articles