Skip to main content

Data Access Object

StructuralAbstractionData accessData processingDecouplingLayered architecturePersistenceAbout 4 min

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

Imagine a library system where the main application manages book loans, user accounts, and inventory. The Data Access Object (DAO) pattern in this context would be used to separate the database operations (such as fetching book details, updating user records, and checking inventory) from the business logic of managing loans and accounts. For instance, there would be a BookDAO class responsible for all database interactions related to books, such as retrieving a book by its ISBN or updating its availability status. This abstraction allows the library system's main application code to focus on business rules and workflows, while the BookDAO handles the complex SQL queries and data management. This separation makes the system easier to maintain and test, as changes to the data source or business logic can be managed independently.

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

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.

Walking through our customers example, here's the basic Customer entity.

@Setter
@Getter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor
public class Customer {

    @EqualsAndHashCode.Include
    private int id;
    private String firstName;
    private String 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
@RequiredArgsConstructor
public class DbCustomerDao implements CustomerDao {

  private final DataSource dataSource;

  // implement the interface using the data source
}

Finally, here's how we use our DAO to manage customers.


@Slf4j
public class App {
    private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1";
    private static final String ALL_CUSTOMERS = "customerDao.getAllCustomers(): ";

    public static void main(final String[] args) throws Exception {
        final var inMemoryDao = new InMemoryCustomerDao();
        performOperationsUsing(inMemoryDao);

        final var dataSource = createDataSource();
        createSchema(dataSource);
        final var dbDao = new DbCustomerDao(dataSource);
        performOperationsUsing(dbDao);
        deleteSchema(dataSource);
    }

    private static void deleteSchema(DataSource dataSource) throws SQLException {
        try (var connection = dataSource.getConnection();
             var statement = connection.createStatement()) {
            statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL);
        }
    }

    private static void createSchema(DataSource dataSource) throws SQLException {
        try (var connection = dataSource.getConnection();
             var statement = connection.createStatement()) {
            statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL);
        }
    }

    private static DataSource createDataSource() {
        var dataSource = new JdbcDataSource();
        dataSource.setURL(DB_URL);
        return dataSource;
    }

    private static void performOperationsUsing(final CustomerDao customerDao) throws Exception {
        addCustomers(customerDao);
        LOGGER.info(ALL_CUSTOMERS);
        try (var customerStream = customerDao.getAll()) {
            customerStream.forEach(customer -> LOGGER.info(customer.toString()));
        }
        LOGGER.info("customerDao.getCustomerById(2): " + customerDao.getById(2));
        final var customer = new Customer(4, "Dan", "Danson");
        customerDao.add(customer);
        LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
        customer.setFirstName("Daniel");
        customer.setLastName("Danielson");
        customerDao.update(customer);
        LOGGER.info(ALL_CUSTOMERS);
        try (var customerStream = customerDao.getAll()) {
            customerStream.forEach(cust -> LOGGER.info(cust.toString()));
        }
        customerDao.delete(customer);
        LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
    }

    private static void addCustomers(CustomerDao customerDao) throws Exception {
        for (var customer : generateSampleCustomers()) {
            customerDao.add(customer);
        }
    }

    public static List<Customer> generateSampleCustomers() {
        final var customer1 = new Customer(1, "Adam", "Adamson");
        final var customer2 = new Customer(2, "Bob", "Bobson");
        final var customer3 = new Customer(3, "Carl", "Carlson");
        return List.of(customer1, customer2, customer3);
    }
}

The program output:

10:02:09.788 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): 
10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getCustomerById(2): Optional[Customer(id=2, firstName=Bob, lastName=Bobson)]
10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@4c3e4790
10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): 
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=4, firstName=Daniel, lastName=Danielson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@5679c6c6
10:02:09.894 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): 
10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.895 [main] INFO com.iluwatar.dao.App -- customerDao.getCustomerById(2): Optional[Customer(id=2, firstName=Bob, lastName=Bobson)]
10:02:09.896 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@23282c25
10:02:09.897 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): 
10:02:09.897 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.897 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.898 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.898 [main] INFO com.iluwatar.dao.App -- Customer(id=4, firstName=Daniel, lastName=Danielson)
10:02:09.898 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@f2f2cc1

Class diagram

Data Access Object
Data Access Object

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.

Credits