📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
Video Tutorial
Table of contents |
---|
Problem |
Forces |
Solution |
Structure - Class Diagram, Sequence Diagram |
Participants and Responsibilities |
Implementation |
Consequences |
Applicability |
References |
Problem
Forces
- You want to implement data access mechanisms to access and manipulate data in a persistent storage.
- You want to decouple the persistent storage implementation from the rest of your application.
- You want to provide a uniform data access API for a persistent mechanism to various types of data sources, such as RDBMS, LDAP, OODB, XML repositories, flat files, and so on.
- You want to organize data access logic and encapsulate proprietary features to facilitate maintainability and portability.
Solution
Benefits of using DAO Design Pattern
- Data Access Object or DAO design pattern is a way to reduce coupling between Business logic and Persistence logic.
- DAO design pattern allows JUnit test to run faster as it allows to create Mock and avoid connecting to a database to run tests. It improves testing because it's easy to write test with Mock objects, rather than an Integration test with the database. In the case of any issue, while running Unit test, you only need to check code and not database. Also shields with database connectivity and environment issues.
- Since DAO pattern is based on interface, it also promotes Object oriented design principle "programming for interface than implementation" which results in flexible and quality code.
Structure
Class Diagram
Sequence Diagram
Participants and Responsibilities
Implementation
**
*
A customer POJO that represents the data that will be read from the data source.*
*
/
public class Customer {
private int id;
private String firstName;
private String lastName;
/**
* Creates an instance of customer.
*/
public Customer(final int id, final String firstName, final 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) {
boolean isEqual = false;
if (this == that) {
isEqual = true;
} else if (that != null && getClass() == that.getClass()) {
final Customer customer = (Customer) that;
if (getId() == customer.getId()) {
isEqual = true;
}
}
return isEqual;
}
@Override
public int hashCode() {
return getId();
}
}
import java.util.Optional;
import java.util.stream.Stream;
/**
* In an application the Data Access Object (DAO) is a part of Data access layer. It is an object
* that provides an interface to some type of persistence mechanism. By mapping application calls
* to the persistence layer, DAO provides some specific data operations without exposing details
* of the database. This isolation supports the Single responsibility principle. It separates what
* data accesses the application needs, in terms of domain-specific objects and data types
* (the public interface of the DAO), from how these needs can be satisfied with a specific DBMS,
* database schema, etc.
*
* <p>Any change in the way data is stored and retrieved will not change the client code as the
* client will be using interface and need not worry about exact source.
*
* @see InMemoryCustomerDao
* @see DbCustomerDao
*/
public interface CustomerDao {
/**
* @return all the customers as a stream. The stream may be lazily or eagerly evaluated based
* on the implementation. The stream must be closed after use.
* @throws Exception if any error occurs.
*/
Stream < Customer > getAll() throws Exception;
/**
* @param id unique identifier of the customer.
* @return an optional with customer if a customer with unique identifier <code>id</code>
* exists, empty optional otherwise.
* @throws Exception if any error occurs.
*/
Optional < Customer > getById(int id) throws Exception;
/**
* @param customer the customer to be added.
* @return true if customer is successfully added, false if customer already exists.
* @throws Exception if any error occurs.
*/
boolean add(Customer customer) throws Exception;
/**
* @param customer the customer to be updated.
* @return true if customer exists and is successfully updated, false otherwise.
* @throws Exception if any error occurs.
*/
boolean update(Customer customer) throws Exception;
/**
* @param customer the customer to be deleted.
* @return true if customer exists and is successfully deleted, false otherwise.
* @throws Exception if any error occurs.
*/
boolean delete(Customer customer) throws Exception;
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
/**
* An implementation of {@link CustomerDao} that persists customers in RDBMS.
*
*/
public class DbCustomerDao implements CustomerDao {
private static final Logger LOGGER = Logger.getLogger(DbCustomerDao.class);
private final DataSource dataSource;
/**
* Creates an instance of {@link DbCustomerDao} which uses provided <code>dataSource</code>
* to store and retrieve customer information.
*
* @param dataSource a non-null dataSource.
*/
public DbCustomerDao(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* @return a lazily populated stream of customers. Note the stream returned must be closed to
* free all the acquired resources. The stream keeps an open connection to the database till
* it is complete or is closed manually.
*/
@Override
public Stream < Customer > getAll() throws Exception {
Connection connection;
try {
connection = getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); // NOSONAR
ResultSet resultSet = statement.executeQuery(); // NOSONAR
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); // NOSONAR
}
}
}, 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"));
}
/**
* {@inheritDoc}
*/
@Override
public Optional < Customer > getById(int id) throws Exception {
ResultSet resultSet = null;
try (Connection connection = getConnection(); PreparedStatement 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();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean add(Customer customer) throws Exception {
if (getById(customer.getId()).isPresent()) {
return false;
}
try (Connection connection = getConnection(); PreparedStatement 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);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean update(Customer customer) throws Exception {
try (Connection connection = getConnection(); PreparedStatement 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);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean delete(Customer customer) throws Exception {
try (Connection connection = getConnection(); PreparedStatement 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);
}
}
}
package com.iluwatar.dao;
/**
* Customer Schema SQL Class
*/
public final class CustomerSchemaSql {
private CustomerSchemaSql() {}
public static final String CREATE_SCHEMA_SQL = "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " +
"LNAME VARCHAR(100))";
public static final String DELETE_SCHEMA_SQL = "DROP TABLE CUSTOMERS";
}
/**
*
* Custom exception
*
*/
public class CustomException extends Exception {
private static final long serialVersionUID = 1 L;
public CustomException() {}
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause) {
super(message, cause);
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
* An in memory implementation of {@link CustomerDao}, which stores the customers in JVM memory
* and data is lost when the application exits.
* <br/>
* This implementation is useful as temporary database or for testing.
*/
public class InMemoryCustomerDao implements CustomerDao {
private Map < Integer, Customer > idToCustomer = new HashMap < > ();
/**
* An eagerly evaluated stream of customers stored in memory.
*/
@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;
}
}
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.h2.jdbcx.JdbcDataSource;
/**
* Data Access Object (DAO) is an object that provides an abstract interface to some type of
* database or other persistence mechanism. By mapping application calls to the persistence layer,
* DAO provide some specific data operations without exposing details of the database. This
* isolation supports the Single responsibility principle. It separates what data accesses the
* application needs, in terms of domain-specific objects and data types (the public interface of
* the DAO), from how these needs can be satisfied with a specific DBMS.
*
* <p>With the DAO pattern, we can use various method calls to retrieve/add/delete/update data
* without directly interacting with the data source. The below example demonstrates basic CRUD
* operations: select, add, update, and delete.
*
*
*/
public class App {
private static final String DB_URL = "jdbc:h2:~/dao";
private static Logger log = Logger.getLogger(App.class);
/**
* Program entry point.
*
* @param args command line args.
* @throws Exception if any error occurs.
*/
public static void main(final String[] args) throws Exception {
final CustomerDao inMemoryDao = new InMemoryCustomerDao();
performOperationsUsing(inMemoryDao);
final DataSource dataSource = createDataSource();
createSchema(dataSource);
final CustomerDao dbDao = new DbCustomerDao(dataSource);
performOperationsUsing(dbDao);
deleteSchema(dataSource);
}
private static void deleteSchema(DataSource dataSource) throws SQLException {
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL);
}
}
private static void createSchema(DataSource dataSource) throws SQLException {
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL);
}
}
private static DataSource createDataSource() {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
return dataSource;
}
private static void performOperationsUsing(final CustomerDao customerDao) throws Exception {
addCustomers(customerDao);
log.info("customerDao.getAllCustomers(): ");
try (Stream < Customer > customerStream = customerDao.getAll()) {
customerStream.forEach((customer) - > log.info(customer));
}
log.info("customerDao.getCustomerById(2): " + customerDao.getById(2));
final Customer customer = new Customer(4, "Dan", "Danson");
customerDao.add(customer);
log.info("customerDao.getAllCustomers(): " + customerDao.getAll());
customer.setFirstName("Daniel");
customer.setLastName("Danielson");
customerDao.update(customer);
log.info("customerDao.getAllCustomers(): ");
try (Stream < Customer > customerStream = customerDao.getAll()) {
customerStream.forEach((cust) - > log.info(cust));
}
customerDao.delete(customer);
log.info("customerDao.getAllCustomers(): " + customerDao.getAll());
}
private static void addCustomers(CustomerDao customerDao) throws Exception {
for (Customer customer: generateSampleCustomers()) {
customerDao.add(customer);
}
}
/**
* Generate customers.
*
* @return list of customers.
*/
public static List < Customer > generateSampleCustomers() {
final Customer customer1 = new Customer(1, "Adam", "Adamson");
final Customer customer2 = new Customer(2, "Bob", "Bobson");
final Customer customer3 = new Customer(3, "Carl", "Carlson");
final List < Customer > customers = new ArrayList < > ();
customers.add(customer1);
customers.add(customer2);
customers.add(customer3);
return customers;
}
}
Applicability
- when you want to consolidate how the data layer is accessed.
- when you want to avoid writing multiple data retrieval/persistence layers.
Comments
Post a Comment
Leave Comment