Вступление
PostgreSQL (который носит прозвище Postgres) известен своей объектно-реляционной природой. В отличие от этого, другие системы баз данных обычно являются реляционными . В силу своей природы он отлично сочетается с Java, которая в значительной степени ориентирована на объекты.
Доступ к базе данных Postgres с использованием Java требует , чтобы вы полагались на JDBC API , как вы, возможно, подозревали. Из-за этого процедуры Postgres и другие системы баз данных похожи. Тем не менее, это не скрывает того факта, что Postgres предлагает дополнительные возможности, такие как расширенная поддержка пользовательских типов данных и больших наборов данных.
Что такое PostgreSQL?
PostgreSQL является производной от ныне несуществующего проекта POSTGRES . POSTGRES нацелен на достижение не только объектной ориентации, но и расширяемости. Тем не менее, Калифорнийский университет прекратил разработку POSTGRES в 1994 году.
Ранние выпуски Postgres были нацелены на компьютеры UNIX. Тем не менее, с годами база данных стала переносимой. Таким образом, вы можете найти его в системах macOS, Linux и Windows.
Его открытый исходный код и бесплатное лицензирование также способствовали его широкому внедрению. Разработчикам это нравится отчасти потому, что они могут покопаться в источниках, чтобы узнать, как именно это работает.
Демонстрационное приложение
Руководство по Postgres является неполным без сопутствующей реализации CRUD. Мы напишем простое Java-приложение, которое может создавать, считывать, обновлять и удалять информацию о клиентах из базы данных Postgres.
Конечно, мы начнем с определения сущностей, а затем используем их для создания схемы базы данных, чтобы убедиться, что таблицы сопоставлены правильно.
И, как того требует надлежащий API, уровень бизнес – логики не должен иметь представления о том, что происходит на уровне базы данных-практика, известная как многоуровневая архитектура . Таким образом, мы выберем шаблон Объект доступа к данным (DAO) для удовлетворения этой потребности.
Зависимость от Maven
Мы начнем с maven-архетип-быстрый запуск
для простого скелетного проекта Maven через ваш терминал:
$ mvn archetype:generate -DgroupId=com.stackabuse.postgresql -DartifactId=java-postgresql-sample -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
После выполнения команды вы должны получить структуру, подобную этой:
java-postgresql-sample ├── src | ├── main | ├── java | ├── com | ├── stackabuse | ├── postgresql └── test
Затем, в вашем pom.xml
файл, добавьте зависимость Postgres:
org.postgresql postgresql {version}
Модель предметной области
Давайте создадим каталог с именем api
в нашем каталоге src
, в котором мы определим модель/сущность – Клиент
:
public class Customer { private Integer id; private String firstName; private String lastName; private String email; // Constructor, getters and setters... @Override public String toString() { return "Customer[" + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + ']'; } }
Эта сущность будет отображена в нашей базе данных Postgres с соответствующими полями немного позже.
Функциональность CRUD
Поскольку мы работаем в соответствии с шаблоном DAO, давайте начнем реализовывать нашу функциональность CRUD через интерфейс Dao
в каталоге spi
, в котором будут размещены все наши интерфейсы и классы обслуживания:
public interface Dao{ Optional get(int id); Collection getAll(); Optional save(T t); void update(T t); void delete(T t); }
Обратите внимание на два обобщения уровня класса : T
и I
. T
представляет фактический объект класса для передачи в базу данных и из нее, тогда как I
является классом первичного ключа сущности.
Теперь у нас есть скелет CRUD и объект домена на месте. Когда эти два дела будут выполнены, мы действительно сможем приступить к созданию нашей базы данных.
Создание базы данных Postgresql
Следуйте руководству по установке PostgreSQL для используемой вами платформы – установка довольно проста. С помощью Postgres мы будем использовать pgAdmin для управления установкой.
В вашей локальной
системе мы создадим базу данных с именем образец базы данных
и создадим таблицу для наших Клиентов
:
Для этого в pgAdmin мы запустим ввод в редакторе запросов:
CREATE TABLE public.customer ( customer_id integer NOT NULL GENERATED ALWAYS AS IDENTITY (START 1 INCREMENT 1 ), first_name character varying(45) NOT NULL, last_name character varying(45) NOT NULL, email character varying(50), CONSTRAINT customer_pkey PRIMARY KEY (customer_id) )
И, таким образом, мы создали таблицу для Клиента
s.
Подключение к базе данных
Прежде чем мы сможем выполнить какие-либо инструкции в базе данных из нашего кода, нам сначала нужно будет настроить подключение к базе данных. Мы сделаем это через Jdcconnection
класс:
public class JdbcConnection { private static final Logger LOGGER = Logger.getLogger(JdbcConnection.class.getName()); private static Optionalconnection = Optional.empty(); public static Optional getConnection() { if (connection.isEmpty()) { String url = "jdbc:postgresql://localhost:5432/sampledb"; String user = "postgres"; String password = "postgres"; try { connection = Optional.ofNullable( DriverManager.getConnection(url, user, password)); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, null, ex); } } return connection; } }
Основная задача вышеприведенного класса-восстановить соединение с базой данных. Поскольку он не всегда может возвращать ненулевой объект Connection
, соединение завернуто в Необязательный
.
Другая примечательная вещь заключается в том, что соединение является статической переменной . Следовательно, класс возвращает первый ненулевой экземпляр соединения, полученный при первом запуске.
Добавление Сущностей
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
Поскольку теперь мы действительно можем подключиться к базе данных, давайте продолжим и попробуем создать объект в базе данных. Для этого мы определим класс PostgreSQL Dao
, который реализует вышеупомянутый интерфейс Dao
:
public class PostgreSqlDao implements Dao{ private static final Logger LOGGER = Logger.getLogger(PostgreSqlDao.class.getName()); private final Optional connection; public PostgreSqlDao() { this.connection = JdbcConnection.getConnection(); } @Override public Optional save(Customer customer) { String message = "The customer to be added should not be null"; Customer nonNullCustomer = Objects.requireNonNull(customer, message); String sql = "INSERT INTO " + "customer(first_name, last_name, email) " + "VALUES(?, ?, ?)"; return connection.flatMap(conn -> { Optional generatedId = Optional.empty(); try (PreparedStatement statement = conn.prepareStatement( sql, Statement.RETURN_GENERATED_KEYS)) { statement.setString(1, nonNullCustomer.getFirstName()); statement.setString(2, nonNullCustomer.getLastName()); statement.setString(3, nonNullCustomer.getEmail()); int numberOfInsertedRows = statement.executeUpdate(); // Retrieve the auto-generated id if (numberOfInsertedRows > 0) { try (ResultSet resultSet = statement.getGeneratedKeys()) { if (resultSet.next()) { generatedId = Optional.of(resultSet.getInt(1)); } } } LOGGER.log( Level.INFO, "{0} created successfully? {1}", new Object[]{nonNullCustomer, (numberOfInsertedRows > 0)}); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, null, ex); } return generatedId; }); } // Other methods of the interface which currently aren't implemented yet }
После создания объекта Customer
вы можете передать его в сохранить
метод PostgreSqlDao
, чтобы добавить его в базу данных.
Метод save
использует строку SQL для работы:
INSERT INTO customer(first_name, last_name, email) VALUES(?, ?, ?)
Используя подключение к базе данных, DAO затем подготавливает инструкцию:
PreparedStatement statement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
Интерес представляет то, что оператор содержит флаг Оператор.RETURN_GENERATED_KEYS
. Это гарантирует, что база данных также сообщит о первичном ключе, созданном ею для новой строки.
Стоит также отметить, что метод save
использует средство отображения Java. Он преобразует соединение с базой данных в тип возвращаемого значения, требуемый методом. И более того, он использует функцию flatMap
, чтобы гарантировать, что возвращаемое значение не имеет необязательного
переноса.
Остальные методы CRUD PostgreSqlDao
должны следовать той же предпосылке. Они должны сопоставить соединение с возвратом, где это необходимо, и сначала проверить, существует ли соединение, прежде чем работать с ним в противном случае.
Считывание Сущностей
В нашей реализации мы решили использовать метод, который возвращает одного Клиента
на основе их идентификатора
, и метод, который возвращает всех постоянных клиентов из базы данных.
Давайте начнем с простого .get()
метода, который возвращает одного Клиента
с соответствующим идентификатором
:
public Optionalget(int id) { return connection.flatMap(conn -> { Optional customer = Optional.empty(); String sql = "SELECT * FROM customer WHERE customer_id = " + id; try (Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { if (resultSet.next()) { String firstName = resultSet.getString("first_name"); String lastName = resultSet.getString("last_name"); String email = resultSet.getString("email"); customer = Optional.of( new Customer(id, firstName, lastName, email)); LOGGER.log(Level.INFO, "Found {0} in database", customer.get()); } } catch (SQLException ex) { LOGGER.log(Level.SEVERE, null, ex); } return customer; }); }
Код довольно прост. Мы выполняем запрос через наш Оператор
объект и упаковываем результаты в Набор результатов
. Затем мы извлекаем информацию из набора результатов
и упаковываем ее в конструктор для Клиента
, который возвращается.
Теперь давайте реализуем метод .GetAll()
:
public CollectiongetAll() { Collection customers = new ArrayList<>(); String sql = "SELECT * FROM customer"; connection.ifPresent(conn -> { try (Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { while (resultSet.next()) { int id = resultSet.getInt("customer_id"); String firstName = resultSet.getString("first_name"); String lastName = resultSet.getString("last_name"); String email = resultSet.getString("email"); Customer customer = new Customer(id, firstName, lastName, email); customers.add(customer); LOGGER.log(Level.INFO, "Found {0} in database", customer); } } catch (SQLException ex) { LOGGER.log(Level.SEVERE, null, ex); } }); return customers; }
Опять же, довольно просто – мы выполняем соответствующий SQL-запрос, извлекаем информацию, создаем объекты Customer
и упаковываем их в ArrayList
.
Обновление Сущностей
Далее, если мы когда-либо захотим обновить объект после его создания, нам понадобится метод .update()
:
public void update(Customer customer) { String message = "The customer to be updated should not be null"; Customer nonNullCustomer = Objects.requireNonNull(customer, message); String sql = "UPDATE customer " + "SET " + "first_name = ?, " + "last_name = ?, " + "email = ? " + "WHERE " + "customer_id = ?"; connection.ifPresent(conn -> { try (PreparedStatement statement = conn.prepareStatement(sql)) { statement.setString(1, nonNullCustomer.getFirstName()); statement.setString(2, nonNullCustomer.getLastName()); statement.setString(3, nonNullCustomer.getEmail()); statement.setInt(4, nonNullCustomer.getId()); int numberOfUpdatedRows = statement.executeUpdate(); LOGGER.log(Level.INFO, "Was the customer updated successfully? {0}", numberOfUpdatedRows > 0); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, null, ex); } }); }
Опять же, мы подготовили инструкцию и выполнили запрос на обновление на основе полей и идентификатора
клиента , переданных методу обновления.
Удаление Сущностей
И, наконец, иногда мы можем пожелать удалить объект, и для этой цели используется метод .delete()
:
public void delete(Customer customer) { String message = "The customer to be deleted should not be null"; Customer nonNullCustomer = Objects.requireNonNull(customer, message); String sql = "DELETE FROM customer WHERE customer_id = ?"; connection.ifPresent(conn -> { try (PreparedStatement statement = conn.prepareStatement(sql)) { statement.setInt(1, nonNullCustomer.getId()); int numberOfDeletedRows = statement.executeUpdate(); LOGGER.log(Level.INFO, "Was the customer deleted successfully? {0}", numberOfDeletedRows > 0); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, null, ex); } }); }
Опять же, на основе Клиента
‘ s id
выполняется запрос на удаление для удаления сущности.
Запуск приложения
После завершения реализации DAO проекту теперь нужна точка входа. Лучшее место для этого было бы в main
статическом методе:
public class CustomerApplication { private static final Logger LOGGER = Logger.getLogger(CustomerApplication.class.getName()); private static final DaoCUSTOMER_DAO = new PostgreSqlDao(); public static void main(String[] args) { // Test whether an exception is thrown when // the database is queried for a non-existent customer. // But, if the customer does exist, the details will be printed // on the console try { Customer customer = getCustomer(1); } catch (NonExistentEntityException ex) { LOGGER.log(Level.WARNING, ex.getMessage()); } // Test whether a customer can be added to the database Customer firstCustomer = new Customer("Manuel", "Kelley", "[email protected]"); Customer secondCustomer = new Customer("Joshua", "Daulton", "[email protected]"); Customer thirdCustomer = new Customer("April", "Ellis", "[email protected]"); addCustomer(firstCustomer).ifPresent(firstCustomer::setId); addCustomer(secondCustomer).ifPresent(secondCustomer::setId); addCustomer(thirdCustomer).ifPresent(thirdCustomer::setId); // Test whether the new customer's details can be edited firstCustomer.setFirstName("Franklin"); firstCustomer.setLastName("Hudson"); firstCustomer.setEmail("[email protected]"); updateCustomer(firstCustomer); // Test whether all customers can be read from database getAllCustomers().forEach(System.out::println); // Test whether a customer can be deleted deleteCustomer(secondCustomer); } // Static helper methods referenced above public static Customer getCustomer(int id) throws NonExistentEntityException { Optional customer = CUSTOMER_DAO.get(id); return customer.orElseThrow(NonExistentCustomerException::new); } public static Collection getAllCustomers() { return CUSTOMER_DAO.getAll(); } public static void updateCustomer(Customer customer) { CUSTOMER_DAO.update(customer); } public static Optional addCustomer(Customer customer) { return CUSTOMER_DAO.save(customer); } public static void deleteCustomer(Customer customer) { CUSTOMER_DAO.delete(customer); } }
Поскольку методы CRUD из Данных PostgreSQL
являются общедоступными, мы завернем их, чтобы предотвратить доступ уровня базы данных к остальной части кода, когда это не требуется.
После этого необходимо создать два других пользовательских класса исключений, которые необходимо создать. Это Несуществующее исключение
:
public class NonExistentEntityException extends Throwable { private static final long serialVersionUID = -3760558819369784286L; public NonExistentEntityException(String message) { super(message); } }
И его наследник, Несуществующее Исключение Клиента
:
public class NonExistentCustomerException extends NonExistentEntityException { private static final long serialVersionUID = 8633588908169766368L; public NonExistentCustomerException() { super("Customer does not exist"); } }
Эти два класса обрабатывают исключения, которые DAO выдает, когда Клиент
не существует, чтобы сделать обработку исключений немного более удобной.
Вывод
Мы видели, как создать CRUD-приложение на основе Postgres. Шаги показывают, что на самом деле настройка серверной части Postgres-это тривиальное дело. Привязка модели домена Java к подключению к базе данных Postgres требует немного больше работы. Это связано с тем, что наилучшая практика требует разделения слоев и сокрытия информации .
Вы можете найти весь код проекта на GitHub .