Aby zrozumieć dzisiejszą lekcję, musisz znać podstawy baz danych. Jeśli nie masz pojęcia czym są i do czego służą, proponuję wpierw poznać lub powtórzyć wiedzę o tym jak dokonywać prostych zapytań SQL do relacyjnych baz danych. Następnie zapoznam Cię z biblioteką JDBC.
Bazy danych – podstawowe informacje
Baza danych jest samodzielnym mechanizmem przechowywania danych. Innym sposobem do trzymania informacji jest korzystanie z obiektów i struktur danych w Javie. Nie zawsze jednak jest to optymalne. Bazy danych posiadają szereg optymalizacji, które pozwalają szybko wyszukiwać w nich informacji. Dodatkowym plusem jest to, że silnik baz danych działa niezależnie od wirtualnej maszyny Javy (z reguły). Tak czy siak, szybciej czy później będziesz mieć do czynienia z relacyjnymi bazami*.
Ponieważ, jak już wspomniałem, bazy danych są niezależnym narzędziem, z reguły posiadają wbudowany mechanizm dostępu do nich. Czasami jest to zwykła konsola (np. PostgreSQL), czasami to wyspecjalizowane narzędzie (SQL Developer lub MySQL Workbench). W obecnych czasach, coraz rzadziej bazy danych są używane jako samodzielna usługa. Z reguły są po prostu „twardym dyskiem” dla języków programowania, które umożliwiają wielowątkowe ich przetwarzanie oraz tworzenie zaawansowanego GUI-a (Graphical User Interface), dzięki któremu łatwo później wykonywać skomplikowane zapytania.
W tym wpisie skupię się na tym, jak możesz łatwo napisać program, który będzie komunikował się z dowolną bazą danych.
Połączenie z bazą z poziomu Javy
Wpierw napiszę enum, który będzie dostarczał mi informacji o położeniu bazy danych. Podstawowe informacje, które musisz tam zawrzeć to prawidłowy sterownik (dla MySql będzie to com.mysql.cj.jdbc.Driver), połączenie z Twoją instancją bazy (u mnie nazywa się ona myjdbc), oraz użytkownik z hasłem, który ma dostęp do serwera bazy danych.
public enum MysqlProperties { DB_DRIVER("com.mysql.cj.jdbc.Driver"), DB_CONNECTION("jdbc:mysql://127.0.0.1:3306/myjdbc?serverTimezone=UTC"), DB_USER("root"), DB_PASSWORD("root"); private final String value; private MysqlProperties(String value) { this.value = value; } public String getValue() { return value; } }
Dołączanie sterownika JDBC
Następnie należy napisać klasę, która korzystając z enuma MysqlProperties, połączy się z bazą. Zawiera ona metodę implementującą interfejs Connection. Wpierw należy sprawdzić, czy sterownik istnieje w Twoim projekcie. Jeśli go nie masz, to musisz ściągnąć odpowiedni plik jar** i dołączyć go w Twoim IDE do projektu, na którym pracujesz. Ja stworzyłem specjalny katalog lib w folderze projektowym i tam go trzymam. Powinieneś także pamiętać, aby był on kompatybilny z wersją serwera baz danych, do którego się chcesz połączyć.
Aby zadeklarować w Eclipse dodatkowe biblioteki, należy kliknąć: prawy przycisk na Twoim projekcie -> properties -> w sekcji ’Java build path’ kliknij zakładkę libraries -> Add JARs.. -> wybierasz sterownik JDBC w odpowiednim katalogu.
Korzystanie z pakietu java.sql.*
Kolejny try catch to właściwa próba połączenia się z bazą. W przypadku niemożności wykonania połączenia zostanie wyrzucony wyjątek SQLException.
public final class DatabaseConnector { private static Logger LOGGER = Logger.getLogger(DatabaseConnector.class.getName()); private DatabaseConnector() { } public static Connection getDBConnection() { Connection dbConnection = null; try { Class.forName(DB_DRIVER.getValue()); } catch (ClassNotFoundException e) { LOGGER.log(Level.WARNING, "Driver not found. " + e.getMessage(), e); } try { dbConnection = DriverManager.getConnection(DB_CONNECTION.getValue(), DB_USER.getValue(), DB_PASSWORD.getValue()); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Can't make connection with database. " + e.getMessage(), e); } return dbConnection; } }
Najlepszym sposobem trzymania zapytań SQL jest zwykły plik tekstowy. W moim przypadku trzymam go w katalogu src/main/resources/jdbc. Stworzę tu dwa pliki: jeden stworzy tabelę w mojej bazie i wypełni ją danymi, drugi za to będzie wykonywał jakieś zapytania typu select.
Przykład pliku create.sql:
DROP TABLE dbuser; CREATE TABLE dbuser (id int, firstname varchar(100), lastname varchar(100), address varchar(200)); INSERT INTO dbuser VALUES(1, "ala", "ma", "kota"); INSERT INTO dbuser VALUES(2, "ala", "ma", "kota"); INSERT INTO dbuser VALUES(3, "ala", "ma", "kota"); INSERT INTO dbuser VALUES(4, "ala", "ma", "kota");
Plik z zapytaniem select.sql:
SELECT * FROM dbuser;
Tego typu pliki umiesz już wczytać, więc nie będę się na tym skupiał. W mojej implementacji metoda wczytująca zapytania zapisuje je na liście.
Klasa dostępowa do danych
Teraz należy napisać klasę, która dokonywać faktycznych zapytań do bazy.
public final class DataAccess { private static Logger LOGGER = Logger.getLogger(DataAccess.class.getName()); private Connection dbConnection; public DataAccess(Connection dbConnection) { this.dbConnection = dbConnection; } public String read(List<String> selectQueries, String columnName) throws SQLException { Statement statement = null; String resultMessage = "NO SELECT RESULT"; try { statement = openConnection(); for (String query : selectQueries) { LOGGER.info(query); ResultSet rs = statement.executeQuery(query); LOGGER.info("Data was found! " + rs.getString(columnName)); rs.close(); } } catch (SQLException e) { LOGGER.log(Level.WARNING, "Driver not found. " + e.getMessage(), e); } finally { if (statement != null) { statement.close(); } } return resultMessage; } public void closeConnection() throws SQLException { if (dbConnection != null) { dbConnection.close(); } } private Statement openConnection() throws SQLException { Statement statement; if (dbConnection == null) { dbConnection = DatabaseConnector.getDBConnection(); } statement = dbConnection.createStatement(); return statement; } }
Klasa w konstruktorze przesyła połączenie uzyskane jako wynik metody getDBConnection. Metody openConnection i closeConnection służą do otworzenia i zamknięcia połączenia z bazą danych. Z reguły, gdy otworzysz raz połączenie nie ma sensu go zamykać do momentu, kiedy to będziesz pewny, że nie wykonasz już więcej zapytań do bazy. Metoda read pozwala na odczytanie zapytań z listy (w tym przypadku muszą to być zapytania typu select). Następnie tworzony jest obiekt na podstawie interfejsu Statement. Umożliwia on skorzystanie z dwóch metod: jedna stosowana jest od odczytu danych (executeQuery) lub ich manipulacji (executeUpdate).
Ponieważ naszym celem jest odczyt danych, to executeQuery musi je zwrócić w jakiś sposób. Zapisane one będą w obiekcie ResultSet, skąd możesz odwołać się do konkretnej nazwy kolumny, aby sprawdzić jej wartość. ResultSet otwiera niejawnie tzw. kursor***, nie można go trzymać otwartego w nieskończoność, dlatego po wykonaniu wszystkich zapytań, należy go zamknąć za pomocą close. Dodatkowo w sekcji finally zamykany jest też obiekt statement.
Otwarcie połączenia i wykonanie poleceń
Teraz musisz tylko skorzystać z powyższej implementacji w swojej metodzie main.
Connection dbConnection = DatabaseConnector.getDBConnection(); DataAccess dataAccess = new DataAccess(dbConnection); List<String> selectQueries = readyFrom(); // wczytanie z pliku zapytań do listy dataAccess.read(selectQueries, columnName); // columnName - kolumna której wartość chcesz odczytać dataAccess.closeConnection();
Gratulacje! Znasz już podstawowy sposób w jaki można komunikować się z bazą danych za pomocą JDBC!
*Istnieją jeszcze inne rodzaje baz danych, np. nierelacyjne (tzw. NoSQL) .
** W moim przypadku skorzystałem z mysql-connector-java-6.0.5.jar, dostępnego pod tym linkiem: https://repo1.maven.org/maven2/mysql/mysql-connector-java/6.0.5/mysql-connector-java-6.0.5.jar
***Wyjaśnienie czym kursor na przykładzie bazy MS SQL: https://docs.microsoft.com/en-us/sql/ado/guide/data/what-is-a-cursor?view=sql-server-ver15