Перша частина Друга частина
Java додаток
3-tier організація
Повернемося до Java-додатку. Варіант попередньої частини був створений в HelloWorld -стилі, щоб проконтролювати коректність початкових дій. Ми реалізуємо триланкову (тришарову) архітектуру, яку в англомовній літературі часто називають 3tier/3layer . Її коротка суть така:- Усі сутності оформлені у вигляді моделей (Model). Це об'єкти, які містять:
- Набір атрибутів (приватні поля класу).
- Конструктор(и).
- Сетери та гетери для встановлення/читання атрибутів.
- Важливо, що іншого коду, крім перерахованого вище в них немає. Часто такі об'єкти називають POJO (Plain Old Java Object).
- Всю логіку роботи з моделями продає шар Service. Він формує бізнес-правила для моделей. Наприклад, обробка запитів від Java-програми. Серед аргументів запитів і результатів, що повертаються, часто виступають моделі (або їх колекції).
- Шар Repository — «посередник» між СУБД та Service, що працює безпосередньо з базою даних, та відповідає за взаємодію з нею.
Model
Усі наші сутності (акції, трейдери, курси та дії трейдерів) та їх табличні еквіваленти мають спільну рису – штучний первинний ключ. Тому оформимо базовий класBaseModel
. Усі моделі будуть успадковуватись від нього.
package sql.demo.model;
import java.util.Objects;
// Базовый класс модели, имеющий ключ id
public class BaseModel {
protected long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public BaseModel() {}
public BaseModel(long id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BaseModel baseModel = (BaseModel) o;
return id == baseModel.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Нижче наведено модель акції для прикладу. Інші лістинги моделей ви можете подивитися за посиланням на github-репозиторій наприкінці статті.
package sql.demo.model;
import java.math.BigDecimal;
// Модель акции
public class Share extends BaseModel{
// поля SQL таблицы и соответствующие им поля модели
// типы данных SQL
private String name; // Наименование
private BigDecimal startPrice; // Начальная цена
private int changeProbability; // Вероятность смены курса (в процентах)
private int delta; // Максимальное колебание (в процентах)стоимости акции в результате торгов
public Share() {
}
public Share(long id, String name, BigDecimal startPrice, int changeProbability, int delta) {
super(id);
this.name = name;
this.startPrice = startPrice;
this.changeProbability = changeProbability;
this.delta = delta;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
... оставшиеся сеттеры/геттеры
}
JDBC
У першій частині ми навчабося встановлювати з'єднання з БД та закривати його. Тепер ходімо далі. Етапи роботи з JDBC відображені на наступній схемі:Class.forName()
завантажує клас та реєструє його в DriverManager;DriverManager.getConnection()
повернеConnection
– з'єднання із зазначеною в аргументі методу базою даних та використанням відповідного JDBC драйвера (який був завантажений з використаннямClass.forName()
).createStatement()
поверне намStatement
– об'єкт, з урахуванням якого можна формувати запити до БД. Існують також:PreparedStatement
що полегшує створення параметризованих та пакетних запитів.
CallableStatement
для виклику власних СУБД SQL-функцій та процедур (їх називають збереженими).- Маючи «на руках»
statement
дозволитьexecute()
відправити запит у вигляді команди мови запитів SQL безпосередньо на виконання СУБД і поверне відповідь у виглядіResultSet
. Для зручності існують:executeQuery()
– для читання даних із СУБД.
executeUpdate()
– для модифікації даних у СУБД. - Безпосередньо відповідь сервера як
ResultSet
можна обробити, ітеруючи його черезfirst()
,last()
,next()
тощо. Окремі поля результати ми можемо отримати через гетери:getInteger()
,getString()
…
ResultSet
, Statement
й Connection
у економії ресурсів. Пам'ятайте, закриваючи об'єкт, що стоїть у послідовності на схемі, ви каскадно закриєте всі породжені в процесі роботи з ним об'єкти. Таким чином, закриття з'єднання (Connection) призведе до закриття всіх його Statement
та всіх ResultSet
, отриманих за їх допомогою.
Реалізація Repository
Після теоретичної JDBC-частини перейдемо до реалізації репозиторію. Архітектурно реалізуємо його так:- Найбільш загальні частини роботи з СУБД винесемо до загального предка –
BaseTable
; - Логічні операції, які ми виконуватимемо, оголосимо в інтерфейсі
TableOperation
;
BaseTable
та реалізовуватиме інтерфейс TableOperation
. Таким чином, нам необхідно прописати реалізацію методів, оголошених в інтерфейсі TableOperation
. При цьому ми можемо використовувати методи батьківського класу BaseTable
. На даний момент в інтерфейсі оголошено методи створення таблиць:
package sql.demo.repository;
import java.sql.SQLException;
// Операции с таблицями
public interface TableOperations {
void createTable() throws SQLException; // создание таблицы
void createForeignKeys() throws SQLException; // создание связей между таблицями
void createExtraConstraints() throws SQLException; // создание дополнительных правил для значений полей таблиц
}
У міру вивчення матеріалу список об'яв методів буде розширюватися ( read()
, update()
….). Нові можливості здійснюватимемо за два кроки:
- Додамо чергову можливість роботи з таблицею як нового методу інтерфейсу.
- Далі, в класах, що імплементують інтерфейс, опишемо програмну реалізацію в нових методах, породжених інтерфейсом.
Share
(акцій). Основна логіка знаходиться в командах створення таблиць із зазначенням типів даних SQL для полів та додавання обмежень:
package sql.demo.repository;
import java.sql.SQLException;
public class Shares extends BaseTable implements TableOperations{
public Shares() throws SQLException {
super("shares");
}
@Override
public void createTable() throws SQLException {
super.executeSqlStatement("CREATE TABLE shares(" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY," +
"name VARCHAR(255) NOT NULL," +
"startPrice DECIMAL(15,2) NOT NULL DEFAULT 10," +
"changeProbability INTEGER NOT NULL DEFAULT 25,"+
"delta INTEGER NOT NULL DEFAULT 15)", "Создана таблиця " + tableName);
}
@Override
public void createForeignKeys() throws SQLException {
}
@Override
public void createExtraConstraints() throws SQLException {
super.executeSqlStatement(
" ALTER TABLE shares ADD CONSTRAINT check_shares_delta CHECK(delta <= 100 and delta > 0)",
"Cоздано ограничение для shares, поле delta = [1,100]");
super.executeSqlStatement(
" ALTER TABLE shares ADD CONSTRAINT check_shares_changeProbability " +
"CHECK(changeProbability <= 100 and changeProbability > 0)",
"Cоздано ограничение для shares, поле changeProbability = 1..100");
}
}
Лістинги інших репозиторіїв та батьківського класу доступні за посиланням на github-репозиторій наприкінці статті. Зрозуміло, можна зробити інший дизайн програми або ретельніший рефакторинг програми: винести спільні частини в батьківський клас, виділити загальні методи і так далі. Але основна мета циклу статей – безпосередня робота з базою даних, так що за бажання дизайн програми тощо, ви можете виконати самостійно. Поточна структура проекту: Окрім репозиторіїв та моделей, ми додатково створабо клас StockExchangeDB
для загального управління нашою емуляцією. На цьому етапі ми керуємо репозиторіями (у наступних частинах перейдемо на послуги). Оголошуємо їх, запускаємо створення таблиць:
package sql.demo;
import org.h2.tools.DeleteDbFiles;
import sql.demo.repository.*;
import java.sql.*;
public class StockExchangeDB {
// Блок объявления констант
public static final String DB_DIR = "c:/JavaPrj/SQLDemo/db";
public static final String DB_FILE = "stockExchange";
public static final String DB_URL = "jdbc:h2:/" + DB_DIR + "/" + DB_FILE;
public static final String DB_Driver = "org.h2.Driver";
// Таблицы СУБД
Traiders traiders;
ShareRates shareRates;
Shares shares;
TraiderShareActions traiderShareActions;
// Получить новое соединение с БД
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(DB_URL);
}
// Инициализация
public StockExchangeDB(boolean renew) throws SQLException, ClassNotFoundException {
if (renew)
DeleteDbFiles.execute(DB_DIR, DB_FILE, false);
Class.forName(DB_Driver);
// Инициализируем таблицы
traiders = new Traiders();
shares = new Shares();
shareRates = new ShareRates();
traiderShareActions = new TraiderShareActions();
}
// Инициализация по умолчанию, без удаления файлу БД
public StockExchangeDB() throws SQLException, ClassNotFoundException {
this(false);
}
// створення всех таблиц и внешних ключей
public void createTablesAndForeignKeys() throws SQLException {
shares.createTable();
shareRates.createTable();
traiders.createTable();
traiderShareActions.createTable();
// створення ограничений на поля таблиц
traiderShareActions.createExtraConstraints();
shares.createExtraConstraints();
// створення внешних ключей (связи между таблицями)
shareRates.createForeignKeys();
traiderShareActions.createForeignKeys();
}
public static void main(String[] args) {
try{
StockExchangeDB stockExchangeDB = new StockExchangeDB(true);
stockExchangeDB.createTablesAndForeignKeys();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("Ошибка SQL !");
} catch (ClassNotFoundException e) {
System.out.println("JDBC драйвер для СУБД не найден!");
}
}
}
Результат виконання:
Резюме
У другій та третій частині статті ми дізналися:- Типи даних SQL.
- Таблиці БД.
- Проектування БД: структур таблиць та відносин між ними.
- Мова запитів SQL у частині створення таблиць БД, встановлення обмежень на поля та зв'язків між таблицями.
- Більше про взаємодію з JDBC.
- Триланкова (тришарова) Model/Repository/Service архітектура програми роботи з даними.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ