docs: update dao

This commit is contained in:
Ilkka Seppälä committed 2024-04-07 10:37:05 +03:00
1 parent b1f8a16b9c
commit b376f1de9a
5 files changed
+127 -105

No files matched your search

+116 -79
View File
@@ -1,21 +1,27 @@
--- ---
title: Data Access Object title: Data Access Object
category: Architectural category: Structural
language: en language: en
tag: tag:
- Data access - Data access
- Layered architecture
- Persistence
--- ---
## Also known as
* Data Access Layer
* DAO
## Intent ## 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 ## Explanation
Real world example Real world example
> There's a set of customers that need to be persisted to database. Additionally we need the whole > 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.
> set of CRUD (create/read/update/delete) operations so we can operate on customers easily.
In plain words In plain words
@@ -23,8 +29,7 @@ In plain words
Wikipedia says Wikipedia says
> In computer software, a data access object (DAO) is a pattern that provides an abstract interface > 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.
> to some type of database or other persistence mechanism.
**Programmatic Example** **Programmatic Example**
@@ -33,114 +38,113 @@ Walking through our customers example, here's the basic `Customer` entity.
```java ```java
public class Customer { public class Customer {
private int id; private int id;
private String firstName; private String firstName;
private String lastName; private String lastName;
public Customer(int id, String firstName, String lastName) { public Customer(int id, String firstName, String lastName) {
this.id = id; this.id = id;
this.firstName = firstName; this.firstName = firstName;
this.lastName = lastName; this.lastName = lastName;
} }
// getters and setters ->
...
} }
``` ```
Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao` 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.
keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation.
```java ```java
public interface CustomerDao { public interface CustomerDao {
Stream<Customer> getAll() throws Exception; Stream<Customer> getAll() throws Exception;
Optional<Customer> getById(int id) throws Exception; Optional<Customer> 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 { public class InMemoryCustomerDao implements CustomerDao {
private final Map<Integer, Customer> idToCustomer = new HashMap<>(); private final Map<Integer, Customer> idToCustomer = new HashMap<>();
// implement the interface using the map // implement the interface using the map
...
} }
@Slf4j @Slf4j
public class DbCustomerDao implements CustomerDao { public class DbCustomerDao implements CustomerDao {
private final DataSource dataSource; private final DataSource
dataSource;
public DbCustomerDao(DataSource dataSource) { public DbCustomerDao(
this.dataSource = dataSource; DataSource dataSource) {
this.dataSource =
dataSource;
} }
// implement the interface using the data source // 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 ```java
final var dataSource = createDataSource(); final var dataSource=createDataSource();
createSchema(dataSource); createSchema(dataSource);
final var customerDao = new DbCustomerDao(dataSource); final var customerDao=new DbCustomerDao(dataSource);
addCustomers(customerDao); addCustomers(customerDao);
log.info(ALL_CUSTOMERS); log.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) { try(var customerStream=customerDao.getAll()){
customerStream.forEach((customer) -> log.info(customer.toString())); customerStream.forEach((customer)->log.info(customer.toString()));
} }
log.info("customerDao.getCustomerById(2): " + customerDao.getById(2)); log.info("customerDao.getCustomerById(2): "+customerDao.getById(2));
final var customer = new Customer(4, "Dan", "Danson"); final var customer=new Customer(4,"Dan","Danson");
customerDao.add(customer); customerDao.add(customer);
log.info(ALL_CUSTOMERS + customerDao.getAll()); log.info(ALL_CUSTOMERS+customerDao.getAll());
customer.setFirstName("Daniel"); customer.setFirstName("Daniel");
customer.setLastName("Danielson"); customer.setLastName("Danielson");
customerDao.update(customer); customerDao.update(customer);
log.info(ALL_CUSTOMERS); log.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) { try(var customerStream=customerDao.getAll()){
customerStream.forEach((cust) -> log.info(cust.toString())); customerStream.forEach((cust)->log.info(cust.toString()));
} }
customerDao.delete(customer); customerDao.delete(customer);
log.info(ALL_CUSTOMERS + customerDao.getAll()); log.info(ALL_CUSTOMERS+customerDao.getAll());
deleteSchema(dataSource); deleteSchema(dataSource);
``` ```
The program output: The program output:
```java ```java
customerDao.getAllCustomers(): customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'} Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'} Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'} Customer{id=3,firstName='Carl',lastName='Carlson'}
customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}]
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@7cef4e59 customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@7cef4e59
customerDao.getAllCustomers(): customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'} Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'} Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'} Customer{id=3,firstName='Carl',lastName='Carlson'}
Customer{id=4, firstName='Daniel', lastName='Danielson'} Customer{id=4,firstName='Daniel',lastName='Danielson'}
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@2db0f6b2 customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@2db0f6b2
customerDao.getAllCustomers(): customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'} Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'} Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'} Customer{id=3,firstName='Carl',lastName='Carlson'}
customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}]
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@12c8a2c0 customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@12c8a2c0
customerDao.getAllCustomers(): customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'} Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'} Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'} Customer{id=3,firstName='Carl',lastName='Carlson'}
Customer{id=4, firstName='Daniel', lastName='Danielson'} Customer{id=4,firstName='Daniel',lastName='Danielson'}
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@6ec8211c
``` ```
## Class diagram ## 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: Use the Data Access Object in any of the following situations:
* When you want to consolidate how the data layer is accessed. * There is a need to abstract and encapsulate all access to the data source.
* When you want to avoid writing multiple data retrieval/persistence layers. * 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 ## Tutorials
* [The DAO Pattern in Java](https://www.baeldung.com/java-dao-pattern) * [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) * [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 ## 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) * [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)
@@ -24,20 +24,16 @@
*/ */
package com.iluwatar.dao; package com.iluwatar.dao;
import java.io.Serial;
/** /**
* Custom exception. * Custom exception.
*/ */
public class CustomException extends Exception { public class CustomException extends Exception {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public CustomException() {
}
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause) { public CustomException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
@@ -35,7 +35,6 @@ class AppTest {
/** /**
* Issue: Add at least one assertion to this test case. * 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[])} * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception. * throws an exception.
*/ */
@@ -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 @Nested
class NonExistingCustomer { class NonExistingCustomer {
@@ -206,39 +206,29 @@ class DbCustomerDaoTest {
@Test @Test
void addingACustomerFailsWithExceptionAsFeedbackToClient() { void addingACustomerFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> { assertThrows(Exception.class, () -> dao.add(new Customer(2, "Bernard", "Montgomery")));
dao.add(new Customer(2, "Bernard", "Montgomery"));
});
} }
@Test @Test
void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() { void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() {
assertThrows(Exception.class, () -> { assertThrows(Exception.class, () -> dao.delete(existingCustomer));
dao.delete(existingCustomer);
});
} }
@Test @Test
void updatingACustomerFailsWithFeedbackToTheClient() { void updatingACustomerFailsWithFeedbackToTheClient() {
final var newFirstname = "Bernard"; final var newFirstname = "Bernard";
final var newLastname = "Montgomery"; final var newLastname = "Montgomery";
assertThrows(Exception.class, () -> { assertThrows(Exception.class, () -> dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname)));
dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname));
});
} }
@Test @Test
void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() { void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> { assertThrows(Exception.class, () -> dao.getById(existingCustomer.getId()));
dao.getById(existingCustomer.getId());
});
} }
@Test @Test
void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> { assertThrows(Exception.class, () -> dao.getAll());
dao.getAll();
});
} }
} }
@@ -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. * customer.
*/ */
@Nested @Nested
@@ -78,7 +78,7 @@ class InMemoryCustomerDaoTest {
} }
@Test @Test
void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception { void updateShouldBeFailureAndNotAffectExistingCustomers() {
final var nonExistingId = getNonExistingCustomerId(); final var nonExistingId = getNonExistingCustomerId();
final var newFirstname = "Douglas"; final var newFirstname = "Douglas";
final var newLastname = "MacArthur"; final var newLastname = "MacArthur";