REST-JDBC — Microsoft Graph

Die Microsoft Graph API liefert Zugriff auf Microsoft-365-Daten (Benutzer, Gruppen, E-Mail, Kalender usw.). Dieses Beispiel zeigt OAuth 2.0 Client Credentials für mandantenweite Leseabfragen und OAuth 2.0 Device Code für delegierte Abfragen und Schreibvorgänge (/me/…).

Schnellstart mit dem JDBC-Client

Benutzer und Gruppen (OAuth2 Client Credentials)

java -jar restjdbc.jar graph-oauth2.sql
  1. Azure-App mit Anwendungsberechtigungen anlegen (siehe Abschnitt 4)
  2. In graph-oauth2.sql tenant, clientId und clientSecret ersetzen
  3. Befehl im Ordner mit graph-spec.json ausführen
connect 'jdbc:rest:https://graph.microsoft.com/v1.0|spec=graph-spec.json,auth=oauth2,tenant=…,clientId=…,clientSecret=…,scope=https://graph.microsoft.com/.default'

Posteingang (OAuth2 Device Code, delegiert)

Der Endpunkt /me/messages erfordert ein delegiertes Token (angemeldeter Benutzer). Dafür eignet sich der Device Code Flow im Treiber:

java -jar restjdbc.jar graph-devicecode.sql
  1. Azure-App als Public Client konfigurieren, delegierte Berechtigung Mail.Read anlegen
  2. In graph-devicecode.sql tenant und clientId ersetzen
  3. Beim Verbindungsaufbau erscheint URL/Code zur Anmeldung im Browser
connect 'jdbc:rest:https://graph.microsoft.com/v1.0|spec=graph-spec.json,auth=oauth2,oauth2GrantType=device_code,tenant=…,clientId=…,scope=Mail.Read offline_access'

Alternativ weiterhin manuelles Bearer-Token: graph-bearer.sql

Kalender (OAuth2 Device Code, delegiert, Schreiben)

Termine des angemeldeten Benutzers lesen und schreiben (INSERT, UPDATE, DELETE):

java -jar restjdbc.jar graph-calendar.sql
  1. Delegierte Berechtigung Calendars.ReadWrite in der Azure-App anlegen
  2. In graph-calendar.sql tenant und clientId ersetzen
  3. Scope: Calendars.ReadWrite offline_access User.Read
connect 'jdbc:rest:https://graph.microsoft.com/v1.0|spec=graph-spec.json,auth=oauth2,oauth2GrantType=device_code,tenant=…,clientId=…,scope=Calendars.ReadWrite offline_access User.Read'

Verschachtelte Graph-Felder (start, end, body, location) werden über jsonPointer und defaultWrite in der Spec abgebildet — siehe Spalten in graph-spec.json, Entity calendar_events.

Benutzer-Kalender (OAuth2 Client Credentials, App-Berechtigung)

Termine eines beliebigen Benutzers im Mandanten lesen und schreiben (/users/{userId}/events):

java -jar restjdbc.jar graph-user-calendar.sql
  1. Anwendungsberechtigung Calendars.ReadWrite anlegen und Admin-Zustimmung erteilen
  2. In graph-user-calendar.sql tenant, clientId, clientSecret und USER-OBJECT-ID ersetzen
  3. Scope: https://graph.microsoft.com/.default

Der Pfad-Parameter userId wird per Spalte bzw. FILTER userId='…' gesetzt (Platzhalter {userId} in der Spec). Beispiel:

INSERT INTO user_calendar_events ("userId", subject, "start_dateTime", "end_dateTime")
VALUES ('USER-OBJECT-ID', 'Meeting', '2026-07-01T10:00:00', '2026-07-01T11:00:00');

UPDATE user_calendar_events SET subject = 'Meeting (neu)' FILTER userId='USER-OBJECT-ID' AND id=EVENT-ID;

Kalenderansicht (OAuth2 Client Credentials)

Kalenderfenster eines Postfachs lesen (/users/{mailbox}/calendarView). Graph verlangt startDateTime und endDateTime als Query-Parameter; weitere Prädikate gehen an $filter. Die Spec deklariert das über Spalten-binding (path / query) plus filterParam — dasselbe generische Verfahren wie für andere REST-APIs (siehe Pfad- und Query-Bindings).

java -jar restjdbc.jar graph-calendar-view.sql
  1. Anwendungsberechtigung Calendars.Read (oder Calendars.ReadWrite) anlegen und Admin-Zustimmung erteilen
  2. In graph-calendar-view.sql tenant, clientId, clientSecret und das Postfach ersetzen
  3. Scope: https://graph.microsoft.com/.default
SELECT id, subject, location, start, "end", categories
FROM calendar_view
FILTER mailbox='user@example.com'
  AND startDateTime='2020-01-01T00:00:00'
  AND endDateTime='2020-12-31T23:59:59';

Weitere OData-Prädikate stehen nach den gebundenen Parametern:

SELECT id, subject FROM calendar_view
FILTER mailbox='user@example.com'
  AND startDateTime='2020-01-01T00:00:00'
  AND endDateTime='2020-12-31T23:59:59'
  AND subject eq 'Meeting';

$expand kommt aus der Spec (expandParam). SELECT attachments ergänzt $expand=attachments. Ein fester Default wie singleValueExtendedProperties($filter=id eq '…') gehört ins Entity-Feld expand.

Das SQL-Schlüsselwort END ist reserviert — die Spalte als "end" quoten.

JDBC-URL

jdbc:rest:https://graph.microsoft.com/v1.0

Die Basis-URL der API steht in der JDBC-URL.

Bestandteil Wert
Treiber-Präfix jdbc:rest:
API-Basis-URL https://graph.microsoft.com/v1.0
Schema (SQL) public (Standard)

Beispiel-Verbindung (Java, OAuth2):

Properties props = new Properties();
props.setProperty("spec", "/pfad/zu/graph-spec.json");
props.setProperty("auth", "oauth2");
props.setProperty("tenant", System.getenv("AZURE_TENANT_ID"));
props.setProperty("clientId", System.getenv("AZURE_CLIENT_ID"));
props.setProperty("clientSecret", System.getenv("AZURE_CLIENT_SECRET"));
props.setProperty("scope", "https://graph.microsoft.com/.default");

Connection conn = DriverManager.getConnection(
    "jdbc:rest:https://graph.microsoft.com/v1.0", props);

Connection Properties

OAuth2 Client Credentials (users, groups)

Property Wert Erforderlich
spec Pfad zur Spec Ja
auth oauth2 Ja
tenant Azure AD Tenant-ID Ja (oder tokenUrl)
clientId App-Registrierung (Client-ID) Ja
oauth2GrantType client_credentials (Standard) Nein
clientSecret Client Secret Ja
scope https://graph.microsoft.com/.default Ja

Alternativ statt tenant:

props.setProperty("tokenUrl",
    "https://login.microsoftonline.com/Ihre-Tenant-ID/oauth2/v2.0/token");

Der Treiber holt und erneuert das Access Token automatisch.

OAuth2 Device Code (messages /me)

Property Wert Erforderlich
auth oauth2 Ja
oauth2GrantType device_code Ja
tenant Azure AD Tenant-ID Ja
clientId App-Registrierung (Public Client) Ja
scope Mail.Read offline_access Ja
clientSecret — Nein (Public Client)

Der Treiber zeigt die Anmelde-URL, wartet auf die Benutzer-Anmeldung und erneuert das Token per Refresh Token.

Bearer (Alternative für messages /me)

Property Wert Erforderlich
auth bearer Ja
token Delegiertes Access Token Ja

Spec-Datei: graph-spec.json

Spec-Datei

Datei: graph-spec.json

Tabellen

SQL-Tabelle REST-Pfad Auth-Typ Anmerkung
users /users OAuth2 Client Credentials User.Read.All
groups /groups OAuth2 Client Credentials Group.Read.All
messages /me/messages OAuth2 Device Code oder Bearer Mail.Read delegiert
calendar_events /me/events OAuth2 Device Code oder Bearer Calendars.ReadWrite delegiert, INSERT/UPDATE/DELETE
user_calendar_events /users/{userId}/events OAuth2 Client Credentials Calendars.ReadWrite App, Pfad-Parameter userId
calendar_view /users/{mailbox}/calendarView OAuth2 Client Credentials Calendars.Read App, binding path/query (mailbox, startDateTime/endDateTime), optional $filter

Gemeinsame Spec-Einstellungen für OData können einmal unter defaults stehen, statt sie in jeder Entity zu wiederholen:

"defaults": {
  "dataPath": "/value",
  "filterParam": "$filter",
  "selectParam": "$select",
  "expandParam": "$expand",
  "orderByParam": "$orderby",
  "pagination": {
    "type": "nextLink",
    "limitParam": "$top",
    "nextLinkPath": "/@odata.nextLink",
    "defaultLimit": 100
  }
}
  • nextLink: der Treiber folgt @odata.nextLink automatisch über alle Seiten
  • filterParam: restlicher FILTER-Inhalt wird als OData-$filter gesendet (native Syntax); führende param=wert-Klauseln mit binding werden vorher abgetrennt
  • selectParam: nur im SELECT genannte Spalten werden als $select angefordert (Pfad-/Query-Bindings entfallen)
  • expandParam: verwandte Ressourcen über $expand (Entity-expand und/oder Spalten-expand)
  • binding: path oder query — mappt FILTER-Spalten auf URI-Platzhalter oder eigene Query-Parameter (required für Pflicht-Query-Parameter)
  • jsonPointer / selectName: verschachtelte Felder (z. B. Absender bei messages)

Entity-Werte überschreiben die Defaults (z. B. write bei den Kalender-Entities). Siehe Defaults und gemeinsame Strukturen.

Azure App-Registrierung

Für users / groups (Client Credentials)

  1. Microsoft Entra ID → App-Registrierungen → Neue Registrierung
  2. Zertifikate & Geheimnisse → Client Secret anlegen
  3. API-Berechtigungen → Microsoft Graph → Anwendungsberechtigungen:
    • User.Read.All
    • Group.Read.All
  4. Administratorzustimmung erteilen

Für Benutzer-Kalender (user_calendar_events) zusätzlich Anwendungsberechtigung: Calendars.ReadWrite.

Für Kalenderansicht (calendar_view) zusätzlich Anwendungsberechtigung: Calendars.Read (oder Calendars.ReadWrite).

Für messages / calendar_events (delegiert)

Zusätzlich Delegierte Berechtigungen: Mail.Read (Posteingang), Calendars.ReadWrite (Kalender). Public Client Flows für Device Code aktivieren, oder Token manuell für Bearer-Auth verwenden.

SQL-Beispiele

Spaltennamen aus der Spec mit gemischter Schreibweise (camelCase, z. B. displayName, start_dateTime) müssen in SELECT, INSERT und UPDATE in doppelten Anführungszeichen stehen — der SQL-Parser normalisiert unquoted Identifier sonst in Kleinbuchstaben. In FILTER und ORDERBY gilt das nicht: dort OData-Syntax mit Graph-Eigenschaftsnamen (displayName, accountEnabled).

Benutzer (OAuth2)

SELECT id, "displayName", mail, "userPrincipalName" FROM users;

SELECT id, "displayName", mail FROM users
  FILTER startswith(displayName,'M') AND accountEnabled eq true
  ORDERBY displayName asc;
SQL HTTP (vereinfacht)
SELECT id, "displayName", mail FROM users GET /users?$top=100&$select=id,displayName,mail
… FILTER startswith(…) …&$filter=startswith(displayName,'M') AND accountEnabled eq true

FILTER enthält OData-Ausdruck — nicht param=wert (dafür ist filterParam in der Spec gesetzt).

Gruppen (OAuth2)

SELECT id, "displayName", "mailEnabled" FROM groups
  FILTER mailEnabled eq true AND securityEnabled eq true;

Posteingang (Device Code oder Bearer)

SELECT id, subject, from_address, "receivedDateTime" FROM messages
  FILTER isRead eq false;

Mit graph-devicecode.sql (OAuth2 Device Code) oder graph-bearer.sql (fertiges Token).

Die Spalten from_address und from_name nutzen jsonPointer; $select fordert das übergeordnete Feld from an (selectName).

Kalender (Device Code oder Bearer)

INSERT INTO calendar_events (subject, "start_dateTime", "end_dateTime", body_content)
VALUES ('Team-Meeting', '2026-07-01T10:00:00', '2026-07-01T11:00:00', 'Agenda …');

SELECT id, subject, "start_dateTime", "end_dateTime" FROM calendar_events
  FILTER startswith(subject,'Team-Meeting');

UPDATE calendar_events SET subject = 'Team-Meeting (verschoben)' FILTER id=EVENT-ID;

DELETE FROM calendar_events FILTER id=EVENT-ID;
SQL HTTP (vereinfacht)
INSERT … POST /me/events mit JSON {subject, start:{dateTime,timeZone}, end:…}
UPDATE … FILTER id=… PATCH /me/events/{id}
DELETE … FILTER id=… DELETE /me/events/{id}

Ohne Angabe von start_timeZone/end_timeZone setzt die Spec Europe/Berlin (defaultWrite). Vollständiges Beispiel: graph-calendar.sql.

Benutzer-Kalender (Client Credentials)

INSERT INTO user_calendar_events ("userId", subject, "start_dateTime", "end_dateTime")
VALUES ('USER-OBJECT-ID', 'Team-Meeting', '2026-07-01T10:00:00', '2026-07-01T11:00:00');

SELECT id, subject FROM user_calendar_events
  FILTER userId='USER-OBJECT-ID' AND startswith(subject,'Team-Meeting');

UPDATE user_calendar_events SET subject = 'Team-Meeting (neu)'
  FILTER userId='USER-OBJECT-ID' AND id=EVENT-ID;

DELETE FROM user_calendar_events FILTER userId='USER-OBJECT-ID' AND id=EVENT-ID;

Pfad-Parameter (userId) stehen vor dem OData-Teil im FILTER, getrennt durch AND. Vollständiges Beispiel: graph-user-calendar.sql.

Kalenderansicht (Client Credentials)

calendar_view nutzt binding, damit Pfad, Query-Parameter und $filter kombiniert werden können. Gebundene FILTER-Klauseln (mailbox, startDateTime, endDateTime) müssen vorne stehen (param=wert); ein OData-Rest ist optional.

SELECT id, subject, location, start, "end", categories
FROM calendar_view
FILTER mailbox='user@example.com'
  AND startDateTime='2020-01-01T00:00:00'
  AND endDateTime='2020-12-31T23:59:59';

SELECT id, subject, attachments FROM calendar_view
FILTER mailbox='user@example.com'
  AND startDateTime='2020-01-01T00:00:00'
  AND endDateTime='2020-12-31T23:59:59';

SELECT id, subject FROM calendar_view
FILTER mailbox='user@example.com'
  AND startDateTime='2020-01-01T00:00:00'
  AND endDateTime='2020-12-31T23:59:59'
  AND subject eq 'Meeting';
SQL HTTP (vereinfacht)
FILTER mailbox=… AND startDateTime=… AND endDateTime=… GET /users/{mailbox}/calendarView?startDateTime=…&endDateTime=…&$select=…&$top=999
… AND subject eq 'Meeting' zusätzlich $filter=subject eq 'Meeting'
SELECT … attachments zusätzlich $expand=attachments

"end" quoten (END ist ein SQL-Schlüsselwort). Ein Default-$expand für Extended Properties gehört in die Spec (expand), nicht ins SQL. Spec-Auszug:

{ "name": "mailbox", "binding": "path", "readOnly": true },
{ "name": "startDateTime", "binding": "query", "required": true, "readOnly": true },
{ "name": "endDateTime", "binding": "query", "required": true, "readOnly": true }

Vollständiges Beispiel: graph-calendar-view.sql.

System-Tabellen

SELECT table_name, remarks FROM system.table_list;

SELECT column_name, type_name FROM system.column_list WHERE table_name = 'users';

SELECT column_name FROM system.pk_list WHERE table_name = 'users';

Hinweise

  • Throttling: Graph antwortet bei Last mit HTTP 429 — Retry-After beachten.
  • 401/403: fehlende Berechtigung, abgelaufenes Token oder keine Admin-Zustimmung.
  • /me/…: nur mit delegiertem Benutzer-Token, nicht mit Client Credentials.
  • Weitere Entities: weitere Graph-Ressourcen lassen sich in graph-spec.json ergänzen — gleiches Muster (dataPath, filterParam, selectParam, expandParam, binding, nextLink, ggf. über defaults).