diff --git a/dao/etc/dao.png b/dao/etc/dao.png index 9fe34b976..452e72ba1 100644 Binary files a/dao/etc/dao.png and b/dao/etc/dao.png differ diff --git a/dao/etc/dao.ucls b/dao/etc/dao.ucls index bf11f18b3..0706837fc 100644 --- a/dao/etc/dao.ucls +++ b/dao/etc/dao.ucls @@ -1,45 +1,71 @@ - - + + - - + + - - - - - - - - - + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + diff --git a/dao/pom.xml b/dao/pom.xml index 05ab2b22a..44c215f22 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -47,6 +47,7 @@ com.h2database h2 + compile de.bechte.junit diff --git a/dao/src/main/java/com/iluwatar/dao/App.java b/dao/src/main/java/com/iluwatar/dao/App.java index 146fddcb0..334c2b9bd 100644 --- a/dao/src/main/java/com/iluwatar/dao/App.java +++ b/dao/src/main/java/com/iluwatar/dao/App.java @@ -20,31 +20,39 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; +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. - *

- * With the DAO pattern, we can use various method calls to retrieve/add/delete/update data without - * directly interacting with the data. The below example demonstrates basic CRUD operations: select, - * add, update, and delete. + * + *

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:customerdb"; private static Logger log = Logger.getLogger(App.class); - + /** * Program entry point. * @@ -52,17 +60,54 @@ public class App { * @throws Exception if any error occurs. */ public static void main(final String[] args) throws Exception { - final CustomerDao customerDao = new InMemoryCustomerDao(); + 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("DROP TABLE CUSTOMERS"); + } + } + + private static void createSchema(DataSource dataSource) throws SQLException { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement()) { + statement.execute("CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " + + "LNAME VARCHAR(100))"); + } + } + + 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(): " + customerDao.getAll()); - log.info("customerDao.getCusterById(2): " + customerDao.getById(2)); + log.info("customerDao.getAllCustomers(): "); + try (Stream 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(): " + customerDao.getAll()); + log.info("customerDao.getAllCustomers(): "); + try (Stream customerStream = customerDao.getAll()) { + customerStream.forEach((cust) -> log.info(cust)); + } customerDao.delete(customer); log.info("customerDao.getAllCustomers(): " + customerDao.getAll()); } diff --git a/dao/src/main/java/com/iluwatar/dao/Customer.java b/dao/src/main/java/com/iluwatar/dao/Customer.java index 6d10fb146..d6b512dd6 100644 --- a/dao/src/main/java/com/iluwatar/dao/Customer.java +++ b/dao/src/main/java/com/iluwatar/dao/Customer.java @@ -20,11 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; /** - * - * Customer + * A customer POJO that represents the data that will be read from the data source. * */ public class Customer { @@ -34,7 +34,7 @@ public class Customer { private String lastName; /** - * Constructor + * Creates an instance of customer. */ public Customer(final int id, final String firstName, final String lastName) { this.id = id; @@ -73,12 +73,12 @@ public class Customer { } @Override - public boolean equals(final Object o) { + public boolean equals(final Object that) { boolean isEqual = false; - if (this == o) { + if (this == that) { isEqual = true; - } else if (o != null && getClass() == o.getClass()) { - final Customer customer = (Customer) o; + } else if (that != null && getClass() == that.getClass()) { + final Customer customer = (Customer) that; if (getId() == customer.getId()) { isEqual = true; } diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerDao.java b/dao/src/main/java/com/iluwatar/dao/CustomerDao.java index 545e46a5e..5d6aa38f5 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/CustomerDao.java @@ -20,12 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; 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 @@ -33,25 +33,25 @@ import java.util.stream.Stream; * 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. - *

- * 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. + * + *

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 + * @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. + * @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 getAll() throws Exception; /** * @param id unique identifier of the customer. - * @return customer with unique identifier id is found, null otherwise. + * @return customer with unique identifier id if found, null otherwise. * @throws Exception if any error occurs. */ Customer getById(int id) throws Exception; diff --git a/dao/src/main/java/com/iluwatar/dao/DBCustomerDao.java b/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java similarity index 71% rename from dao/src/main/java/com/iluwatar/dao/DBCustomerDao.java rename to dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java index 950ecb1b3..91279b99e 100644 --- a/dao/src/main/java/com/iluwatar/dao/DBCustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; import java.sql.Connection; @@ -35,27 +36,39 @@ import java.util.stream.StreamSupport; import javax.sql.DataSource; /** - * + * An implementation of {@link CustomerDao} that persists customers in RDBMS. * */ -public class DBCustomerDao implements CustomerDao { +public class DbCustomerDao implements CustomerDao { private final DataSource dataSource; - public DBCustomerDao(DataSource dataSource) { + /** + * Creates an instance of {@link DbCustomerDao} which uses provided dataSource + * 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 getAll() throws Exception { - + Connection connection; try { connection = getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); ResultSet resultSet = statement.executeQuery(); - return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { - + return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, + Spliterator.ORDERED) { + @Override public boolean tryAdvance(Consumer action) { try { @@ -65,15 +78,15 @@ public class DBCustomerDao implements CustomerDao { action.accept(createCustomer(resultSet)); return true; } catch (SQLException e) { - e.printStackTrace(); - return false; + throw new RuntimeException(e); } - }}, false).onClose(() -> mutedClose(connection)); + } + }, false).onClose(() -> mutedClose(connection)); } catch (SQLException e) { throw new Exception(e.getMessage(), e); } } - + private Connection getConnection() throws SQLException { return dataSource.getConnection(); } @@ -91,31 +104,40 @@ public class DBCustomerDao implements CustomerDao { resultSet.getString("FNAME"), resultSet.getString("LNAME")); } - + + /** + * {@inheritDoc} + */ @Override public Customer getById(int id) throws Exception { try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { - statement.setInt(1, id); - ResultSet resultSet = statement.executeQuery(); - if (resultSet.next()) { - return createCustomer(resultSet); - } else { - return null; - } + PreparedStatement statement = + connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { + + statement.setInt(1, id); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return createCustomer(resultSet); + } else { + return null; + } } catch (SQLException ex) { throw new Exception(ex.getMessage(), ex); } } + /** + * {@inheritDoc} + */ @Override public boolean add(Customer customer) throws Exception { if (getById(customer.getId()) != null) { return false; } - + try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { + PreparedStatement statement = + connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { statement.setInt(1, customer.getId()); statement.setString(2, customer.getFirstName()); statement.setString(3, customer.getLastName()); @@ -126,10 +148,14 @@ public class DBCustomerDao implements CustomerDao { } } + /** + * {@inheritDoc} + */ @Override public boolean update(Customer customer) throws Exception { try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { + 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()); @@ -139,10 +165,14 @@ public class DBCustomerDao implements CustomerDao { } } + /** + * {@inheritDoc} + */ @Override public boolean delete(Customer customer) throws Exception { try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { + PreparedStatement statement = + connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { statement.setInt(1, customer.getId()); return statement.executeUpdate() > 0; } catch (SQLException ex) { diff --git a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java index 62276d5c5..63576b99a 100644 --- a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; import java.util.HashMap; diff --git a/dao/src/test/java/com/iluwatar/dao/AppTest.java b/dao/src/test/java/com/iluwatar/dao/AppTest.java index 08babc62a..169fc046e 100644 --- a/dao/src/test/java/com/iluwatar/dao/AppTest.java +++ b/dao/src/test/java/com/iluwatar/dao/AppTest.java @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; import org.junit.Test; diff --git a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java b/dao/src/test/java/com/iluwatar/dao/CustomerTest.java index 600b5ba3f..6a02fd6be 100644 --- a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java +++ b/dao/src/test/java/com/iluwatar/dao/CustomerTest.java @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; import static org.junit.Assert.assertEquals; diff --git a/dao/src/test/java/com/iluwatar/dao/DBCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java similarity index 84% rename from dao/src/test/java/com/iluwatar/dao/DBCustomerDaoTest.java rename to dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java index 243d9b3ac..470b33557 100644 --- a/dao/src/test/java/com/iluwatar/dao/DBCustomerDaoTest.java +++ b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java @@ -28,33 +28,51 @@ import org.mockito.Mockito; import de.bechte.junit.runners.context.HierarchicalContextRunner; +/** + * Tests {@link DbCustomerDao}. + */ @RunWith(HierarchicalContextRunner.class) -public class DBCustomerDaoTest { +public class DbCustomerDaoTest { private static final String DB_URL = "jdbc:h2:~/dao:customerdb"; - private DBCustomerDao dao; + private DbCustomerDao dao; private Customer existingCustomer = new Customer(1, "Freddy", "Krueger"); + /** + * Creates customers schema. + * @throws SQLException if there is any error while creating schema. + */ @Before public void createSchema() throws SQLException { try (Connection connection = DriverManager.getConnection(DB_URL); Statement statement = connection.createStatement()) { - statement.execute("CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), LNAME VARCHAR(100))"); + statement.execute("CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100)," + + " LNAME VARCHAR(100))"); } } + /** + * Represents the scenario where DB connectivity is present. + */ public class ConnectionSuccess { + /** + * Setup for connection success scenario. + * @throws Exception if any error occurs. + */ @Before public void setUp() throws Exception { JdbcDataSource dataSource = new JdbcDataSource(); dataSource.setURL(DB_URL); - dao = new DBCustomerDao(dataSource); + dao = new DbCustomerDao(dataSource); boolean result = dao.add(existingCustomer); assertTrue(result); } - public class NonExistantCustomer { + /** + * Represents the scenario when DAO operations are being performed on a non existing customer. + */ + public class NonExistingCustomer { @Test public void addingShouldResultInSuccess() throws Exception { @@ -97,6 +115,11 @@ public class DBCustomerDaoTest { } } + /** + * Represents a scenario where DAO operations are being performed on an already existing + * customer. + * + */ public class ExistingCustomer { @Test @@ -135,14 +158,23 @@ public class DBCustomerDaoTest { } } - public class DBConnectivityIssue { + /** + * Represents a scenario where DB connectivity is not present due to network issue, or + * DB service unavailable. + * + */ + public class ConnectivityIssue { private static final String EXCEPTION_CAUSE = "Connection not available"; @Rule public ExpectedException exception = ExpectedException.none(); + /** + * setup a connection failure scenario. + * @throws SQLException if any error occurs. + */ @Before public void setUp() throws SQLException { - dao = new DBCustomerDao(mockedDatasource()); + dao = new DbCustomerDao(mockedDatasource()); exception.expect(Exception.class); exception.expectMessage(EXCEPTION_CAUSE); } @@ -186,6 +218,10 @@ public class DBCustomerDaoTest { } + /** + * Delete customer schema for fresh setup per test. + * @throws SQLException if any error occurs. + */ @After public void deleteSchema() throws SQLException { try (Connection connection = DriverManager.getConnection(DB_URL); diff --git a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java index ca5180e97..49272728e 100644 --- a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java +++ b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; import static org.junit.Assert.assertEquals; @@ -36,6 +37,9 @@ import org.junit.runner.RunWith; import de.bechte.junit.runners.context.HierarchicalContextRunner; +/** + * Tests {@link InMemoryCustomerDao}. + */ @RunWith(HierarchicalContextRunner.class) public class InMemoryCustomerDaoTest { @@ -47,8 +51,12 @@ public class InMemoryCustomerDaoTest { dao = new InMemoryCustomerDao(); dao.add(CUSTOMER); } - - public class NonExistantCustomer { + + /** + * Represents the scenario when the DAO operations are being performed on a non existent + * customer. + */ + public class NonExistingCustomer { @Test public void addingShouldResultInSuccess() throws Exception { @@ -91,6 +99,10 @@ public class InMemoryCustomerDaoTest { } } + /** + * Represents the scenario when the DAO operations are being performed on an already existing + * customer. + */ public class ExistingCustomer { @Test