---
layout: pattern
title: Data Access Object
folder: dao
permalink: /patterns/dao/
categories: Architectural
tags:
 - Data access
---

## Intent
Object provides an abstract interface to some type of database or other persistence mechanism.

## 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.

```java
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;
  }

  public int getId() {
    return id;
  }

  public void setId(final int id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(final String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(final String lastName) {
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return "Customer{" + "id=" + getId() + ", firstName='" + getFirstName() + '\'' + ", lastName='"
        + getLastName() + '\'' + '}';
  }

  @Override
  public boolean equals(final Object that) {
    var isEqual = false;
    if (this == that) {
      isEqual = true;
    } else if (that != null && getClass() == that.getClass()) {
      final var customer = (Customer) that;
      if (getId() == customer.getId()) {
        isEqual = true;
      }
    }
    return isEqual;
  }

  @Override
  public int hashCode() {
    return getId();
  }
}
```

Here's the DAO 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;

  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<>();

  @Override
  public Stream<Customer> getAll() {
    return idToCustomer.values().stream();
  }

  @Override
  public Optional<Customer> getById(final int id) {
    return Optional.ofNullable(idToCustomer.get(id));
  }

  @Override
  public boolean add(final Customer customer) {
    if (getById(customer.getId()).isPresent()) {
      return false;
    }

    idToCustomer.put(customer.getId(), customer);
    return true;
  }

  @Override
  public boolean update(final Customer customer) {
    return idToCustomer.replace(customer.getId(), customer) != null;
  }

  @Override
  public boolean delete(final Customer customer) {
    return idToCustomer.remove(customer.getId()) != null;
  }
}

public class DbCustomerDao implements CustomerDao {

  private static final Logger LOGGER = LoggerFactory.getLogger(DbCustomerDao.class);

  private final DataSource dataSource;

  public DbCustomerDao(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  @Override
  public Stream<Customer> getAll() throws Exception {
    try {
      var connection = getConnection();
      var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS");
      var resultSet = statement.executeQuery();
      return StreamSupport.stream(new Spliterators.AbstractSpliterator<Customer>(Long.MAX_VALUE,
          Spliterator.ORDERED) {

        @Override
        public boolean tryAdvance(Consumer<? super Customer> action) {
          try {
            if (!resultSet.next()) {
              return false;
            }
            action.accept(createCustomer(resultSet));
            return true;
          } catch (SQLException e) {
            throw new RuntimeException(e);
          }
        }
      }, false).onClose(() -> mutedClose(connection, statement, resultSet));
    } catch (SQLException e) {
      throw new CustomException(e.getMessage(), e);
    }
  }

  private Connection getConnection() throws SQLException {
    return dataSource.getConnection();
  }

  private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet) {
    try {
      resultSet.close();
      statement.close();
      connection.close();
    } catch (SQLException e) {
      LOGGER.info("Exception thrown " + e.getMessage());
    }
  }

  private Customer createCustomer(ResultSet resultSet) throws SQLException {
    return new Customer(resultSet.getInt("ID"),
        resultSet.getString("FNAME"),
        resultSet.getString("LNAME"));
  }

  @Override
  public Optional<Customer> getById(int id) throws Exception {

    ResultSet resultSet = null;

    try (var connection = getConnection();
         var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) {

      statement.setInt(1, id);
      resultSet = statement.executeQuery();
      if (resultSet.next()) {
        return Optional.of(createCustomer(resultSet));
      } else {
        return Optional.empty();
      }
    } catch (SQLException ex) {
      throw new CustomException(ex.getMessage(), ex);
    } finally {
      if (resultSet != null) {
        resultSet.close();
      }
    }
  }

  @Override
  public boolean add(Customer customer) throws Exception {
    if (getById(customer.getId()).isPresent()) {
      return false;
    }

    try (var connection = getConnection();
         var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) {
      statement.setInt(1, customer.getId());
      statement.setString(2, customer.getFirstName());
      statement.setString(3, customer.getLastName());
      statement.execute();
      return true;
    } catch (SQLException ex) {
      throw new CustomException(ex.getMessage(), ex);
    }
  }

  @Override
  public boolean update(Customer customer) throws Exception {
    try (var connection = getConnection();
         var statement =
             connection
                 .prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) {
      statement.setString(1, customer.getFirstName());
      statement.setString(2, customer.getLastName());
      statement.setInt(3, customer.getId());
      return statement.executeUpdate() > 0;
    } catch (SQLException ex) {
      throw new CustomException(ex.getMessage(), ex);
    }
  }

  @Override
  public boolean delete(Customer customer) throws Exception {
    try (var connection = getConnection();
         var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) {
      statement.setInt(1, customer.getId());
      return statement.executeUpdate() > 0;
    } catch (SQLException ex) {
      throw new CustomException(ex.getMessage(), ex);
    }
  }
}
```

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);
```


## Class diagram
![alt text](./etc/dao.png "Data Access Object")

## Applicability
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

## 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)
