- Шаблон DAO в Java
- Дальнейшее чтение:
- Введение в Spring Data JPA
- Обзор JPA / Hibernate Типы Каскадов
- 2. Простая реализация
- 2.1. Доменный класс
- 2.2. API DAO
- 2.3. КлассUserDao
- 3. Использование шаблона с JPA
- 3.1. КлассJpaUserDao
- 3.2. Рефакторинг классаUser
- 3.3. Программная загрузка JPA Entity Manager
- 3.4. КлассUserApplication
- 4. Заключение
- Data Access Object
- Detailed Description
- Detailed Example
Шаблон DAO в Java
Шаблон объекта доступа к данным (DAO) — это структурный шаблон, который позволяет намisolate the application/business layer from the persistence layer (usually a relational database, but it could be any other persistence mechanism) using an abstract API.
Функциональность этого API заключается в том, чтобы скрыть от приложения все сложности, связанные с выполнением операций CRUD в базовом механизме хранения. Это позволяет обоим слоям развиваться отдельно, ничего не зная друг о друге.
В этом руководстве мы глубоко погрузимся в реализацию шаблона и узнаем, как использовать его для абстрагирования вызововJPA entity manager.
Дальнейшее чтение:
Введение в Spring Data JPA
Введение в Spring Data JPA с Spring 4 — конфигурация Spring, DAO, ручные и сгенерированные запросы и управление транзакциями.
Обзор JPA / Hibernate Типы Каскадов
Быстрый и практический обзор типов каскада JPA / Hibernate.
2. Простая реализация
Чтобы понять, как работает шаблон DAO, давайте создадим простой пример.
Допустим, мы хотим разработать приложение, управляющее пользователями. Чтобы модель предметной области приложения полностью не зависела от базы данных, мы создадимa simple DAO class that will take care of keeping these components neatly decoupled from each other.
2.1. Доменный класс
Поскольку наше приложение будет работать с пользователями, нам нужно определить только один класс для реализации его доменной модели:
КлассUser — это просто простой контейнер для пользовательских данных, поэтому он не реализует какое-либо другое поведение, заслуживающее внимания.
Конечно, наиболее актуальный выбор дизайна, который нам нужно сделать, это как сохранить приложение, использующее этот класс, изолированным от любого механизма персистентности, который может быть реализован в какой-то момент.
Что ж, это именно та проблема, которую пытается решить шаблон DAO.
2.2. API DAO
Давайте определим базовый уровень DAO, чтобы увидеть, как он можетkeep the domain model completely decoupled from the persistence layer.
public interface Dao < Optionalget(long id); List getAll(); void save(T t); void update(T t, String[] params); void delete(T t); >
С высоты птичьего полета видно, что интерфейсDao определяет абстрактный API, который выполняет операции CRUD с объектами типаT.
Благодаря высокому уровню абстракции, который предоставляет интерфейс, легко создать конкретную детализированную реализацию, которая работает с объектамиUser.
2.3. КлассUserDao
Давайте определим индивидуальную реализацию интерфейсаDao:
public class UserDao implements Dao < private Listusers = new ArrayList<>(); public UserDao() < users.add(new User("John", "[email protected]")); users.add(new User("Susan", "[email protected]")); >@Override public Optional get(long id) < return Optional.ofNullable(users.get((int) id)); >@Override public List getAll() < return users; >@Override public void save(User user) < users.add(user); >@Override public void update(User user, String[] params) < user.setName(Objects.requireNonNull( params[0], "Name cannot be null")); user.setEmail(Objects.requireNonNull( params[1], "Email cannot be null")); users.add(user); >@Override public void delete(User user) < users.remove(user); >>
КлассUserDao реализует все функции, необходимые для выборки, обновления и удаления объектовUser.
For simplicity’s sake, the users List acts like an in-memory database, which is populated with a couple of User objects in the constructor.
Конечно, другие методы легко реорганизовать, чтобы они могли работать, например, с реляционной базой данных.
Хотя классыUser иUserDao сосуществуют независимо в одном приложении, нам все же нужно посмотреть, как последний может использоваться для сохранения уровня сохраняемости скрытым от логики приложения:
public class UserApplication < private static Dao userDao; public static void main(String[] args) < userDao = new UserDao(); User user1 = getUser(0); System.out.println(user1); userDao.update(user1, new String[]); User user2 = getUser(1); userDao.delete(user2); userDao.save(new User("Julie", "[email protected]")); userDao.getAll().forEach(user -> System.out.println(user.getName())); > private static User getUser(long id) < Optionaluser = userDao.get(id); return user.orElseGet( () -> new User("non-existing user", "no-email")); > >
Пример придуман, но в двух словах показывает мотивы, лежащие в основе шаблона DAO. В этом случае методmain просто использует экземплярUserDao для выполнения операций CRUD с несколькими объектамиUser.
The most relevant facet of this process is how UserDao hides from the application all the low-level details on how the objects are persisted, updated, and deleted.
3. Использование шаблона с JPA
Среди разработчиков есть общая тенденция думать, что выпуск JPA снизил функциональность шаблона DAO до нуля, поскольку шаблон становится просто еще одним уровнем абстракции и сложности, реализованным поверх уровня, предоставляемого менеджером сущностей JPA.
Безусловно, в некоторых сценариях это действительно так. Даже в этом случае, sometimes we just want to expose to our application only a few domain-specific methods of the entity manager’s API.. В таких случаях шаблон DAO имеет свое место.
3.1. КлассJpaUserDao
С учетом сказанного, давайте создадим новую реализацию интерфейсаDao, чтобы мы могли увидеть, как он может инкапсулировать функциональность, которую менеджер сущностей JPA предоставляет из коробки:
public class JpaUserDao implements Dao < private EntityManager entityManager; // standard constructors @Override public Optionalget(long id) < return Optional.ofNullable(entityManager.find(User.class, id)); >@Override public List getAll() < Query query = entityManager.createQuery("SELECT e FROM User e"); return query.getResultList(); >@Override public void save(User user) < executeInsideTransaction(entityManager ->entityManager.persist(user)); > @Override public void update(User user, String[] params) < user.setName(Objects.requireNonNull(params[0], "Name cannot be null")); user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null")); executeInsideTransaction(entityManager ->entityManager.merge(user)); > @Override public void delete(User user) < executeInsideTransaction(entityManager ->entityManager.remove(user)); > private void executeInsideTransaction(Consumer action) < EntityTransaction tx = entityManager.getTransaction(); try < tx.begin(); action.accept(entityManager); tx.commit(); >catch (RuntimeException e) < tx.rollback(); throw e; >> >
КлассJpaUserDao может работать с любой реляционной базой данных, поддерживаемой реализацией JPA.
Более того, если мы внимательно посмотрим на класс, мы поймем, как использованиеComposition иDependency Injection позволяет нам вызывать только те методы диспетчера сущностей, которые требуются нашему приложению.
Проще говоря, у нас есть специализированный API для домена, а не API всего менеджера сущностей.
3.2. Рефакторинг классаUser
В этом случае мы будем использовать Hibernate в качестве реализации JPA по умолчанию, поэтому мы соответствующим образом реорганизуем классUser:
@Entity @Table(name = "users") public class User < @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; private String email; // standard constructors / setters / getters >
3.3. Программная загрузка JPA Entity Manager
Предполагая, что у нас уже есть рабочий экземпляр MySQL, работающий локально или удаленно, и таблица базы данных“users” заполнена некоторыми пользовательскими записями, нам нужно получить диспетчер сущностей JPA, чтобы мы могли использовать классJpaUserDao для выполнения CRUD-операций в базе данных.
В большинстве случаев мы делаем это с помощью стандартного файла“persistence.xml”.
В этом случае мы воспользуемся подходом“xml-less” и получим диспетчер сущностей с простой Java через удобный классEntityManagerFactoryBuilderImpl Hibernate.
Для подробного объяснения того, как запустить реализацию JPA с помощью Java, проверьтеthis article.
3.4. КлассUserApplication
Наконец, давайте реорганизуем исходный классUserApplication, чтобы он мог работать с экземпляромJpaUserDao и выполнять операции CRUD над объектамиUser:
public class UserApplication < private static DaojpaUserDao; // standard constructors public static void main(String[] args) < User user1 = getUser(1); System.out.println(user1); updateUser(user1, new String[]); saveUser(new User("Monica", "[email protected]")); deleteUser(getUser(2)); getAllUsers().forEach(user -> System.out.println(user.getName())); > public static User getUser(long id) < Optionaluser = jpaUserDao.get(id); return user.orElseGet( () -> new User("non-existing user", "no-email")); > public static List getAllUsers() < return jpaUserDao.getAll(); >public static void updateUser(User user, String[] params) < jpaUserDao.update(user, params); >public static void saveUser(User user) < jpaUserDao.save(user); >public static void deleteUser(User user) < jpaUserDao.delete(user); >>
Даже когда пример действительно довольно ограничен, он остается полезным для демонстрации того, как интегрировать функциональность шаблона DAO с тем, что предоставляет менеджер сущностей.
В большинстве приложений есть структура DI, которая отвечает за внедрение экземпляраJpaUserDao в классUserApplication. Для простоты мы опустили детали этого процесса.
Здесь наиболее важно подчеркнуть, какthe JpaUserDao class helps to keep the UserApplication class completely agnostic about how the persistence layer performs CRUD operations.
Кроме того, в дальнейшем мы могли бы заменить MySQL на любую другую СУБД (и даже на плоскую базу данных), и, тем не менее, наше приложение продолжит работать, как ожидалось, благодаря уровню абстракции, обеспечиваемому интерфейсомDao и менеджер сущности.
4. Заключение
В этой статье мы подробно рассмотрели ключевые концепции шаблона DAO, как реализовать его на Java и как использовать его поверх диспетчера сущностей JPA.
Как обычно, доступны все примеры кода, показанные в этой статьеover on GitHub.
Data Access Object
Code that depends on specific features of data resources ties together business logic with data access logic. This makes it difficult to replace or modify an application’s data resources.
The Data Access Object (or DAO) pattern:
- separates a data resource’s client interface from its data access mechanisms
- adapts a specific data resource’s access API to a generic client interface
The DAO pattern allows data access mechanisms to change independently of the code that uses the data.
Detailed Description
See the Core J2EE TM Patterns
Detailed Example
The Java Pet Store sample application uses the DAO pattern both for database vendor-neutral data access, and to represent XML data sources as objects.
- Accessing a database with a DAO. A Data Access Object class can provide access to a particular data resource without coupling the resource’s API to the business logic. For example, sample application classes access catalog categories, products, and items using DAO interface CatalogDAO . Reimplementing CatalogDAO for a different data access mechanism (to use a Connector, for example), would have little or no impact on any classes that use CatalogDAO , because only the implementation would change. Each potential alternate implementation of CatalogDAO would access data for the items in the catalog in its own way, while presenting the same API to the class that uses it. The following code excerpts illustrate how the sample application uses the DAO pattern to separate business logic from data resource access mechanisms:
- Interface CatalogDAO defines the DAO API. Notice that the methods in the interface below make no reference to a specific data access mechanism. For example, none of the methods specify an SQL query, and they throw only exceptions of type CatalogDAOSysException . Avoiding mechanism-specific information in the DAO interface, including exceptions thrown, is essential for hiding implementation details.
public interface CatalogDAO
public class CloudscapeCatalogDAO implements CatalogDAO < . public static String GET_CATEGORY_STATEMENT = "select name, descn " + " from (category a join category_details b on a.catid=b.catid) " + " where locale = ? and a.catid = ?"; . public Category getCategory(String categoryID, Locale l) throws CatalogDAOSysException < Connection c = null; PreparedStatement ps = null; ResultSet rs = null; Category ret = null; try < c = getDataSource().getConnection(); ps = c.prepareStatement(GET_CATEGORY_STATEMENT,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); ps.setString(1, l.toString()); ps.setString(2, categoryID); rs = ps.executeQuery(); if (rs.first()) < ret = new Category(categoryID, rs.getString(1), rs.getString(2)); >rs.close(); ps.close(); c.close(); return ret; > catch (SQLException se) < throw new CatalogDAOSysException("SQLException: " + se.getMessage()); >> . >
select name, descn from (category a join category_details b on a.catid=b.catid) where locale = ? and a.catid = ? . select name, descn from category a, category_details b where a.catid = b.catid and locale = ? and a.catid = ? .
Method GenericCatalogDAO.getCategory chooses the SQL corresponding to the configured database type, and uses it to fetch a category from the database via JDBC. The following code excerpt shows shows the implementation of the method.
public Category getCategory(String categoryID, Locale locale) throws CatalogDAOSysException < Connection connection = null; ResultSet resultSet = null; PreparedStatement statement = null; try < connection = getDataSource().getConnection(); String[] parameterValues = new String[] < locale.toString(), categoryID >; statement = buildSQLStatement(connection, sqlStatements, XML_GET_CATEGORY, parameterValues); resultSet = statement.executeQuery(); if (resultSet.first()) < return new Category(categoryID, resultSet.getString(1), resultSet.getString(2)); >return null; > catch (SQLException exception) < throw new CatalogDAOSysException("SQLException: " + exception.getMessage()); >finally < closeAll(connection, statement, resultSet); >>
public static Screens loadScreenDefinitions(URL location) < Element root = loadDocument(location); if (root != null) return getScreens(root); else return null; >. public static Screens getScreens(Element root) < // get the template String defaultTemplate = getTagValue(root, DEFAULT_TEMPLATE); if (defaultTemplate == null) < System.err.println("*** ScreenDefinitionDAO error: " + " Default Template not Defined."); return null; >Screens screens = new Screens(defaultTemplate); getTemplates(root, screens); // get screens NodeList list = root.getElementsByTagName(SCREEN); for (int loop = 0; loop < list.getLength(); loop++) < Node node = list.item(loop); if ((node != null) && node instanceof Element) < String templateName = ((Element)node).getAttribute(TEMPLATE); String screenName = ((Element)node).getAttribute(NAME); HashMap parameters = getParameters(node); Screen screen = new Screen(screenName, templateName, parameters); if (!screens.containsScreen(screenName)) < screens.addScreen(screenName, screen); >else < System.err.println("*** Non Fatal errror: Screen " + screenName + " defined more than once in screen definitions file"); >> > return screens; > .