From 76fb9aff8b44ae3da01a36b0b200691be0adc755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sat, 18 Jul 2020 10:22:43 +0300 Subject: [PATCH] #590 add explanation for Dao --- dao/README.md | 294 +++++++++++++++++- .../main/java/com/iluwatar/dao/Customer.java | 2 +- 2 files changed, 293 insertions(+), 3 deletions(-) diff --git a/dao/README.md b/dao/README.md index 3fe1ec20e..4b65679c4 100644 --- a/dao/README.md +++ b/dao/README.md @@ -9,8 +9,298 @@ tags: --- ## Intent -Object provides an abstract interface to some type of database or -other persistence mechanism. +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 getAll() throws Exception; + + Optional 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 Map idToCustomer = new HashMap<>(); + + @Override + public Stream getAll() { + return idToCustomer.values().stream(); + } + + @Override + public Optional 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 getAll() throws Exception { + try { + var connection = getConnection(); + var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); + var resultSet = statement.executeQuery(); + return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, + Spliterator.ORDERED) { + + @Override + public boolean tryAdvance(Consumer 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 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") diff --git a/dao/src/main/java/com/iluwatar/dao/Customer.java b/dao/src/main/java/com/iluwatar/dao/Customer.java index 43649989b..2630c9252 100644 --- a/dao/src/main/java/com/iluwatar/dao/Customer.java +++ b/dao/src/main/java/com/iluwatar/dao/Customer.java @@ -35,7 +35,7 @@ public class Customer { /** * Creates an instance of customer. */ - public Customer(final int id, final String firstName, final String lastName) { + public Customer(int id, String firstName, String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName;