2. Архітектура багатошарового Java-додатка
Java-додаток часто поділяється на шари, кожен з яких має чітко визначену роль. Розглянемо типову тришарову архітектуру:
![]() |
- шар [1], який тут називається [ui] (User Interface), — це шар, що взаємодіє з користувачем через графічний інтерфейс Swing, консольний інтерфейс або веб-інтерфейс. Її завданням є передача даних, отриманих від користувача, на рівень [2] або ж відображення для користувача даних, наданих рівнем [2].
- Рівень [2], який тут називається [metier], — це рівень, що застосовує так звані бізнес-правила, c.a.d. специфічна логіка додатка, яка не зважає на те, звідки надходять дані, що їй надаються, і куди надходять результати, які вона генерує.
- шар [3], який тут називається [DAO] (Data Access Object), — це шар, який надає шару [2] попередньо збережені дані (файли, бази даних, ...) та зберігає деякі результати, надані шаром [2].
Існують різні варіанти реалізації шару [DAO]. Розглянемо деякі з них:
![]() |
Вищезазначений рівень [JDBC] є стандартним рівнем, що використовується в Java для доступу до баз даних. Він ізолює рівень [DAO] від рівня SGBD, який керує базою даних. Теоретично можна змінити SGBD, не змінюючи код шару [DAO]. Незважаючи на цю перевагу, API та JDBC мають певні недоліки:
- усі операції з SGBD можуть викликати контрольоване (checked) виключення SQLException. Це змушує код, що викликає (у даному випадку рівень [DAO]), оточувати їх блоками try/catch, що робить код досить громіздким.
- шар [DAO] не є повністю незалежним від SGBD. Наприклад, у них є власні методи автоматичного генерування значень первинних ключів, які шар [DAO] не може ігнорувати. Отже, під час вставки запису:
- у випадку з Oracle шар [DAO] повинен спочатку отримати значення первинного ключа запису, а потім вставити цей запис.
- у SQL Server шар [DAO] вставляє запис, якому шар SGBD автоматично присвоює значення первинного ключа, яке потім передається шару [DAO].
Ці відмінності можна усунути за допомогою збережених процедур. У наведеному вище прикладі рівень [DAO] викличе збережену процедуру в Oracle або SQL Server, яка врахує особливості рівня SGBD. Ці особливості будуть приховані на рівні [DAO]. Проте, хоча зміна SGBD не потребуватиме переписання шару [DAO], це все одно означатиме необхідність переписання збережених процедур. Це не можна вважати непереборною перешкодою.
Було докладено чимало зусиль, щоб ізолювати шар [DAO] від пропрієтарних аспектів SGBD. Рішенням, яке останніми роками мало справжній успіх у цій сфері, є рішення від Hibernate:
![]() |
Шар [Hibernate] розміщується між шаром [DAO], написаним розробником, та шаром [JDBC]. Hibernate — це ORM (об’єктно-реляційний мапер), інструмент, який слугує мостом між реляційним світом баз даних та світом об’єктів, з якими працює Java. Розробник рівня [DAO] більше не бачить ні рівень [JDBC], ні таблиці бази даних, вміст яких він хоче використовувати. Він бачить лише об’єктне зображення бази даних, яке надає рівень [Hibernate]. Зв’язок між таблицями бази даних та об’єктами, з якими працює рівень [DAO], здійснюється переважно двома способами:
- за допомогою конфігураційних файлів типу XML
- за допомогою анотацій Java у коді — ця техніка доступна лише починаючи з версії JDK 1.5
Рівень [Hibernate] є рівнем абстракції, який прагне бути максимально прозорим. Ідеальним варіантом є ситуація, коли розробник рівня [DAO] може повністю ігнорувати той факт, що він працює з базою даних. Це можливо, якщо саме не він пише конфігурацію, яка слугує мостом між реляційним та об’єктним світами. Налаштування цього мосту є досить складним і вимагає певної практики.
Об’єктний рівень [4], що є відображенням рівня BD, називається «контекстом персистентності». Шар [DAO], що базується на Hibernate, виконує операції збереження (CRUD: create — read — update — delete) над об’єктами контексту збереження, які Hibernate перетворює на команди SQL, що виконуються шаром JDBC. Для операцій запиту до бази даних (SQL Select), Hibernate надає розробнику мову HQL (Hibernate Query Language) для запитів до контексту персистентності [4], а не до самої BD.
Hibernate є популярним, але складним у освоєнні. Крива навчання, яку часто подають як легку, насправді є досить крутою. Як тільки у нас з’являється база даних із таблицями, що мають відношення «один до багатьох» або «багато до багатьох», налаштування реляційно-об’єктного мосту виходить за межі можливостей звичайного новачка. Помилки в налаштуванні можуть призвести до зниження продуктивності додатків.
З огляду на успіх продуктів ORM, компанія Sun, розробник Java, вирішила стандартизувати рівень ORM за допомогою специфікації під назвою JPA (Java Persistence API), яка з’явилася одночасно з Java 5. Специфікація JPA була реалізована в різних продуктах: Hibernate, Toplink, EclipseLink, OpenJpa, ... З JPA попередня архітектура набуває такого вигляду:
![]() |
Рівень [DAO] тепер взаємодіє зі специфікацією JPA — набором інтерфейсів. Розробник отримав переваги у вигляді стандартизації. Раніше, якщо він змінював свій рівень ORM, йому доводилося також змінювати рівень [DAO], який був написаний для взаємодії з конкретним ORM. Тепер він напише шар [DAO], який взаємодіятиме з шаром JPA. Незалежно від того, який продукт реалізує цей шар, інтерфейс шару JPA, що надається шару [DAO], залишається незмінним.
У цьому документі ми будемо використовувати шар [DAO], що базується на шарі JPA/Hibernate або JPA/EclipseLink. Крім того, ми використовуватимемо фреймворк Spring 2.8 для зв’язування цих шарів між собою.
![]() |
Головна перевага Spring полягає в тому, що він дозволяє пов’язувати шари за допомогою конфігурації, а не в коді. Отже, якщо реалізацію JPA / Hibernate потрібно замінити на реалізацію Hibernate без JPA, наприклад, тому що додаток працює в середовищі JDK 1.4, яке не підтримує JPA, ця зміна реалізації шару [DAO] не впливає на код шару [métier]. Необхідно змінити лише файл конфігурації Spring, який пов’язує шари між собою.
У Java EE 5 існує інше рішення: реалізувати шари [metier] та [DAO] за допомогою EJB3 (Enterprise Java Bean версії 3):
![]() |
Ми побачимо, що це рішення не надто відрізняється від того, що використовує Spring. Середовище Java EE5 доступне на так званих серверах додатків, таких як Sun Application Server 9.x (Glassfish), Jboss Application Server, Oracle Container for Java (OC4J) тощо. Сервер додатків — це, по суті, сервер веб-додатків. Існують також середовища EE 5, так звані «автономні», c.a.d, які можна використовувати поза межами сервера додатків. Це стосується JBoss, EJB3 або OpenEJB.
У середовищі EE5 шари реалізовані об’єктами, що називаються EJB (Enterprise Java Bean). У попередніх версіях EE об’єкти EJB (EJB, 2.x) вважалися складними у впровадженні та тестуванні, а іноді й малопродуктивними. Розрізняють EJB2.x «entity» та EJB2.x «session». Коротко кажучи, EJB2.x «entity» — це відображення рядка таблиці бази даних, а EJB2.x «session» — об’єкт, що використовується для реалізації шарів [metier], [DAO] багаторівневої архітектури. Одне з головних зауважень щодо шарів, реалізованих за допомогою EJB, полягає в тому, що їх можна використовувати лише в контейнерах EJB — сервісі, що надається середовищем EE. Це середовище, яке складніше налагодити, ніж середовище SE (Standard Edition), може відбивати у розробника бажання часто проводити тестування. Проте існують середовища розробки Java, які спрощують використання сервера додатків завдяки автоматизації розгортання EJB на сервері: Eclipse, NetBeans, JDeveloper, IntelliJ, IDEA. У цьому посібнику ми використовуватимемо NetBeans 6.8 та сервер додатків GlassFish v3.
Фреймворк Spring з’явився у відповідь на складність EJB2. Spring надає в середовищі SE значну кількість сервісів, які зазвичай забезпечують середовища EE. Так, у розділі «Збереження даних» Spring надає пули з’єднань та менеджери транзакцій, необхідні для додатків. Поява Spring сприяла розвитку культури модульного тестування, яке стало простішим у реалізації в контексті SE, ніж у контексті EE. Spring дозволяє реалізовувати рівні додатка за допомогою класичних об’єктів Java (POJO, Plain Old/Ordinary Java Object), що дає змогу повторно використовувати їх в іншому контексті. Нарешті, він досить прозоро інтегрує численні сторонні інструменти, зокрема інструменти збереження даних, такі як Hibernate, EclipseLink, Ibatis тощо.
Java EE5 було розроблено для усунення недоліків специфікації EJB2. EJB та 2.x перетворилися на EJB3. Це POJOs, позначені анотаціями, які роблять їх особливими об’єктами, коли вони знаходяться всередині контейнера EJB3. У цьому контейнері EJB3 зможе користуватися послугами контейнера (пул з’єднань, менеджер транзакцій тощо). Поза контейнером EJB3 об’єкт EJB3 стає звичайним об’єктом Java. Його анотації EJB ігноруються.
Вище ми зобразили Spring та контейнер EJB3 як можливу інфраструктуру (фреймворк) нашої багаторівневої архітектури. Саме ця інфраструктура надаватиме необхідні нам послуги: пул з’єднань та менеджер транзакцій.
- З використанням Spring шари будуть реалізовані за допомогою POJOs. Вони матимуть доступ до сервісів Spring (пул з’єднань, менеджер транзакцій) шляхом ін’єкції залежностей у ці POJOs: під час їх створення Spring ін’єктує в них посилання на сервіси, які їм знадобляться.
- У контейнері EJB3 шари будуть реалізовані за допомогою EJB. Архітектура з шарами, реалізованими за допомогою EJB3, мало чим відрізняється від архітектури, реалізованої за допомогою POJO, екземпляри яких створює Spring. Ми знайдемо багато схожих рис.
- На завершення ми наведемо приклад багатошарового веб-додатку:
![]() |






