317 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
---
 | 
						|
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
 | 
						|

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