Вступление
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 Optional connection = 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 Dao CUSTOMER_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 .