SQLite Alternativen zu SQlite, dateibasiert, leicht verschlüsselbar, Nutzerverwaltung

TomBombadil

Mitglied
Ich habe bisher immer mit Sqlite in Java gearbeitet, habe jetzt aber die Herrausforderung das ich eine Anwendung zur privaten Konten/Buget- Verwaltung entwickeln möchte, diese sollte es allerding möglich machen das ich leicht verschiedene Nutzer einrichten kann und zum anderen sich diese gut und sicher verschlüsseln lässt.

Es wäre es wohl möglich mit SQlite und CipherSql eine Verschlüsselung zu realisieren, auch ein Nutzerkonzept ließe sich vermutlich mit einer guten DB Struktur machen. Allerdings scheint mir das alles sehr "zusammengefriggelt", daher meine Frage nach neueren Datenbanksystemen die dies können und gleichzeitig, wie SQlite, dateibasiert sind.

Bei meinen recherchen habe ich 2 recht vielversprechende Systeme gefunden H2 und HSQLDB.
Meine Fragen nun
  • wären diese Systeme geeignet, für meine Zwecke?
  • gibt es noch andere evtl. bessere Alternativen
  • Welche Datenbank-Designer sind für diese Systeme ratsam
    • bei SQlite verwende ich bspw. DB Browser for SQlite
zum Schluss habt ihr für das favorisierte System Codebeispiele wie man es in seinem Java Programm einbinden kann?
 

Oneixee5

Top Contributor
Sqlite ist, wie ich finde keine gute Wahl für Java.

H2 wird allgemein besser bewertet und ich weiß, dass H2 eine einfache Möglichkeit zur Verschlüsselung bietet: http://www.h2database.com/html/features.html#file_encryption

Datenbank-Designer - braucht kein Mensch ... aber so ein GUI-Tool gibt es auch.

Ob die Verschlüsslung sicher ist, ist so eine Sache. Wenn du die DB weitergibst, dann musst du auch die Zugangsdaten und Verschlüsselung weitergeben. Auch wenn diese innerhalb des Programms liegen. Durch diese Weitergabe ist natürlich der Zugriff möglich. Dieser Zugriff kann 'belauscht' oder die die Zugriffsdaten können aus dem Programm extrahiert werden. Also wenn du die DB inkl. der Zugangsdaten weitergibst, dann ist diese genauso sicher wie eine weitergegebene unverschlüsselte DB.
 

Robert Zenz

Top Contributor
ch habe bisher immer mit Sqlite in Java gearbeitet, habe jetzt aber die Herrausforderung das ich eine Anwendung zur privaten Konten/Buget- Verwaltung entwickeln möchte, diese sollte es allerding möglich machen das ich leicht verschiedene Nutzer einrichten kann und zum anderen sich diese gut und sicher verschlüsseln lässt.
Wie @Oneixee bereits sagte, die H2 wird eine gute Wahl sein. Sie ist von Syntax relativ aehnlich zu PostgreSQL und im Gegensatz zu SQLite hat H2 brauchbare Datentypen.

Das Verschluesseln wuerde ich so realisieren dass du die Datenbank als primaere "Datei" verwendest. Also der Benutzer waehlt aus wo er seine Daten speichern will, in welcher Datei, und das ist die Datei von der H2. Verschluesseln kannst du dann einfach symmetrisch machen und der Benutzer gibt den Schluessel als Passwort ein. Damit hast du automatisch die Benutzer abgetrennt, denn diese brauche ihre eigenen Dateien (wo alle Daten drinnen sind) und das Passwort dazu.
 

TomBombadil

Mitglied
Wie @Oneixee bereits sagte, die H2 wird eine gute Wahl sein. Sie ist von Syntax relativ aehnlich zu PostgreSQL und im Gegensatz zu SQLite hat H2 brauchbare Datentypen.
gut dann wird es wohl H2 werden

Wenn ich das alles richtig gelesen habe kann man in H2 ganz normales Standard SQL verwenden?
Und H2 wäre wohl auch optional in der Lage eine Serverdatenbank bereitzustellen? Und wohl auch eine, wie auch immer ich mir das genau vorstellen darf, eine inMemory Datenbank zu stellen?

Sorry das ich so blöde Fragen stelle aber SQLite und etwas MySQL (nur mit PHP) war bisher das einzige Datenbanksystem mit dem ich gearbeitet habe. Zu SQLite kam ich durch die Android Entwicklung weil es damals dort als Datenbank vorgeschrieben war.

Ob die Verschlüsslung sicher ist, ist so eine Sache. Wenn du die DB weitergibst, dann musst du auch die Zugangsdaten und Verschlüsselung weitergeben. Auch wenn diese innerhalb des Programms liegen. Durch diese Weitergabe ist natürlich der Zugriff möglich. Dieser Zugriff kann 'belauscht' oder die die Zugriffsdaten können aus dem Programm extrahiert werden. Also wenn du die DB inkl. der Zugangsdaten weitergibst, dann ist diese genauso sicher wie eine weitergegebene unverschlüsselte DB.
Hier habe ich mir gedacht das ich einen Anmeldedialog mache wie ihn viele Websiten haben. Wo man die Option hat entweder ein Konto anzulegen oder sich einzuloggen. Dabei dann das eingegebene Passwort mit SHA-512 Bit zu verschlüsseln.


Das Verschluesseln wuerde ich so realisieren dass du die Datenbank als primaere "Datei" verwendest. Also der Benutzer waehlt aus wo er seine Daten speichern will, in welcher Datei, und das ist die Datei von der H2. Verschluesseln kannst du dann einfach symmetrisch machen und der Benutzer gibt den Schluessel als Passwort ein. Damit hast du automatisch die Benutzer abgetrennt, denn diese brauche ihre eigenen Dateien (wo alle Daten drinnen sind) und das Passwort dazu.
Wenn ich das dann richtig verstehe bräuchte ich in dem Fall eine Datenbank die die Nutzer selbst speichert. Und dann für jeden Nutzer eine eigene Kontodatenbank-Datei? Richtig?


Ich seh schon das Projekt wird lustig, ich muss es mal unabhängig von Datenbank auch komplett DAU sicher aufbauen.
EDIT Wenn ich so drüber nachdenke darf ich wegen der DAU Sicherheit dem Nutzer nicht den Speicherort überlassen, wo die DB-Dateien gespeichert werden sollte mein Programm selbstständig ohne den Nutzer machen
 
Zuletzt bearbeitet:

Robert Zenz

Top Contributor
Wenn ich das dann richtig verstehe bräuchte ich in dem Fall eine Datenbank die die Nutzer selbst speichert. Und dann für jeden Nutzer eine eigene Kontodatenbank-Datei? Richtig?
Ja, so haette ich mir mir das vorgestellt. Also, unter der Vorraussetzung dass es fuer private Nutzer ist, wenn es fuer eine Firma ist dann einen schoenen Datenbank-Server mit "Mandanten"-Faehigkeit vermutlich. Achso, das setzt natuerlich eine Desktop-Anwendung voraus. Wenn du da eine Web-Anwendung baust dann eher weniger.

Wenn ich das alles richtig gelesen habe kann man in H2 ganz normales Standard SQL verwenden?
Und H2 wäre wohl auch optional in der Lage eine Serverdatenbank bereitzustellen? Und wohl auch eine, wie auch immer ich mir das genau vorstellen darf, eine inMemory Datenbank zu stellen?
Ja, ja und ja. Und H2 kann eben auch direkt aus Dateien betrieben, so wie SQLite eben auch.
 

TomBombadil

Mitglied
Ja, so haette ich mir mir das vorgestellt. Also, unter der Vorraussetzung dass es fuer private Nutzer ist, wenn es fuer eine Firma ist dann einen schoenen Datenbank-Server mit "Mandanten"-Faehigkeit vermutlich. Achso, das setzt natuerlich eine Desktop-Anwendung voraus. Wenn du da eine Web-Anwendung baust dann eher weniger.
Es ist für Privatnutzer und soll schon eine komplett clientseitige offline Desktop Anwendung sein! Evtl. werde ich irgendwann mal noch eine Android-App draus machen.
Hatte auch schon dran gedacht ganz einfach eine Base Anwendung draus zu machen, aber das hatte ich schon mal bei einem anderen Projekt versucht und bin dann einfach zu schnell an die Grenzen des Programms gestoßen. Außerdem kann man eine Base Datei noch schwerer DAU sicher machen.

Daher doch besser das gute alte Java ;)
 

KonradN

Super-Moderator
Mitarbeiter
Dabei dann das eingegebene Passwort mit SHA-512 Bit zu verschlüsseln.
Also erst einmal der generelle Hinweis: Passwörter werden nicht gespeichert. Auch nicht verschlüsselt.
Statt dessen wird lediglich ein (salted) hash gespeichert. Das hat den Vorteil, dass es eben keinen Weg gibt, das Passwort erneut zu erhalten.

Und wenn es eine Desktop Anwendung ist: Wieso da noch eine Anmeldung oder so? Wieso sollte das notwendig sein? Der übliche Weg wäre doch eigentlich: Jeder User hat einen Account beim Rechner. Und wenn Du einmal angemeldet bist, dann kannst Du das Programm starten und hast Zugriffe auf die Ressourcen des Users. Sprich: Die Daten können einfach im Home-Verzeichnis liegen. Da bräuchte man dann in der Regel auch keinerlei Verschlüsselung (Das wäre dann - so man es möchte - auf Systemebene, dass das Homeverzeichnis verschlüsselt wird oder die ganze Festplatte oder oder oder ....

Oder hast Du ein spezielles Szenario? Also sowas wie einen Rechner im Kiosk Mode? Das bringt halt massive Probleme mit sich. Wenn jemand die Kontrolle über den Computer hat, dann kann er halt auch direkt auf das Programm zugreifen. Kann da z.B. mit dem Debugger etwas machen. Kann ggf. sogar Code anpassen / modifizieren. Jede Verschlüsselung macht da dann wenig Sinn, da es auch direkten Zugriff auf die Entschlüsselung gibt.
 

Robert Zenz

Top Contributor
Und wenn es eine Desktop Anwendung ist: Wieso da noch eine Anmeldung oder so? Wieso sollte das notwendig sein? Der übliche Weg wäre doch eigentlich: Jeder User hat einen Account beim Rechner. Und wenn Du einmal angemeldet bist, dann kannst Du das Programm starten und hast Zugriffe auf die Ressourcen des Users. Sprich: Die Daten können einfach im Home-Verzeichnis liegen. Da bräuchte man dann in der Regel auch keinerlei Verschlüsselung (Das wäre dann - so man es möchte - auf Systemebene, dass das Homeverzeichnis verschlüsselt wird oder die ganze Festplatte oder oder oder ....
Es ist immer nett wenn eine Applikation soetwas unterstuetzt. Zum einen gibt es ja noch Betriebssysteme auf denen Benutzertrennung etwas fragwuerdig sit \hustwindowshust\ und zum anderen koennte es dennoch durchaus sein dass entweder sich mehrere Personen ein Konto teilen (Firmenrechner?) oder eine Person gerne unterschiedliche Dateien haette. Aehnlich wie mit Keepass (einzelne Passwort-Dateien), Firefox (Profile) oder auch viele Buchhaltungsprogramme fuer private Personen bieten auch solche Moeglichkeiten.

Android ist auch ein sehr schoenes Beispiel wo mehrere Benutzer schwierig ist, weil das ist etwas was je anch Geraet/Hersteller abgeschaltet sein kann.
 

KonradN

Super-Moderator
Mitarbeiter
Ich will ja nicht diese Punkte diskutieren - dass es Szenarien gibt, habe ich ja bereits selbst angedeutet. Das sollte man aber prüfen...

Windows hat eine gute Benutzer Trennung. Und das eigentlich schon seit Windows NT 3.51. Aber das ist auch noch nicht, was mich getriggert hat ...

mehrere Personen ein Konto teilen (Firmenrechner?)
Was? Sowas gibt es wirklich? Das sind dann so kleine Firmen, wo es eigentlich keine User-Accounts bewusst gibt oder an was dachtest Du da?

Denn ich kenne es so, dass man jährlich mit Pflichtschulungen gequält wird zu Security, Datenschutz und was weiss ich nicht alles ... Ein geteilter User Account ist da irgendwie undenkbar :) Und wenn da die Passwörter doch eh bekannt sind: Wozu brauchst du das dann? Du hast das doch eh auf einem Zettel am Monitor ... Oder gibt es einen Grund, wieso Du glaubst, dass die Security beim Betriebssystem ignoriert wird, aber bei der App wird es dann wichtig?

Und was mir da natürlich einfällt (Spaß): Diese ganze Passwort Sache ist doch eh Unsinn! Es gibt eine klare Arbeitsanweisung die besagt, dass alle Passwörter "streng geheim" sein müssen. (Was wir für Probleme haben, weil manche Software einfach immer Großbuchstaben oder Zahlen haben will ... aber die sind dann halt einfach nicht nutzbar bei uns in der Firma ...)

Also generell ja: Es mag Umstände geben, die es erforderlich machen. Aber es ist relativ sinnlos, da es sehr einfach zu knacken ist und es gibt in der Regel bessere Möglichkeiten auf dem System.
 

TomBombadil

Mitglied
Ich habe mich nun entschieden das Konzept der Datenspeicherung ein klein wenig von KeePass abzuschauen. ich habe vor eine eigene Nutzerverwaltung zu machen unabhängig von System Accounts, werde es so lösen das ich den persönlichen Odner als standard Speicherort abgeben werden, dieser aber auch gändert werden kann. Der Nutzer meldet sich per Nutzername und Passwort im Programm an und bekommt dann eine Datenbank erstellt.
Zu Nutzerverwaltung habe ich mich jetzt für JSON entschieden, eine eigene Datenbank mit letztlich nur einer Tabelle schien mir übertrieben

Zur Datenbanksteuerung habe ich nun rausgefunden das man H2 ja auch recht komfortabel mit JDBC verwalten kann und dafür hatte ich mir schon mal für SQLite ein einfaches Hilfsmittel geschrieben, welches ich jetzt auf H2 angepasst habe.


Java:
package org.budgetman.db;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JTable;
import org.budgetman.db.User;
import javax.swing.table.DefaultTableModel;
import org.h2.tools.RunScript;

import util.Verschluesselung;

import org.budgetman.db.DBMethoden;

public class DBVerbindungH2 {

    private static final String H2DRIVER = "org.h2.Driver";

    private File dbFile;
    private Connection connection;

    private String username;
    private String password;

    public DBVerbindungH2(File dbFile, String username, String password) {
        super();
        this.dbFile = dbFile;
        this.username = username;
        this.password = password;
    }
    
    public DBVerbindungH2(User user) {
        super();
        this.dbFile = user.getUserDB();
        this.username = user.getUsername();
        this.password = user.getPassword();
    }

    /*
     * der Vollständigkeit halber eingefügt aber bei H2 scheint keine Notwendigkeit
     * zu bestehen den H2 Treiber zu ladem sollte es dennoch mal nötig sein kann
     * diese Methode gemeinsam mit der Konstante H2DRIVER genutzt werden
     */
    private boolean dbTreiberLaden(String treiberstring) {
        try {
            Class.forName(treiberstring);
            System.out.println("JDBC-Treiber erfolgreich geladen");
            return true;
        } catch (ClassNotFoundException e) {
            System.err.println("Fehler beim Laden des JDBC-Treibers");
            e.printStackTrace();
            return false;
        }
    }

    public DBMethoden getDBMethoden() {
        return new DBMethoden() {

            @Override
            public boolean open() {
                try {
                    connection = DriverManager.getConnection("jdbc:h2:" + dbFile.getAbsolutePath(), username, password);
                    return true;
                } catch (SQLException e) {
                    e.printStackTrace();
                    return false;
                }
            }

            @Override
            public boolean isOpen() {
                try {
                    return connection != null && !connection.isClosed();
                } catch (SQLException e) {
                    e.printStackTrace();
                    return false;
                }
            }

            @Override
            public void close() {
                try {
                    if (connection != null && !connection.isClosed()) {
                        connection.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public boolean isClosed() {
                try {
                    return connection == null || connection.isClosed();
                } catch (SQLException e) {
                    e.printStackTrace();
                    return true;
                }
            }

            @Override
            public boolean sqlStatement(String sqlStatement) {
                try (Statement statement = connection.createStatement()) {
                    return statement.execute(sqlStatement);
                } catch (SQLException e) {
                    e.printStackTrace();
                    return false;
                }
            }

            @Override
            public void sqlStatements(File sqlFile) {
                try {
                    FileReader fileReader = new FileReader(sqlFile.getAbsolutePath());
                    RunScript.execute(connection, fileReader);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void sqlStatements(List<String> statements) {
                try (Statement statement = connection.createStatement()) {
                    for (String sqlStatement : statements) {
                        statement.addBatch(sqlStatement);
                    }
                    statement.executeBatch();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void sqlStatements(String[] statements) {
                sqlStatements(List.of(statements));
            }

            @Override
            public ResultSet sqlSelect(String select) {
                try (Statement statement = connection.createStatement()) {
                    return statement.executeQuery(select);
                } catch (SQLException e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @Override
            public ResultSet sqlSelect(String spalten, String tabelle, String where, String orderBy, String groupBy) {
                String selectStatement = "SELECT " + spalten + " FROM " + tabelle;
                if (where != null && !where.isEmpty()) {
                    selectStatement += " WHERE " + where;
                }
                if (orderBy != null && !orderBy.isEmpty()) {
                    selectStatement += " ORDER BY " + orderBy;
                }
                if (groupBy != null && !groupBy.isEmpty()) {
                    selectStatement += " GROUP BY " + groupBy;
                }

                return sqlSelect(selectStatement);
            }

            @Override
            public ResultSet[] sqlSelects(List<String> selects) {
                ResultSet[] resultSets = new ResultSet[selects.size()];
                int index = 0;
                for (String selectStatement : selects) {
                    resultSets[index++] = sqlSelect(selectStatement);
                }
                return resultSets;
            }

            @Override
            public ResultSet[] sqlSelects(String[] selects) {
                return sqlSelects(List.of(selects));
            }

            @Override
            public ResultSet[] sqlSelects(File sqlFile) {
                try {
                    List<String> sqlStatements = Files.readAllLines(sqlFile.toPath(), StandardCharsets.UTF_8);

                    List<ResultSet> resultSets = new ArrayList<>();

                    try (Statement statement = connection.createStatement()) {
                        for (String sqlStatement : sqlStatements) {
                            if (!sqlStatement.trim().isEmpty()) {
                                // Führe das SQL-Statement aus und füge das ResultSet zur Liste hinzu
                                ResultSet resultSet = statement.executeQuery(sqlStatement);
                                resultSets.add(resultSet);
                            }
                        }
                    }

                    return resultSets.toArray(new ResultSet[0]);
                } catch (IOException | SQLException e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @Override
            public ResultSet getTabellenInhalt(String tabelle) {
                return sqlSelect("SELECT * FROM '" + tabelle + "';");
            }

            @Override
            public ResultSet getTabellenInhalt(String tabelle, String[] spalten) {
                  String result = String.join(", ", spalten);
                  return sqlSelect("SELECT " + result + " FROM '" + tabelle + "';");
            }

            @Override
            public JTable getASJTable(ResultSet resultSet) {
                return new JTable(new ResultSetTableModel(resultSet));
            }
            
            @Override
            public JTable refillJTable(ResultSet resultSet, JTable jTable) {
                jTable.setModel(new ResultSetTableModel(resultSet));
                return jTable;
            }

            @Override
            public int getAnzahlzeilen(String tabelle) {
                String countStatement = "SELECT COUNT(*) FROM " + tabelle;
                try (Statement statement = connection.createStatement();
                        ResultSet resultSet = statement.executeQuery(countStatement)) {
                    if (resultSet.next()) {
                        return resultSet.getInt(1);
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                return 0;
            }

            @Override
            public String toString(ResultSet resultSet) {
                try {
                    StringBuilder resultString = new StringBuilder();
                    while (resultSet.next()) {
                        int columns = resultSet.getMetaData().getColumnCount();
                        for (int i = 1; i <= columns; i++) {
                            resultString.append(resultSet.getString(i)).append(" ");
                        }
                        resultString.append("\n");
                    }
                    return resultString.toString();
                } catch (SQLException e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @Override
            public ResultSet getDatensatz(int id, String tabelle) {
                String selectStatement = "SELECT * FROM " + tabelle + " WHERE id = " + id;
                return sqlSelect(selectStatement);
            }

            @Override
            public void setDBVersion(int dbVersion) {
                String[] statements = { "CREATE TABLE IF NOT EXISTS dbversion (curversion INTEGER);",
                        "DELETE FROM dbversion;", "INSERT INTO dbversion (curversion) VALUES ('" + dbVersion + "');" };
                sqlStatements(statements);
            }

            @Override
            public int getDBVersion() {
                ResultSet resultSet = sqlSelect("SELECT curversion FROM dbversion;");
                try {
                    resultSet.next();
                    return resultSet.getInt("curversion");
                } catch (SQLException e) {
                    e.printStackTrace();
                    return -1;
                }
            }

            @Override
            public boolean dbExists() {
                try {
                    Statement selectStatement = connection.createStatement();
                    selectStatement.executeQuery("SELECT curversion FROM dbversion");
                    return true;
                } catch (SQLException e) {
                    return false;
                }
            }

            @Override
            public void clearTable(String table) {
                String deleteAllStatement = "DELETE FROM " + table;
                sqlStatement(deleteAllStatement);
            }

            @Override
            public void dropTable(String table) {
                String dropTableStatement = "DROP TABLE IF EXISTS " + table;
                sqlStatement(dropTableStatement);
            }
        };
    }

}


Java:
package org.budgetman.db;

import java.io.File;
import java.util.Arrays;

import util.Util;

/**
 *
 * Die Klasse kann Datenbanken erstellen und mit hilfe einer versionsnummer auch
 * Upgraden Diese Klasse ist derzeit nur fuer SQLite-Datenbanken vorgesehen
 *
 * Benoetigt folgende Bibliotheken sqlite-jdbc-3.8.7.jar
 *
 * @author Jan Simon erste version am 29.07.2017 fertiggestellt
 *
 */

public class DBErstellung implements Runnable {

    private File[] sqlFiles;
    private DBMethoden dbMethoden;
    private File dbFile;
    private int curDbVersion;

    /**
     * Konstruktor der Klasse alle Paramter muessen uebergeben werden Nach der
     * implementierung des Konstruktors muss nur noch die Methode run() aufgerufen
     * werden
     *
     * @param sqlfiles     Die SQL-Files aus denen die Datenbank erstellt werden
     *                     soll, diese werden intern alphabetisch bzw. nummerisch
     *                     aufsteigend sortiert
     * @param dbMethoden   eine vollstaendige impelmentierung des Interface
     *                     {@link DBMethoden} erforderlich Es kann ueber die Klasse
     *                     {@link DBVerbindung} implementiert werden
     * @param dbfile       Der sqlite-File in den die Datenbank geschrieben werden
     *                     soll. Es ist nur die vollstaendige Pfadangabe
     *                     erforderlich die Datei selbst wird automatisch erzeugt
     * @param curdbversion die Version die die Datenbank bekommen soll WICHTIG fuer
     *                     jede DB-Version muss eine SQL-Datei in SQL-Files
     *                     existieren
     */
    public DBErstellung(File[] sqlFiles, DBMethoden dbMethoden, File dbFile, int curDbVersion) {
        super();
        this.sqlFiles = sqlFiles;
        this.dbMethoden = dbMethoden;
        this.dbFile = dbFile;
        this.curDbVersion = curDbVersion;
    }

    private void dbErstellen() {
        this.dbMethoden.sqlStatements(this.sqlFiles[0]);
        this.dbMethoden.setDBVersion(1);
    }

    private boolean dbPruefen() {
        if (!Util.getDateiTools().ordnerErstellen("datenbank")) {
            // der Ordner musste nicht erstellt werden
            if (this.dbFile.exists()) {
                // Datenbankdatei existiert
                if (this.dbMethoden.open()) {
                    // Datenbank lässt sich öffnen
                    if (this.dbMethoden.dbExists()) {
                        // Standard-Tabelle dbversion existiert

                        // dbverbindung wird absichtlich nicht geschlossen da unmittelbar
                        // nach dbPruefen() noch weitere Methoden die DB Zugriff benoetigigen aufgerufen
                        // werden
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private void dbUpgrade() {
        int curVersion = this.dbMethoden.getDBVersion();
        int zaehler = 1; // Zaehler wird mit 1 initialisiert da 0 bereits fuer die DBErstellung verwendet
                            // wurde

        while (curVersion < this.curDbVersion) {
            this.dbMethoden.sqlStatements(this.sqlFiles[zaehler]);
            curVersion++;
            this.dbMethoden.setDBVersion(curVersion);
            System.out.println("Upgrade auf DBVersion " + curVersion + " wurde erfolgreich durchgefuehrt");
            zaehler++;
        }
    }

    private File[] getSortetFilelist(File[] files) {
        Arrays.sort(files);
        return files;
    }

    /**
     * Alle Funktionen die von dieser Klasse gestartet werden, werden ausschließlich
     * in dieser Methode implementiert um die Klasse spaeter noch in einen Thread
     * ausfuehren zu koennen
     *
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        this.sqlFiles = getSortetFilelist(this.sqlFiles);
        if (!dbPruefen()) {
            // Wenn DB nicht existiert wird diese in DBPruefen() nicht geöffnet
            // Daher wird an dieser Stelle geöffnet
            this.dbMethoden.open();
            dbErstellen();
        }
        dbUpgrade();
        this.dbMethoden.close();

    }

}


Habt ihr anmerkungen/Verbesserungsvorschläge dazu?
 

mihe7

Top Contributor
* der Vollständigkeit halber eingefügt aber bei H2 scheint keine Notwendigkeit * zu bestehen den H2 Treiber zu ladem sollte es dennoch mal nötig sein kann * diese Methode gemeinsam mit der Konstante H2DRIVER genutzt werden
Das wäre nur nötig, wenn Du einen alten JDBC-Treiber (JDBC Version < 4.0) nutzt.

Deine sqlSelect-Methoden dürften nicht funktionieren, d. h. ein bereits geschlossenes ResultSet zurückgeben.

Eine JTable hat in einer DB-Klasse nichts verloren.

Ganz grundsätzlich: niemals eine WHERE-Klausel per Hand aus Strings zusammenbauen, wenn die Parameter nicht ausschließlich unter Deiner Kontrolle stehen. Damit wird der SQL-Injection Tür und Tor geöffnet. Für solche Fälle verwendet man ein PreparedStatement.
 

Oneixee5

Top Contributor
Auf den ersten Blick bin ich nicht so begeistert.
Du hast zwar einen Import "import util.Verschluesselung;" - ich sehe aber keine Nutzung irgend einer Verschlüsselung. Ich hätte erwartet, das für eine verschlüsselte DB auch eine verschlüsselte Verbindung genutzt wird: jdbc:h2:ssl://
Dann hast du eine einzelne Connection, welche du die ganze Zeit offen hältst. Das führt schnell mal zu Deadlocks und ist wahrscheinlich so auch nicht threadsicher. Ein GUI verwendet aber meistens mehrere Threads. Eine Möglichkeit Transaktionen zu behandeln finde ich jetzt auch nicht. Warum werden keine PreparedStatements verwendet? Diese erhöhen die Sicherheit und gerade in Schleifen können sie die DB und das Programm entlasten. SQL aus Strings zusammen zubauen hat immer den Beigeschmack von Sicherheitslücken.
Mein Fazit wäre, da du versuchst mit Verschlüsselung Sicherheit zu suggerieren, dafür ist der DB-Zugriff erstaunlich naiv geschrieben. Sorry aber eine mildere Beschreibung fällt mir gerade nicht ein.
 

TomBombadil

Mitglied
Auf den ersten Blick bin ich nicht so begeistert.
Du hast zwar einen Import "import util.Verschluesselung;" - ich sehe aber keine Nutzung irgend einer Verschlüsselung. Ich hätte erwartet, das für eine verschlüsselte DB auch eine verschlüsselte Verbindung genutzt wird: jdbc:h2:ssl://
Dann hast du eine einzelne Connection, welche du die ganze Zeit offen hältst. Das führt schnell mal zu Deadlocks und ist wahrscheinlich so auch nicht threadsicher. Ein GUI verwendet aber meistens mehrere Threads. Eine Möglichkeit Transaktionen zu behandeln finde ich jetzt auch nicht. Warum werden keine PreparedStatements verwendet? Diese erhöhen die Sicherheit und gerade in Schleifen können sie die DB und das Programm entlasten. SQL aus Strings zusammen zubauen hat immer den Beigeschmack von Sicherheitslücken.
Mein Fazit wäre, da du versuchst mit Verschlüsselung Sicherheit zu suggerieren, dafür ist der DB-Zugriff erstaunlich naiv geschrieben. Sorry aber eine mildere Beschreibung fällt mir gerade nicht ein.
In der Hinsicht fehlt es mir leider an Erfahrung :-( Du bleibst bei deiner Kritik sachlich und fair.
Bin zwar kein blutiger Anfänger Objektorientierung, Datentypen, Kontrollstrukturen, Kapselung etc sind mir schon Begriffe, aber mir fehlt schon noch einiges. Hab halt keinen fachlichen Ansprechpartner, d.h. ich probiere etwas aus, sehe es funktioniert, kann aber meist wenig bis garnicht beurteilen ob das gute Praxis ist und ob es Angriffspunkte gibt. Etwas Try and Error mäßig. Durch ChatGPT hab ich immerhin schon einiges dazulernen können.

Um die Connection zu öffnen und schließen sind open und close Methoden drin.
Soweit ich das verstanden hatte, evtl. liege ich da falsch, kümmert sich H2 selbstständig um die Verschlüsselung, oder?

Den Parameter "jdbc:h2:ssl://" bspw. habe ich nirgends gefunden, hatte z.B. im H2 Tutorial nur den jdbc:h2: Parameter stehen.

Ganz grundsätzlich: niemals eine WHERE-Klausel per Hand aus Strings zusammenbauen, wenn die Parameter nicht ausschließlich unter Deiner Kontrolle stehen. Damit wird der SQL-Injection Tür und Tor geöffnet. Für solche Fälle verwendet man ein PreparedStatement.

Zu den PreparedStatements hab ich leider mal das eine und mal das andere gelesen, daher kann ich sie bisher noch nicht wirklich einordnen an welcher stelle sie praktischen Nutzen haben.
Was würdet Ihr algemein sagen, was man tun sollte um SQL Injections vorbeugen kann?
Das Interface DBMethoden habe ich entwicklet um die option Offen zu halten über eine einheitliche Schnittstelle mit verschieden Datenbanken komunizieren zu können.

Mein Ziel wäre es eine sichere und komfortable Datenbankschnittstelle zu haben.

"Rand- Anektdote"
In meiner Fachinformatiker-Ausbildung war ich derjenige der mit Datenbanken am besten umgehen konnte, oft auch besser als die Dozenten. z.B. Gestaltung von Bewegungstabellen um bei starker Normalisierung SELECTs über mehere Tabellen sicher durchzuführen. Bei SQL hatte ich in der Abschlussprüfung eine 1. Jetzt bekomme ich es mit Profis zu tun und denke mir; "Wozu überhaupt, war ich in der Ausbildung? Genutzt hats nicht viel":confused:
 

LimDul

Top Contributor
Wenn du das machst, um dabei was zu lernen - dann ist es gut. Wenn deine Erwartung ist, dass da was raus kommt, was wirklich sicher & komfortabel ist und andere Leute nutzen können - da sehe ich schwarz. Da fehlt noch extrem viel an Grundlagen.
Thema SQL-Injections - Immer PreparedStatemets nutzen. Immer - ohne Ausnahme. Ich kann mich in meiner Laufbahn an keinen Fall erinnern, wo ich nicht PreparedStatements nutzen konnte.
Datenbankverbindungen verwaltet man in der Regel gepoolt (Siehe z.B. hier https://www.cockroachlabs.com/blog/what-is-connection-pooling/)

Grundsätzlich bietet es sich an, bereits bestehende Konzepte mal anzusehen. Mit JPA gibt es bereits das alles in komfortabel, sicher & schön. Wenn man es von Grund auf zum lernen selber machen will, kann man da zumindest mal sehen, was alles geht.
 

Oneixee5

Top Contributor
JDBC ist nicht die einzige Möglichkeit auf DB's zuzugreifen - aber die Grundlage. In Real-World-Projekten wird pures JDBC eigentlich nicht mehr verwendet. Um aber bei JDBC zu bleiben: Connections solltest du niemals halten, das macht nur Ärger. Man verwendet besser DataSources:
Java:
DataSource createDataSource() {
  JdbcConnectionPool pool = JdbcConnectionPool.create(CONN_URL, "sa", "sa");
  pool.setMaxConnections(10);
  pool.....
  return pool;
}
Oh: Jetzt wird mit Spatzen auf Kanonen geschossen! -> ConnectionPool
Nein, das ist einfach und clever. Das Vorgehen stellt sicher, dass man im Programm immer eine intakte Connection erhält. Also man muss die Connection nicht halten sondern holt die immer wieder neu:
Java:
Connection connection = pooledDatasource.getConnection();
Jetzt schließt man die die Connection nach jeder Nutzung:
Java:
try (Connection connection = pooledDatasource.getConnection()) {
    // -> mein Code
}
Ja das wird hier automatisch gemacht! Beachte die Klammern. https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
Connections aus einem ConnectionPool werden nicht wirklich geschlossen, das Öffnen eine Connection ist sehr teuer. Die Methode #close() gibt die Connection nur an den Pool zurück. Dieser entscheidet dann, was er damit macht. Ist die Connection irgendwie schlecht oder gibt es zu viele, dann werden sie nach allen Regeln der Kunst entsorgt. Ansonsten für später gehalten. Holt man eine neue Connection und es gibt keine Verfügbare mehr, dann wird eine neue erzeugt.
Die Instanz der DataSource kann man problemlos halten, auch als statische Variable.

JDBC-Treiber manuell zu laden ist aktuellen Treibern nicht mehr nötig - obwohl das noch oft so gezeigt wird. Google:"Autoloading of JDBC drivers"
 

Oneixee5

Top Contributor
} catch (SQLException e) {
e.printStackTrace();
}
Das ist keine sinnvolle Fehlerbehandlung. Fehler schreibt man besser in ein Logfile. Nutzer können nie wirklich wiedergeben wie die Fehlermeldung wirklich war - aber eine Logfile per Mail schicken können die meisten Leute.
Der Fehler wird hier auch nicht behandelt sondern einfach ignoriert. Der Zustand deines Programms ist danach nicht mehr vorhersehbar.
 

mihe7

Top Contributor
Zu den PreparedStatements hab ich leider mal das eine und mal das andere gelesen, daher kann ich sie bisher noch nicht wirklich einordnen an welcher stelle sie praktischen Nutzen haben.
Bei einem PreparedStatement werden in der Query Parameter verwendet, die Du dann mit entsprechenden Methoden setzt. Ich verwende wie @LimDul praktisch immer PreparedStatements.

Aus einem statement.executeUpdate("DELELTE FROM tabelle WHERE id = " + id) wird etwas wie
Java:
public void removeRecord(String recordId) {
    try(PreparedStatement stmt = conn.prepareStatement("DEELETE FROM tabelle WHERE id = ?")) {
        stmt.setString(1, recordId); // die 1 kennzeichnet den 1. Parameter in der Abfrage
        stmt.executeUpdate();
    }
    // ...
}

Dadurch gibt es keine Möglichkeit für SQL-Injections.

Warum?

Im Fall eines Statements gibst Du durch das Zusammenbauen des Strings einem potentiellen Angreifer Zugriff auf den SQL-Code. Der wird dann an die DB gesendet, dort übersetzt und ausgeführt -> Problem. Im Fall eines PreparedStatements hat der Angreifer keinen Zugriff auf den SQL-Code. Dieser wird vorab mit Parametern versehen, an die DB gesendet und dort übersetzt. Erst im Anschluss werden die Parameter mit Werten gefüllt. Jetzt ist es völlig egal, was Du da reinschreibst, denn das Übersetzen des SQL-Codes ist bereits abgeschlossen. Der Wert des Parameters wird also nicht als SQL-Statement verstanden und kann damit auch keinen Schaden anrichten.
 

TomBombadil

Mitglied
Erstmal Danke, für die vielen guten Antworten!

Ja es ist in gewisser Weise ein Übungsprojekt, das aber gerne in der privaten Praxis Anwendung finden soll. Ich schreibe es auch für den Eigenbedarf ;-). klar ich könnte mich einfach auch am Markt bei vorhandener Software in der Richtung bedienen, aber selbst schreiben ist cooler :cool: . Ich könnte mir auch vorstellen es, wenn mal eine brauchbare erste Version fertig ist es als OpenSource Projekt auf GitHub zu Hosten.

Der Hinweis mit den Logfiles ist gut, gibt es da evtl. schon eine fertige Bibliothek o.ä. für? Ich hatte mir zwar auch mal selbst einen LogWriter geschrieben wäre aber nicht so sicher ob der einem Real-Projekt wirklich standhält.

Ich mache jetzt erstmal eine kurze Projektpause. Muss noch einiges anderes erledigen.
 

TomBombadil

Mitglied
Mit JPA konkurrieren wird schwer ;)
Das dachte ich mir auch. Auch deswegen die Projektpause, weil ich feststellen musste das zum einen, an einigen stellen Grundlagen fehlen, zum anderen aber ich mir ja auch das leben leichter machen kann ein komplettes Framework wie Jakarta zu verwenden. Diese soll ja unter anderem die Kommunikation mit Datenbanken auf ein höheres Abstraktionslevel heben, wenn ich das richtig verstanden habe kann man dort dann die JPQL (Java Persistence Query Language) als einheitliche Datenbank-Abfrage Sprache benutzen?

Ich hatte schon lange mal mich mit Jakarta bzw. Java EE befassen wollen, mir fehlt noch der richtige Einstieg. Könnt ihr da gute Tutorials empfehlen? Ist Jakarta praxistauglich grade auch im Hinblick darauf was in Unternehmen eingesetzt wird, oder wäre es besser sich mit einem anderen Java-Framework zu befassen?
Ansonsten was macht Jakarta leichter und evtl. auch effizienter, als ich das mit JavaSE könnte?
 

mihe7

Top Contributor
Jakarta EE ersetzt nicht Java SE, sondern ist schlicht eine Sammlung von verschiedenen APIs, z. B. für Dependency Injection, Mail, Persistenz usw.

JPA (Jakarta Persistence API) bildet Objekte auf relationale Datenbanksysteme ab. Das Transaktionsmanagement kann man z. B. einem EJB-Container überlassen.
 

Oneixee5

Top Contributor
Java selbst bietet Logging an: https://javabeginners.de/Allgemeines/Logging/Einfaches_Logging.php. Meistens wird man aber SL4J: https://www.slf4j.org/ verwenden. Das ist eine Art Schnittstelle ohne konkrete Implementierung. Dann kann jeder, der die Anwendung nutzt, sein eigenes Loggin-Framework verwenden. Etwa Logback oder Log4J2.
Für eine private Nutzung würde ich Jakarta EE eher nicht in Betracht ziehen. Ich denke da eignen sich leichtgewichtigere Ansätze besser, etwa Spring Boot. Auch hier kann man JPA verwenden und ich finde sogar noch besser und einfacher als in Jakarta EE. Um das Ganze einfach zu halten, kann man dann auch Spring Data JDBC verwenden. Das hat mit "deinem" JDBC nicht viel gemeinsam, ist aber einfacher und schneller als JPA.
 
Zuletzt bearbeitet:
Ähnliche Java Themen
  Titel Forum Antworten Datum
B SQLite + jdbc + IntelliJ-Consumer = "No suitable driver found..." Datenbankprogrammierung 15
Maxim6394 JPA 3.2 & SQLite - LocalDateTime wird falsch geladen Datenbankprogrammierung 1
Maxim6394 EclipseLink + SQLite | Unable to acquire a connection from driver [null] Datenbankprogrammierung 6
J SQLite Abfrage fehlerhaft - komme nicht weiter - please help. Datenbankprogrammierung 3
thor_norsk SQLite Fehlermeldung Datenbankprogrammierung 4
N JDBC SQLITE und Cascading Datenbankprogrammierung 2
B SQlite Datenbank, trotz Statements wurden nicht alle Zeilen erzeugt? Datenbankprogrammierung 35
B SQLite Befehl bauen? Datenbankprogrammierung 4
D SQLite Datenbank in Android Studio (Java) durchsuchen Datenbankprogrammierung 3
thobren Projekt SQlite! Wie kann ich auf auf SQlite Daten zugreifen? Datenbankprogrammierung 4
Davee SQLite SQLite Datenbank lässt sich nicht auf anderen PCs öffnen Datenbankprogrammierung 8
B Wie kopieren ich eine Spalte von einer Tabelle in eine andere Tabelle SQLite durch java code? Datenbankprogrammierung 26
D SQLite Collections oder Arrays in SQLite abbilden Datenbankprogrammierung 7
N ORM für Sqlite Datenbankprogrammierung 4
M SQLite Datenbank mit SQLite Datenbankprogrammierung 7
N Sqlite DB mit Java wird auf Linuxsystem nicht gefunden Datenbankprogrammierung 9
N SQLite Datenbankprogrammierung 2
S Daten von SQLite Datenbank nutzen Datenbankprogrammierung 5
B SQLite Frage zu SQLite Datenbankverbindung Datenbankprogrammierung 7
E Sqlite-jdbc Mitliefern Datenbankprogrammierung 4
X Sqlite Fks Datenbankprogrammierung 4
C JDBC und SQLite Datenbank Datenbankprogrammierung 8
X SQLite SQLite Programm beendet/führt nicht weiter aus Datenbankprogrammierung 12
Sam96 SQLite mit JavaFX Datenbankprogrammierung 1
T sqlite select Datenbankprogrammierung 12
V SQLite Performance: 1 Datei mit einzelnen Einträgen gegenüber SQLite Datenbankprogrammierung 7
F Java SQLite Error Datenbankprogrammierung 19
F Sqlite cannot commit Datenbankprogrammierung 2
H SQLite Sqlite Datenbank direkt einbinden. Datenbankprogrammierung 5
U Dom Parser und SQLite füllen Datenbankprogrammierung 5
D SQLite Datenkbank auf WebServer möglich? Datenbankprogrammierung 4
M Datenbankausgabe .jsp per SQLite Datenbankprogrammierung 7
J SQLite Login Datenbank Datenbankprogrammierung 2
M SQLite Einstieg mit SQLite, wohin mit der DLL? Datenbankprogrammierung 7
M SQLite Speicherpfad Datenbankprogrammierung 0
G SQLite SQLite Select für View vereinfachen/optimieren Datenbankprogrammierung 4
G sqlite innerjoin Datenbankprogrammierung 5
G SQLite Daten aus SQLite DB in andere SQLite DB importieren Datenbankprogrammierung 4
R sqlite UPDATE wirkt nicht aus Java Datenbankprogrammierung 7
G SQLite SQLite Abfrage Datenbankprogrammierung 4
F SQLite-Extensions unter Java Datenbankprogrammierung 2
H SQLite mit DefaultTableModel synchronisieren Datenbankprogrammierung 5
D SQLite Statement nimmt keine Namen aus getter-Methoden Datenbankprogrammierung 11
L SQLite fügt nur den ersten Datensatz ein Datenbankprogrammierung 2
S SQLite Ausführbares Jar mit SQLite DB Datenbankprogrammierung 4
F [SQLite] Mehrere Datensätze einfügen Datenbankprogrammierung 12
H SQLite Datenkbank erstellen Datenbankprogrammierung 3
S Abfrage auf SQLite-DB Datenbankprogrammierung 2
Kasoki SQLite SQLite oder doch XML!? Datenbankprogrammierung 2
G SQLite Abfrage, ob in Tabelle X Spalte Y existiert Datenbankprogrammierung 4
G SQLJet (SQLite) - Mehrbenutzerzugriff auf Datenbank handhaben Datenbankprogrammierung 1
S SQLite in JAR Datenbankprogrammierung 8
J SQLite --> Java SDK Datenbankprogrammierung 7
P Datenbank für Java Anwendung wie SQLite ohne Installation Datenbankprogrammierung 4
P Sqlite API für JAVA ? Datenbankprogrammierung 9
feuervogel SQLite unter Linux mit Eclipse einrichten Datenbankprogrammierung 8
K SQLite Datenbankprogrammierung 5
S SQLite oder RDBMS als Datei(nicht Client/Server) Datenbankprogrammierung 5

Ähnliche Java Themen

Neue Themen


Oben