mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-15 16:58:56 +00:00
docs: update dao
This commit is contained in:
+120
-83
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user