Wiele aplikacji, zwłaszcza te, które operują na większych ilościach danych wymaga wydajnego i wygodnego sposobu ich przechowywania. Z praktycznego punktu widzenia dobrze jest, jeśli umożliwi on dostęp do danych także spoza naszego programu. Kilka lekcji temu, pokazałem jak przechowywać dane w pliku tekstowym w formacie CSV. Często jest to bardzo dobre rozwiązanie, czasem jednak ze względu na ilości i rodzaj przechowywanych danych wygodniej jest użyć bardziej zaawansowanych rozwiązań jak bazy danych.
W tej lekcji pokażę jak utworzyć prostą bazę danych a także wykonać podstawowe czynności jak wstawianie, wyszukiwanie, zmiana i usuwanie danych. W naszym programie użyjemy systemu zarządzania bazami danych o nazwie SQlite, która nie wymaga instalacji serwera bazodanowego, jest więc wygodnym rozwiązaniem dla samodzielnych aplikacji. Zaznaczam, że nie będę tu omawiał języka SQL
używanego w komunikacji z bazą ani omawiał teorii relacyjnych baz danych, do których należy SQLite. Trzeba jednak jasno powiedzieć, że jeśli chce się korzystać programistycznie z takich baz danych to trzeba tą wiedzę uzupełnić, przynajmniej w podstawowym zakresie.
Pobranie biblioteki sqlite-jdbc
p>Naszą przygodę z bazami danych zaczniemy od pobrania biblioteki sqlite-jdbc
z repozytorium. Wybierz najnowszą wersję, w chwili pisania tej lekcji jest to plik sqlite-jdbc-3.8.10.1.jar
. Pobrany plik .jar
umieść w stosownym, czyli takim, które jest dla Ciebie wygodne, miejscu na swoim dysku a następnie dołącz go jako bibliotekę do nowo utworzonego projektu o nazwie JavaDB
. O tym jak to zrobić pisałem tutaj.
Tworzenie bazy danych
Teraz umieść w klasie JavaDB
kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package javadb; import java.sql.Connection; import java.sql.DriverManager; public class JavaDB { public static void main(String[] args) { // Nazwa bazy String baza = "organizmy"; // Wywołanie metody polacz, która zwraca obiekt typu Connection Connection polaczenie = polacz(baza); } /** * Metoda odpowiedzialna za połączenie z bazą * jeśli bazy nie ma to zostaje utworzona */ public static Connection polacz(String baza) { Connection polaczenie = null; try { // Wskazanie jaki rodzaj bazy danych będzie wykorzystany, tu sqlite Class.forName("org.sqlite.JDBC"); // Połączenie, wskazujemy rodzaj bazy i jej nazwę polaczenie = DriverManager.getConnection("jdbc:sqlite:"+baza+".db"); System.out.println("Połączyłem się z bazą "+baza); } catch (Exception e) { System.err.println("Błąd w połączeniu z bazą: \n" + e.getMessage()); return null; } return polaczenie; } } |
Utworzona została metoda metoda polacz()
, która jak sama nazwa wskazuje jest odpowiedzialna za połączenie z bazą danych o wskazanej nazwie. Jeśli tej bazy nie ma, to zostaje utworzona. Uruchom program i jeśli uzyskasz w terminalu komunikat:
Połączyłem się z bazą organizmy
to sprawdź zawartość katalogu z projektem. Powinien tam się pojawić plik organizmy.db
na razie jest on pusty.
Tworzenie tabeli
Teraz czas na utworzenie tabeli. Nie chcę tu omawiać szerzej samej zasady działania SQLite, napiszę tyle ile jest niezbędne do zrozumienia prezentowanego kodu. SQLite umożliwia tworzenie baz danych typu SQL. Przechowują one dane w tabelach, które składają się z rekordów a każdy z nich z pól o określonym typie (co do pewnego stopnia przypomina przechowywanie danych w obiektach w Javie). Stworzymy prostą tabelę przechowującą dane dotyczące organizmów, odpowiadającą tej, którą zapisywaliśmy w pliku csv
.
W tamtym programie tworzyliśmy obiekty typu Takson
, który przechowywał informacje o numerze taksonu, jego nazwie gatunkowej i rodzajowej, liczbie 2n oraz x.
Dodaj do projektu klasę Takson
o odrobinę zmodyfikowanej treści:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
package javadb; public class Takson { private int id; private String rodzaj; private String gatunek; private int n2; private int x; private String uwagi; public Takson(int id, String rodzaj, String gatunek, int n2, int x) { this.id = id; this.rodzaj = rodzaj; this.gatunek = gatunek; this.n2 = n2; this.x = x; } public int getId() { return id; } public void setId(int nr) { this.id = id; } public String getRodzaj() { return rodzaj; } public void setRodzaj(String rodzaj) { this.rodzaj = rodzaj; } public String getGatunek() { return gatunek; } public void setGatunek(String gatunek) { this.gatunek = gatunek; } public int getN2() { return n2; } public void setN2(int n2) { this.n2 = n2; } public int getX() { return x; } public void setX(int x) { this.x = x; } public String getUwagi() { return uwagi; } public void setUwagi(String uwagi) { this.uwagi = uwagi; } } |
Dodaj teraz metodę do pliku JavaDB
, która będzie odpowiadała za tworzenie tabeli (trzeba też uzupełnić import) java.sql.Statement;
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public static void stworzTabele(Connection polaczenie, String tabela) { // Obiekt odpowiadający za wykonanie instrukcji Statement stat = null; try { stat = polaczenie.createStatement(); // polecenie SQL tworzące tablicę String tabelaSQL = "CREATE TABLE " + tabela + " (ID INT PRIMARY KEY NOT NULL," + " RODZAJ CHAR(50) NOT NULL, " + " GATUNEK CHAR(50) NOT NULL, " + " N2 INT, " + " X INT, " + " UWAGI TEXT)"; // wywołanie polecenia stat.executeUpdate(tabelaSQL); // zamykanie wywołania i połączenia stat.close(); polaczenie.close(); } catch (SQLException e) { System.out.println("Nie mogę stworzyć tabeli" + e.getMessage()); } } |
Do metody main()
trzeba rzecz jasna dodać odpowiednie wywołanie metody stworzTabele()
:
stworzTabele(polaczenie, baza);
Po uruchomieniu programu tabela zostanie utworzona w bazie danych, na co wskazuje zwiększona wielkość pliku organizmy.db
, która wcześniej zajmowała 0 KB a teraz 3 KB. Jeśli chcesz sprawdzić zawartość bazy, możesz użyć na przykład któregoś z programów wymienionych na stronie Wikipedii poświęconej SQLite. Na przykład po otwarciu pliku bazy w programie SQLite Database Browser otrzymamy taki obrazek:
Kluczowym elementem w powyższym kodzie jest komenda w języku SQL
odpowiadająca za utworzenie tabeli. Nie wnikając w szczegóły widać, że znajduje się tam polecenie utworzenia tabeli po którym następuje jej nazwa a później w parze nawiasów opisy poszczególnych pól zawierające ich nazwę, rodzaj przechowywanych danych i ewentualnie dodatkowe instrukcje (np. że nie może mieć zawartości NULL
) . Pierwsze pole ID
jest kluczowe nie tylko z nazwy, pozwala bowiem na jednoznaczną identyfikację rekordu. Jego zawartość musi być unikalna. Pola RODZAJ
i GATUNEK
przechowują ciąg do 50 znaków, N2
i X
liczby całkowite a UWAGI
dłuższy tekst.
Dodawanie danych
Skoro mamy tablicę, można już zakomentować wywołanie metody stworzTabele
a następnie dodajmy jakieś dane. Najpierw stwórzmy odpowiednią metodę, która będzie przyjmowała jako argument obiekt typu Takson
i umieszczała w tabeli dane przez niego przechowywane. Oczywiście pośrednictwo obiektu nie jest konieczne, można bezpośrednio umieszczać dane, w formie łańcucha znaków w poleceniu. Ponieważ w poniższym przykładzie końcowa forma komendy może nie być łatwa do odczytania z kodu, zostanie, w celach dydaktycznych, wyświetlona w terminalu, może ona wyglądać tak:
INSERT INTO organizmy (ID, RODZAJ, GATUNEK, N2, X, UWAGI) VALUES (3,'Amaranthus','bouchonii',52,13,'Sprawdzić dane!' );
A teraz kod wspomnianej metody:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public static void dodajDane(Takson takson, String baza) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); String dodajSQL = "INSERT INTO " + baza + " (ID, RODZAJ, GATUNEK, N2, X, UWAGI) " + "VALUES (" + takson.getId() + "," + "'" + takson.getRodzaj() + "'," + "'" + takson.getGatunek() + "'," + takson.getN2() + "," + takson.getX() + "," + "'" + takson.getUwagi() + "'" + " );"; stat.executeUpdate(dodajSQL); stat.close(); polaczenie.close(); // Komunikat i wydrukowanie końcowej formy polecenia SQL System.out.println("Polecenie: \n" + dodajSQL + "\n wykonane."); } catch (Exception e) { System.out.println("Nie mogę dodać danych " + e.getMessage()); } } |
Dodajmy też kod wywołujący tą metodę do metody main()
1 2 3 4 5 6 7 |
Takson gatunek1 = new Takson(1, "Acer", "negundo", 26, 13); dodajDane(gatunek1, baza); Takson gatunek2 = new Takson(2, "Acer", "saccharinum", 52, 13); dodajDane(gatunek2, baza); Takson gatunek3 = new Takson(3, "Amaranthus", "bouchonii", 52, 13); gatunek3.setUwagi("Sprawdzić dane!"); dodajDane(gatunek3, baza); |
Możemy teraz sprawdzić, czy się udało:
Jeśli teraz ponownie spróbujesz uruchomić program, otrzymasz komunikat:
Nie mogę dodać danych UNIQUE constraint failed: organizmy.ID
Spowodowany jest tym, że próbujemy dodać rekord o takim samym kluczu co już istniejący a element, który jest kluczem musi być unikalny. Możesz usunąć plik z bazą danych, wtedy program wykona wszystko od nowa, albo usunąć/zakomentować polecenia dodawania rekordów.
Wyszukiwanie danych
Kolejnym podstawowym poleceniem używanym w bazach danych jest wyszukiwanie. Dodaj kolejną metodę do klasy JavaDB
, nie zapominając o aktualizacji listy importowanych klas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public static void szukaj(String baza, String pole, String wartosc) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); // Polecenie wyszukania String szukajSQL = "SELECT * FROM " + baza + " WHERE " + pole + "=='" + wartosc + "';"; ResultSet wynik = stat.executeQuery(szukajSQL); System.out.println("Wynik polecenia:\n" + szukajSQL); while (wynik.next()) { int id = wynik.getInt("id"); System.out.println("ID: " + id); System.out.println("Rodzaj: " + wynik.getString("rodzaj")); System.out.println("Gatunek: " + wynik.getString("gatunek")); System.out.println("2n: " + wynik.getString("N2")); System.out.println("x: " + wynik.getString("X")); System.out.println("Uwagi: " + wynik.getString("UWAGI")); System.out.println(" ---------------------- "); } wynik.close(); stat.close(); polaczenie.close(); } catch (Exception e) { System.out.println("Nie mogę wyszukać danych " + e.getMessage()); } } |
Metodę wywołamy w ten sposób:
szukaj(baza, "RODZAJ", "Acer");
Tu mamy dość prosty przykład wyszukiwania, gdy porównujemy zawartość jednego pola z wyszukiwanym tekstem na zasadzie równości, ale oczywiście są możliwe znacznie bardziej złożone zapytania, których jednak nie będziemy tu omawiać.
Wynik powinien wyglądać tak:
Wynik polecenia:
SELECT * FROM organizmy WHERE RODZAJ='Acer';
ID: 1
Rodzaj: Acer
Gatunek: negundo
2n: 26
x: 13
Uwagi: null
----------------------
ID: 2
Rodzaj: Acer
Gatunek: saccharinum
2n: 52
x: 13
Uwagi: null
----------------------
Modyfikacje danych
Następna metoda służy zmianie już istniejących rekordów.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static void zmien(String baza, String poleSzukane, String wartoscSzukana, String poleZmieniane, String nowaWartosc) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); // Polecenie zmiany String zmienSQL = "UPDATE " + baza + " SET " + poleZmieniane + " = '" + nowaWartosc + "' WHERE " + poleSzukane + "='" + wartoscSzukana + "';"; System.out.println("Polecenie zmiany:\n" + zmienSQL); // Uwaga: wywołujemy metodę executeUpdate stat.executeUpdate(zmienSQL); stat.close(); polaczenie.close(); } catch (Exception e) { System.out.println("Nie mogę poprawić danych " + e.getMessage()); } } |
Użyjemy jej do poprawienia zawartości pola UWAGI
w rekordzie w którym pole ID
jest równe 3
. Trzeba w tym celu dopisać do metody main()
:
1 2 3 |
szukaj(baza, "ID", "3"); zmien(baza, "ID", "3", "UWAGI", "Dane sprawdzone"); szukaj(baza, "ID", "3"); |
Po uruchomieniu programu otrzymamy:
Połączyłem się z bazą organizmy
Polecenie:
SELECT * FROM organizmy WHERE ID=='3';
ID: 3
Rodzaj: Amaranthus
Gatunek: bouchonii
2n: 52
x: 13
Uwagi: Sprawdzić dane!
----------------------
Polecenie zmiany:
UPDATE organizmy SET UWAGI = 'Dane sprawdzone' WHERE ID='3';
Polecenie:
SELECT * FROM organizmy WHERE ID=='3';
ID: 3
Rodzaj: Amaranthus
Gatunek: bouchonii
2n: 52
x: 13
Uwagi: Dane sprawdzone
----------------------
Jak widać udało się.
Usuwanie danych
Ostatni z podstawowych działań na bazie danych jest usuwanie rekordów.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static void usun(String baza, String pole, String wartosc) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); // Polecenie usunięcia String usunSQL = "DELETE FROM "+ baza + " WHERE " + pole + "='" + wartosc + "';"; System.out.println("Polecenie:\n" + usunSQL); stat.executeUpdate(usunSQL); wynik.close(); stat.close(); polaczenie.close(); } catch (Exception e) { System.out.println("Nie mogę usunąć danych " + e.getMessage()); } } |
Wywołamy ją tak:
1 2 3 |
usun(baza, "GATUNEK", "saccharinum"); szukaj(baza, "RODZAJ", "Acer"); |
Teraz w bazie znajdzie się tylko jeden gatunek z rodzaju Acer:
Wynik polecenia:
SELECT * FROM organizmy WHERE RODZAJ='Acer';
ID: 1
Rodzaj: Acer
Gatunek: negundo
2n: 26
x: 13
Uwagi: null
----------------------
Na koniec
Zauważ, że budowa czterech powyższych metod jest w sumie bardzo podobna, to co je różni to głównie treść polecenia SQL
, które zostaje wywołane.
Kompletny kod pliku JavaDB.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
package javadb; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JavaDB { public static void main(String[] args) { // Nazwa bazy String baza = "organizmy"; // Wywołanie metody polacz, która zwraca oboekt typu Connection Connection polaczenie = polacz(baza); stworzTabele(polaczenie, baza); Takson gatunek1 = new Takson(1, "Acer", "negundo", 26, 13); dodajDane(gatunek1, baza); Takson gatunek2 = new Takson(2, "Acer", "saccharinum", 52, 13); dodajDane(gatunek2, baza); Takson gatunek3 = new Takson(3, "Amaranthus", "bouchonii", 52, 13); gatunek3.setUwagi("Sprawdzić dane!"); dodajDane(gatunek3, baza); szukaj(baza, "RODZAJ", "Acer"); szukaj(baza, "ID", "3"); zmien(baza, "ID", "3", "UWAGI", "Dane sprawdzone"); szukaj(baza, "ID", "3"); usun(baza, "GATUNEK", "saccharinum"); szukaj(baza, "RODZAJ", "Acer"); } /** * Metoda odpowiedzialna za połączenie z bazą jeśli bazy nie ma to zostaje * utworzona */ public static Connection polacz(String baza) { Connection polaczenie = null; try { // Wskazanie jaki rodzaj bazy danych bęzie wykorzystany, tu sqlite Class.forName("org.sqlite.JDBC"); // Połączenie, wskazujemy rodzaj bazy i jej nazwę polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); System.out.println("Połączyłem się z bazą " + baza); } catch (Exception e) { System.err.println("Błąd w połączeniu z bazą: \n" + e.getMessage()); return null; } return polaczenie; } /** * Metoda odpowiedzialna za tworzenie tabeli */ public static void stworzTabele(Connection polaczenie, String tabela) { // Obiekt odpowiadający za wykonanie instrukcji Statement stat = null; try { stat = polaczenie.createStatement(); // polecenie SQL tworzące tablicę String tabelaSQL = "CREATE TABLE " + tabela + " (ID INT PRIMARY KEY NOT NULL," + " RODZAJ CHAR(50) NOT NULL, " + " GATUNEK CHAR(50) NOT NULL, " + " N2 INT, " + " X INT, " + " UWAGI TEXT)"; // wywołanie polecenia stat.executeUpdate(tabelaSQL); // zamykanie wywołania i połączenia stat.close(); polaczenie.close(); } catch (SQLException e) { System.out.println("Nie mogę stworzyć tabeli " + e.getMessage()); } } /** * Metoda odpowiedzialna za dodawanie danych do tabeli */ public static void dodajDane(Takson takson, String baza) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); String dodajSQL = "INSERT INTO " + baza + " (ID, RODZAJ, GATUNEK, N2, X, UWAGI) " + "VALUES (" + takson.getId() + "," + "'" + takson.getRodzaj() + "'," + "'" + takson.getGatunek() + "'," + takson.getN2() + "," + takson.getX() + "," + "'" + takson.getUwagi() + "'" + " );"; stat.executeUpdate(dodajSQL); stat.close(); polaczenie.close(); // Komunikat i wydrukowanie końcowej formy polecenia SQL System.out.println("Polecenie: \n" + dodajSQL + "\n wykonane."); } catch (Exception e) { System.out.println("Nie mogę dodać danych " + e.getMessage()); } } /** * Metoda odpowiedzialna za wyszukanie danych i wydrukowanie rezultatu */ public static void szukaj(String baza, String pole, String wartosc) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); // Polecenie wyszukania String szukajSQL = "SELECT * FROM " + baza + " WHERE " + pole + "='" + wartosc + "';"; ResultSet wynik = stat.executeQuery(szukajSQL); System.out.println("Polecenie:\n" + szukajSQL); while (wynik.next()) { int id = wynik.getInt("id"); System.out.println("ID: " + id); System.out.println("Rodzaj: " + wynik.getString("rodzaj")); System.out.println("Gatunek: " + wynik.getString("gatunek")); System.out.println("2n: " + wynik.getString("N2")); System.out.println("x: " + wynik.getString("X")); System.out.println("Uwagi: " + wynik.getString("UWAGI")); System.out.println(" ---------------------- "); } wynik.close(); stat.close(); polaczenie.close(); } catch (Exception e) { System.out.println("Nie mogę wyszukać danych " + e.getMessage()); } } /** * Metoda odpowiedzialna za zmianę danych */ public static void zmien(String baza, String poleSzukane, String wartoscSzukana, String poleZmieniane, String nowaWartosc) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); // Polecenie zmiany String zmienSQL = "UPDATE " + baza + " SET " + poleZmieniane + " = '" + nowaWartosc + "' WHERE " + poleSzukane + "='" + wartoscSzukana + "';"; System.out.println("Polecenie zmiany:\n" + zmienSQL); // Uwaga: wywołujemy metodę executeUpdate stat.executeUpdate(zmienSQL); stat.close(); polaczenie.close(); } catch (Exception e) { System.out.println("Nie mogę poprawić danych " + e.getMessage()); } } /** * Metoda odpowiedzialna za usunięcie danych */ public static void usun(String baza, String pole, String wartosc) { Connection polaczenie = null; Statement stat = null; try { Class.forName("org.sqlite.JDBC"); polaczenie = DriverManager.getConnection("jdbc:sqlite:" + baza + ".db"); stat = polaczenie.createStatement(); // Polecenie usunięcia String usunSQL = "DELETE FROM "+ baza + " WHERE " + pole + "='" + wartosc + "';"; System.out.println("Polecenie:\n" + usunSQL); stat.executeUpdate(usunSQL); stat.close(); polaczenie.close(); } catch (Exception e) { System.out.println("Nie mogę usunąć danych " + e.getMessage()); } } } |
Jeśli zamierzasz pracować z bazą SQLite, warto poszerzyć jej znajomość we własnym zakresie. W Internecie dostępnych wiele tutoriali, na przykład pod adresem https://www.guru99.com/sqlite-tutorial.html.
w metodzie usun() nie potrzebny jest fragment wynik.close() – albo ja jestem ślepy, albo nigdzie w kodzie metody nie wywołujesz zmiennej wynik.
Hmmm…. najwyraźniej tak. Dziękuję za zwrócenie uwagi.