# JDBC voor databases vanuit Apps Script [[TOC]] ## JDBC-service inleiding De JDBC-service laat je vanuit Apps Script verbinding maken met externe relationele databases: MySQL, PostgreSQL, Microsoft SQL Server, Oracle en Google Cloud SQL. Dit is nuttig wanneer je data wilt ophalen of schrijven naar een bedrijfsdatabase zonder externe middleware. Twee aandachtspunten voordat je begint: - De service kan alleen verbinden met poort 1025 of hoger. Draait je database op een lagere poort, dan werkt de verbinding niet. - Apps Script vereist TLS 1.2 of hoger. Oudere TLS-versies (1.0 en 1.1) zijn uitgeschakeld. ```javascript function testVerbinding() { const verbinding = Jdbc.getConnection( 'jdbc:mysql://db.bedrijf.nl:3306/klanten', 'gebruikersnaam', 'wachtwoord' ); Logger.log('Verbinding geslaagd'); verbinding.close(); } ``` :::warn title="Sla wachtwoorden nooit in scripttekst op" Plaats databasewachtwoorden nooit hardcoded in je code. Gebruik `PropertiesService` om credentials veilig op te slaan en op te halen, zodat ze niet meelekken bij delen of versiebeheer. ::: ## Veilige credential-opslag ```javascript function slaCredentialsOp() { const props = PropertiesService.getScriptProperties(); props.setProperties({ DB_URL: 'jdbc:mysql://db.bedrijf.nl:3306/klanten', DB_USER: 'appgebruiker', DB_PASS: 'VeiligWachtwoord123!', }); } function getVerbinding() { const props = PropertiesService.getScriptProperties(); return Jdbc.getConnection( props.getProperty('DB_URL'), props.getProperty('DB_USER'), props.getProperty('DB_PASS') ); } ``` ## SELECT-queries uitvoeren Gebruik altijd een `PreparedStatement` met getypeerde setters in plaats van string-concatenatie. Dat beschermt je tegen SQL-injectie. ```javascript function leesKlanten() { const conn = getVerbinding(); try { const stmt = conn.prepareStatement( 'SELECT id, naam, email, stad FROM klanten WHERE actief = ? ORDER BY naam LIMIT ?' ); stmt.setBoolean(1, true); stmt.setInt(2, 100); const rs = stmt.executeQuery(); const resultaten = []; while (rs.next()) { resultaten.push({ id: rs.getInt(1), naam: rs.getString(2), email: rs.getString(3), stad: rs.getString(4), }); } rs.close(); stmt.close(); Logger.log(`${resultaten.length} klanten opgehaald`); return resultaten; } finally { conn.close(); } } ``` ## INSERT en UPDATE met transacties Door `setAutoCommit(false)` te gebruiken voer je meerdere schrijfacties uit als een geheel. Mislukt er een, dan draai je alles terug met `rollback()`. ```javascript function voegKlantToe(naam, email, stad) { const conn = getVerbinding(); conn.setAutoCommit(false); try { const stmt = conn.prepareStatement( 'INSERT INTO klanten (naam, email, stad, aangemaakt_op) VALUES (?, ?, ?, NOW())' ); stmt.setString(1, naam); stmt.setString(2, email); stmt.setString(3, stad); stmt.execute(); const logStmt = conn.prepareStatement( 'INSERT INTO audit_log (actie, details, tijdstip) VALUES (?, ?, NOW())' ); logStmt.setString(1, 'KLANT_AANGEMAAKT'); logStmt.setString(2, `Klant ${naam} (${email}) aangemaakt`); logStmt.execute(); conn.commit(); Logger.log('Klant toegevoegd en audit-log bijgewerkt'); } catch(e) { conn.rollback(); Logger.log('Fout, rollback uitgevoerd: ' + e.message); throw e; } finally { conn.close(); } } ``` ## Bulk-insert vanuit Sheets Voor grotere volumes verzamel je statements met `addBatch()` en stuur je ze in een keer weg met `executeBatch()`. Dat is veel sneller dan rij voor rij uitvoeren. ```javascript function syncSheetNaarDatabase() { const blad = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Imports'); const data = blad.getDataRange().getValues(); const conn = getVerbinding(); conn.setAutoCommit(false); try { const stmt = conn.prepareStatement( 'INSERT INTO producten (naam, prijs, categorie) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE prijs = VALUES(prijs)' ); let succes = 0; let fouten = 0; for (let i = 1; i < data.length; i++) { try { stmt.setString(1, data[i][0]); stmt.setFloat(2, parseFloat(data[i][1])); stmt.setString(3, data[i][2]); stmt.addBatch(); succes++; } catch(e) { fouten++; Logger.log(`Fout rij ${i}: ${e.message}`); } } stmt.executeBatch(); conn.commit(); Logger.log(`${succes} rijen gesynchroniseerd, ${fouten} fouten`); } catch(e) { conn.rollback(); throw e; } finally { conn.close(); } } ``` ## Cloud SQL verbinding Voor Google Cloud SQL gebruik je `Jdbc.getCloudSqlConnection()`. Die methode heeft geen IP-whitelisting nodig, omdat de verbinding binnen de Google-infrastructuur blijft. De URL heeft de vorm `jdbc:google:mysql://project:regio:instance/database`. ```javascript function cloudSqlVerbinding() { const cloudSqlUrl = 'jdbc:google:mysql://mijn-project:europe-west4:mijn-instance/database'; const conn = Jdbc.getCloudSqlConnection(cloudSqlUrl, 'gebruiker', 'wachtwoord'); const stmt = conn.createStatement(); const rs = stmt.executeQuery('SELECT COUNT(*) FROM orders'); if (rs.next()) { Logger.log(`Aantal orders: ${rs.getInt(1)}`); } rs.close(); conn.close(); } ``` :::tip title="Sluit verbindingen altijd af" Open verbindingen tellen mee in de quota van je script. Sluit `ResultSet`, `Statement` en `Connection` netjes af, bij voorkeur de verbinding in een `finally`-blok, zodat een fout halverwege geen openstaande verbinding achterlaat. ::: :::faq ### Welke databasetypes ondersteunt JDBC? MySQL, PostgreSQL, Microsoft SQL Server, Oracle en Google Cloud SQL voor MySQL. De prefix in de connectionstring verschilt per type, bijvoorbeeld `jdbc:mysql://`, `jdbc:postgresql://` en `jdbc:sqlserver://`. ### Hoe bescherm ik me tegen SQL-injectie? Gebruik altijd een `PreparedStatement` met getypeerde setters zoals `setString()`, `setInt()` en `setBoolean()` voor queryparameters. Bouw queries nooit op met string-concatenatie van gebruikersinvoer. ### Kan ik verbinden met een database achter een firewall? Apps Script verbindt vanuit de Google-infrastructuur. Voor `Jdbc.getConnection()` moet je de IP-ranges van de Google-databron in je firewall toelaten. Voor Cloud SQL is `Jdbc.getCloudSqlConnection()` eenvoudiger, want die heeft geen IP-whitelisting nodig. ### Waarom krijg ik een verbinding op een lage poort niet werkend? De JDBC-service verbindt alleen met poort 1025 of hoger. Draait je database op een lagere poort, verplaats hem dan of zet er een proxy voor. Controleer ook dat TLS 1.2 of hoger actief is, want oudere TLS-versies zijn uitgeschakeld. ### Waarom loopt mijn script tegen een tijdslimiet aan? Niet de verbinding zelf, maar de totale uitvoeringstijd van een Apps Script-run is begrensd (enkele minuten voor gratis accounts, langer voor Workspace-accounts). Houd queries klein, verwerk in batches en sluit verbindingen direct na gebruik. ::: :::howto title="Database-data synchroniseren naar Sheets" 1. Haal credentials op uit `PropertiesService`. 2. Open een verbinding met `getVerbinding()`. 3. Voer een SELECT-query uit met een `PreparedStatement`. 4. Lees de `ResultSet` in een array van arrays. 5. Wis het doelblad en schrijf de kolomkoppen. 6. Schrijf alle data in een keer weg met `setValues()`. 7. Sluit de verbinding in een `finally`-blok. ::: JDBC maakt van Apps Script een krachtige brug tussen Google Workspace en bestaande bedrijfsdatabases. Door transacties te gebruiken en credentials veilig op te slaan bouw je betrouwbare data-synchronisatie.