Java zaawansowane #7: połączenie z bazą danych przez JDBC

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.

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ć.

Katalog do którego ściągnąłem sterownik MySQL

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 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!

*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

Może Ci się również spodoba

1 Odpowiedź

  1. Kuba pisze:

    Fajny przykład. Jest to może gdzieś na githubie?

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *