docs: update dao

This commit is contained in:
Ilkka Seppälä
2024-04-07 10:37:05 +03:00
parent b1f8a16b9c
commit b376f1de9a
5 changed files with 131 additions and 109 deletions
+120 -83
View File
@@ -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<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 {
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
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)
@@ -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);
}
@@ -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.
*/
@@ -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());
}
}
@@ -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";