Data Access Object
Also known as
- Data Access Layer
- DAO
Intent
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.
In plain words
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.
Programmatic Example
Walking through our customers example, here's the basic Customer
entity.
public class Customer {
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;
}
}
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.
public interface CustomerDao {
Stream<Customer> getAll() throws Exception;
Optional<Customer> getById(int id) throws Exception;
boolean add(Customer customer) throws Exception;
boolean update(Customer customer) throws Exception;
boolean delete(Customer customer) throws Exception;
}
public class InMemoryCustomerDao implements CustomerDao {
private final Map<Integer, Customer> idToCustomer = new HashMap<>();
// implement the interface using the map
}
@Slf4j
public class DbCustomerDao implements CustomerDao {
private final 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.
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:
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
Applicability
Use the Data Access Object in any of the following situations:
- 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
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: Often used in conjunction with the DAO pattern to define application's boundaries and its set of available operations.
- Factory: Can be used to instantiate DAOs dynamically, providing flexibility in the choice of implementation.
- Abstract Factory: Helps in abstracting the creation of DAOs, especially when supporting multiple databases or storage mechanisms.
- Strategy: Might be employed to change the data access strategy at runtime, depending on the context.