From b376f1de9a538f60b679928ba4bb544c70951dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sun, 7 Apr 2024 10:37:05 +0300 Subject: [PATCH] docs: update dao --- dao/README.md | 203 +++++++++++------- .../com/iluwatar/dao/CustomException.java | 10 +- .../test/java/com/iluwatar/dao/AppTest.java | 1 - .../com/iluwatar/dao/DbCustomerDaoTest.java | 22 +- .../iluwatar/dao/InMemoryCustomerDaoTest.java | 4 +- 5 files changed, 131 insertions(+), 109 deletions(-) diff --git a/dao/README.md b/dao/README.md index 3b0898717..7376c353f 100644 --- a/dao/README.md +++ b/dao/README.md @@ -1,30 +1,35 @@ --- title: Data Access Object -category: Architectural +category: Structural language: en tag: - - Data access + - Data access + - Layered architecture + - Persistence --- +## Also known as + +* Data Access Layer +* DAO + ## Intent -Object provides an abstract interface to some type of database or other persistence mechanism. +The Data Access Object (DAO) design pattern aims to separate the application's business logic from the persistence layer, typically a database or any other storage mechanism. By using DAOs, the application can access and manipulate data without being dependent on the specific database implementation details. ## Explanation Real world example -> There's a set of customers that need to be persisted to database. Additionally we need the whole -> set of CRUD (create/read/update/delete) operations so we can operate on customers easily. +> There's a set of customers that need to be persisted to database. Additionally, we need the whole set of CRUD (create/read/update/delete) operations, so we can operate on customers easily. In plain words -> DAO is an interface we provide over the base persistence mechanism. +> DAO is an interface we provide over the base persistence mechanism. Wikipedia says -> In computer software, a data access object (DAO) is a pattern that provides an abstract interface -> to some type of database or other persistence mechanism. +> In computer software, a data access object (DAO) is a pattern that provides an abstract interface to some type of database or other persistence mechanism. **Programmatic Example** @@ -33,114 +38,113 @@ Walking through our customers example, here's the basic `Customer` entity. ```java public class Customer { - private int id; - private String firstName; - private String lastName; + private int id; + private String firstName; + private String lastName; - public Customer(int id, String firstName, String lastName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } - // getters and setters -> - ... + public Customer(int id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } } ``` -Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao` -keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation. +Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao` keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation. ```java public interface CustomerDao { - Stream getAll() throws Exception; + Stream getAll() throws Exception; - Optional getById(int id) throws Exception; + Optional getById(int id) throws Exception; - boolean add(Customer customer) throws Exception; + boolean add(Customer customer) throws Exception; - boolean update(Customer customer) throws Exception; + boolean update(Customer customer) throws Exception; - boolean delete(Customer customer) throws Exception; + boolean delete(Customer customer) throws Exception; } public class InMemoryCustomerDao implements CustomerDao { - private final Map idToCustomer = new HashMap<>(); + private final Map idToCustomer = new HashMap<>(); - // implement the interface using the map - ... + // implement the interface using the map } @Slf4j public class DbCustomerDao implements CustomerDao { - private final DataSource dataSource; + private final DataSource + dataSource; - public DbCustomerDao(DataSource dataSource) { - this.dataSource = dataSource; + public DbCustomerDao( + DataSource dataSource) { + this.dataSource = + dataSource; } // implement the interface using the data source - ... +} ``` -Finally here's how we use our DAO to manage customers. +Finally, here's how we use our DAO to manage customers. ```java - final var dataSource = createDataSource(); - createSchema(dataSource); - final var customerDao = new DbCustomerDao(dataSource); - - addCustomers(customerDao); - log.info(ALL_CUSTOMERS); - try (var customerStream = customerDao.getAll()) { - customerStream.forEach((customer) -> log.info(customer.toString())); - } - log.info("customerDao.getCustomerById(2): " + customerDao.getById(2)); - final var customer = new Customer(4, "Dan", "Danson"); - customerDao.add(customer); - log.info(ALL_CUSTOMERS + customerDao.getAll()); - customer.setFirstName("Daniel"); - customer.setLastName("Danielson"); - customerDao.update(customer); - log.info(ALL_CUSTOMERS); - try (var customerStream = customerDao.getAll()) { - customerStream.forEach((cust) -> log.info(cust.toString())); - } - customerDao.delete(customer); - log.info(ALL_CUSTOMERS + customerDao.getAll()); - - deleteSchema(dataSource); + final var dataSource=createDataSource(); + createSchema(dataSource); +final var customerDao=new DbCustomerDao(dataSource); + + addCustomers(customerDao); + log.info(ALL_CUSTOMERS); + try(var customerStream=customerDao.getAll()){ + customerStream.forEach((customer)->log.info(customer.toString())); + } + log.info("customerDao.getCustomerById(2): "+customerDao.getById(2)); +final var customer=new Customer(4,"Dan","Danson"); + customerDao.add(customer); + log.info(ALL_CUSTOMERS+customerDao.getAll()); + customer.setFirstName("Daniel"); + customer.setLastName("Danielson"); + customerDao.update(customer); + log.info(ALL_CUSTOMERS); + try(var customerStream=customerDao.getAll()){ + customerStream.forEach((cust)->log.info(cust.toString())); + } + customerDao.delete(customer); + log.info(ALL_CUSTOMERS+customerDao.getAll()); + + deleteSchema(dataSource); ``` The program output: ```java -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@7cef4e59 -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -Customer{id=4, firstName='Daniel', lastName='Danielson'} -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@2db0f6b2 -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@12c8a2c0 -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -Customer{id=4, firstName='Daniel', lastName='Danielson'} -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c +customerDao.getAllCustomers(): + Customer{id=1,firstName='Adam',lastName='Adamson'} + Customer{id=2,firstName='Bob',lastName='Bobson'} + Customer{id=3,firstName='Carl',lastName='Carlson'} + customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}] + customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@7cef4e59 + customerDao.getAllCustomers(): + Customer{id=1,firstName='Adam',lastName='Adamson'} + Customer{id=2,firstName='Bob',lastName='Bobson'} + Customer{id=3,firstName='Carl',lastName='Carlson'} + Customer{id=4,firstName='Daniel',lastName='Danielson'} + customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@2db0f6b2 + customerDao.getAllCustomers(): + Customer{id=1,firstName='Adam',lastName='Adamson'} + Customer{id=2,firstName='Bob',lastName='Bobson'} + Customer{id=3,firstName='Carl',lastName='Carlson'} + customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}] + customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@12c8a2c0 + customerDao.getAllCustomers(): + Customer{id=1,firstName='Adam',lastName='Adamson'} + Customer{id=2,firstName='Bob',lastName='Bobson'} + Customer{id=3,firstName='Carl',lastName='Carlson'} + Customer{id=4,firstName='Daniel',lastName='Danielson'} + customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@6ec8211c ``` ## Class diagram @@ -151,14 +155,47 @@ customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c Use the Data Access Object in any of the following situations: -* When you want to consolidate how the data layer is accessed. -* When you want to avoid writing multiple data retrieval/persistence layers. +* There is a need to abstract and encapsulate all access to the data source. +* The application needs to support multiple types of databases or storage mechanisms without significant code changes. +* You want to keep the database access clean and simple, and separate from business logic. ## Tutorials * [The DAO Pattern in Java](https://www.baeldung.com/java-dao-pattern) * [Data Access Object Pattern](https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm) +## Known Uses + +* Enterprise applications that require database interaction. +* Applications requiring data access to be adaptable to multiple storage types (relational databases, XML files, flat files, etc.). +* Frameworks providing generic data access functionalities. + +## Consequences + +Benefits: + +* Decoupling: Separates the data access logic from the business logic, enhancing modularity and clarity. +* Reusability: DAOs can be reused across different parts of the application or even in different projects. +* Testability: Simplifies testing by allowing business logic to be tested separately from the data access logic. +* Flexibility: Makes it easier to switch underlying storage mechanisms with minimal impact on the application code. + +Trade-offs: + +* Layer Complexity: Introduces additional layers to the application, which can increase complexity and development time. +* Overhead: For simple applications, the DAO pattern might introduce more overhead than necessary. +* Learning Curve: Developers might need time to understand and implement the pattern effectively, especially in complex projects. + +## Related Patterns + +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/): Often used in conjunction with the DAO pattern to define application's boundaries and its set of available operations. +* [Factory](https://java-design-patterns.com/patterns/factory/): Can be used to instantiate DAOs dynamically, providing flexibility in the choice of implementation. +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Helps in abstracting the creation of DAOs, especially when supporting multiple databases or storage mechanisms. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Might be employed to change the data access strategy at runtime, depending on the context. + ## Credits * [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/49u3r91) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3U5cxEI) +* [Expert One-on-One J2EE Design and Development](https://amzn.to/3vK3pfq) +* [Professional Java Development with the Spring Framework](https://amzn.to/49tANF0) diff --git a/dao/src/main/java/com/iluwatar/dao/CustomException.java b/dao/src/main/java/com/iluwatar/dao/CustomException.java index 4de6d9e43..ce8ca5642 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomException.java +++ b/dao/src/main/java/com/iluwatar/dao/CustomException.java @@ -24,20 +24,16 @@ */ package com.iluwatar.dao; +import java.io.Serial; + /** * Custom exception. */ public class CustomException extends Exception { + @Serial private static final long serialVersionUID = 1L; - public CustomException() { - } - - public CustomException(String message) { - super(message); - } - public CustomException(String message, Throwable cause) { super(message, cause); } diff --git a/dao/src/test/java/com/iluwatar/dao/AppTest.java b/dao/src/test/java/com/iluwatar/dao/AppTest.java index ee8f4f4a3..64d8b2805 100644 --- a/dao/src/test/java/com/iluwatar/dao/AppTest.java +++ b/dao/src/test/java/com/iluwatar/dao/AppTest.java @@ -35,7 +35,6 @@ class AppTest { /** * Issue: Add at least one assertion to this test case. - * * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} * throws an exception. */ diff --git a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java index cfa7e9882..759ab155c 100644 --- a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java +++ b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java @@ -87,7 +87,7 @@ class DbCustomerDaoTest { } /** - * Represents the scenario when DAO operations are being performed on a non existing customer. + * Represents the scenario when DAO operations are being performed on a non-existing customer. */ @Nested class NonExistingCustomer { @@ -206,39 +206,29 @@ class DbCustomerDaoTest { @Test void addingACustomerFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.add(new Customer(2, "Bernard", "Montgomery")); - }); + assertThrows(Exception.class, () -> dao.add(new Customer(2, "Bernard", "Montgomery"))); } @Test void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() { - assertThrows(Exception.class, () -> { - dao.delete(existingCustomer); - }); + assertThrows(Exception.class, () -> dao.delete(existingCustomer)); } @Test void updatingACustomerFailsWithFeedbackToTheClient() { final var newFirstname = "Bernard"; final var newLastname = "Montgomery"; - assertThrows(Exception.class, () -> { - dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname)); - }); + assertThrows(Exception.class, () -> dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname))); } @Test void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.getById(existingCustomer.getId()); - }); + assertThrows(Exception.class, () -> dao.getById(existingCustomer.getId())); } @Test void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.getAll(); - }); + assertThrows(Exception.class, () -> dao.getAll()); } } diff --git a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java index 855dd01d3..340a0920c 100644 --- a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java +++ b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java @@ -48,7 +48,7 @@ class InMemoryCustomerDaoTest { } /** - * Represents the scenario when the DAO operations are being performed on a non existent + * Represents the scenario when the DAO operations are being performed on a non-existent * customer. */ @Nested @@ -78,7 +78,7 @@ class InMemoryCustomerDaoTest { } @Test - void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception { + void updateShouldBeFailureAndNotAffectExistingCustomers() { final var nonExistingId = getNonExistingCustomerId(); final var newFirstname = "Douglas"; final var newLastname = "MacArthur";