refactor: rename and delete some patterns (#2970)

This commit is contained in:
Ilkka Seppälä
2024-05-23 17:57:46 +03:00
committed by GitHub
parent 399970f8d1
commit 962e8898e0
94 changed files with 972 additions and 3619 deletions
+201
View File
@@ -0,0 +1,201 @@
---
title: Data Access Object
category: Structural
language: en
tag:
- Data access
- Layered architecture
- Persistence
---
## Also known as
* Data Access Layer
* DAO
## Intent
The Data Access Object (DAO) design pattern aims to separate the application's business logic from the persistence layer, typically a database or any other storage mechanism. By using DAOs, the application can access and manipulate data without being dependent on the specific database implementation details.
## 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;
}
}
```
Here's the `CustomerDao` 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<>();
// implement the interface using the map
}
@Slf4j
public class DbCustomerDao implements CustomerDao {
private final DataSource
dataSource;
public DbCustomerDao(
DataSource dataSource) {
this.dataSource =
dataSource;
}
// implement the interface using the data source
}
```
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);
```
The program output:
```java
customerDao.getAllCustomers():
Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3,firstName='Carl',lastName='Carlson'}
customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}]
customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@7cef4e59
customerDao.getAllCustomers():
Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3,firstName='Carl',lastName='Carlson'}
Customer{id=4,firstName='Daniel',lastName='Danielson'}
customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@2db0f6b2
customerDao.getAllCustomers():
Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3,firstName='Carl',lastName='Carlson'}
customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}]
customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@12c8a2c0
customerDao.getAllCustomers():
Customer{id=1,firstName='Adam',lastName='Adamson'}
Customer{id=2,firstName='Bob',lastName='Bobson'}
Customer{id=3,firstName='Carl',lastName='Carlson'}
Customer{id=4,firstName='Daniel',lastName='Danielson'}
customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@6ec8211c
```
## Class diagram
![alt text](./etc/dao.png "Data Access Object")
## Applicability
Use the Data Access Object in any of the following situations:
* There is a need to abstract and encapsulate all access to the data source.
* The application needs to support multiple types of databases or storage mechanisms without significant code changes.
* You want to keep the database access clean and simple, and separate from business logic.
## Tutorials
* [The DAO Pattern in Java](https://www.baeldung.com/java-dao-pattern)
* [Data Access Object Pattern](https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm)
## Known Uses
* Enterprise applications that require database interaction.
* Applications requiring data access to be adaptable to multiple storage types (relational databases, XML files, flat files, etc.).
* Frameworks providing generic data access functionalities.
## Consequences
Benefits:
* Decoupling: Separates the data access logic from the business logic, enhancing modularity and clarity.
* Reusability: DAOs can be reused across different parts of the application or even in different projects.
* Testability: Simplifies testing by allowing business logic to be tested separately from the data access logic.
* Flexibility: Makes it easier to switch underlying storage mechanisms with minimal impact on the application code.
Trade-offs:
* Layer Complexity: Introduces additional layers to the application, which can increase complexity and development time.
* Overhead: For simple applications, the DAO pattern might introduce more overhead than necessary.
* Learning Curve: Developers might need time to understand and implement the pattern effectively, especially in complex projects.
## Related Patterns
* [Service Layer](https://java-design-patterns.com/patterns/service-layer/): Often used in conjunction with the DAO pattern to define application's boundaries and its set of available operations.
* [Factory](https://java-design-patterns.com/patterns/factory/): Can be used to instantiate DAOs dynamically, providing flexibility in the choice of implementation.
* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Helps in abstracting the creation of DAOs, especially when supporting multiple databases or storage mechanisms.
* [Strategy](https://java-design-patterns.com/patterns/strategy/): Might be employed to change the data access strategy at runtime, depending on the context.
## 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)
* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/49u3r91)
* [Patterns of Enterprise Application Architecture](https://amzn.to/3U5cxEI)
* [Expert One-on-One J2EE Design and Development](https://amzn.to/3vK3pfq)
* [Professional Java Development with the Spring Framework](https://amzn.to/49tANF0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+75
View File
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true">
<class id="1" language="java" name="com.iluwatar.dao.App" project="dao"
file="/dao/src/main/java/com/iluwatar/dao/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="910" y="191"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="false" static="true"/>
<operations public="true" package="true" protected="true" private="false" static="true"/>
</display>
</class>
<interface id="2" language="java" name="com.iluwatar.dao.CustomerDao" project="dao"
file="/dao/src/main/java/com/iluwatar/dao/CustomerDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="536" y="187"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="3" language="java" name="com.iluwatar.dao.InMemoryCustomerDao" project="dao"
file="/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="289" y="455"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="false" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.dao.DBCustomerDao" project="dao"
file="/dao/src/main/java/com/iluwatar/dao/DBCustomerDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="751" y="456"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="false" static="true"/>
<operations public="true" package="true" protected="true" private="false" static="true"/>
</display>
</class>
<interface id="5" language="java" name="javax.sql.DataSource" project="dao"
file="/opt/Softwares/Eclipses/MARS/eclipse/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1083" y="459"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<association id="6">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="7" name="dataSource"/>
<multiplicity id="8" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="5" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="9">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="2"/>
</realization>
<dependency id="10">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="2"/>
</dependency>
<realization id="11">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="2"/>
</realization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>
+68
View File
@@ -0,0 +1,68 @@
@startuml
package com.iluwatar.dao {
class App {
- ALL_CUSTOMERS : String {static}
- DB_URL : String {static}
- log : Logger {static}
+ App()
- addCustomers(customerDao : CustomerDao) {static}
- createDataSource() : DataSource {static}
- createSchema(dataSource : DataSource) {static}
- deleteSchema(dataSource : DataSource) {static}
+ generateSampleCustomers() : List<Customer> {static}
+ main(args : String[]) {static}
- performOperationsUsing(customerDao : CustomerDao) {static}
}
class Customer {
- firstName : String
- id : int
- lastName : String
+ Customer(id : int, firstName : String, lastName : String)
+ equals(that : Object) : boolean
+ getFirstName() : String
+ getId() : int
+ getLastName() : String
+ hashCode() : int
+ setFirstName(firstName : String)
+ setId(id : int)
+ setLastName(lastName : String)
+ toString() : String
}
interface CustomerDao {
+ add(Customer) : boolean {abstract}
+ delete(Customer) : boolean {abstract}
+ getAll() : Stream<Customer> {abstract}
+ getById(int) : Optional<Customer> {abstract}
+ update(Customer) : boolean {abstract}
}
class CustomerSchemaSql {
+ CREATE_SCHEMA_SQL : String {static}
+ DELETE_SCHEMA_SQL : String {static}
- CustomerSchemaSql()
}
class DbCustomerDao {
- LOGGER : Logger {static}
- dataSource : DataSource
+ DbCustomerDao(dataSource : DataSource)
+ add(customer : Customer) : boolean
- createCustomer(resultSet : ResultSet) : Customer
+ delete(customer : Customer) : boolean
+ getAll() : Stream<Customer>
+ getById(id : int) : Optional<Customer>
- getConnection() : Connection
- mutedClose(connection : Connection, statement : PreparedStatement, resultSet : ResultSet)
+ update(customer : Customer) : boolean
}
class InMemoryCustomerDao {
- idToCustomer : Map<Integer, Customer>
+ InMemoryCustomerDao()
+ add(customer : Customer) : boolean
+ delete(customer : Customer) : boolean
+ getAll() : Stream<Customer>
+ getById(id : int) : Optional<Customer>
+ update(customer : Customer) : boolean
}
}
DbCustomerDao ..|> CustomerDao
InMemoryCustomerDao ..|> CustomerDao
@enduml
@@ -0,0 +1,69 @@
@startuml
package com.iluwatar.dao {
class App {
- ALL_CUSTOMERS : String {static}
- DB_URL : String {static}
- LOGGER : Logger {static}
+ App()
- addCustomers(customerDao : CustomerDao) {static}
- createDataSource() : DataSource {static}
- createSchema(dataSource : DataSource) {static}
- deleteSchema(dataSource : DataSource) {static}
+ generateSampleCustomers() : List<Customer> {static}
+ main(args : String[]) {static}
- performOperationsUsing(customerDao : CustomerDao) {static}
}
class Customer {
- firstName : String
- id : int
- lastName : String
+ Customer(id : int, firstName : String, lastName : String)
# canEqual(other : Object) : boolean
+ equals(o : Object) : boolean
+ getFirstName() : String
+ getId() : int
+ getLastName() : String
+ hashCode() : int
+ setFirstName(firstName : String)
+ setId(id : int)
+ setLastName(lastName : String)
+ toString() : String
}
interface CustomerDao {
+ add(Customer) : boolean {abstract}
+ delete(Customer) : boolean {abstract}
+ getAll() : Stream<Customer> {abstract}
+ getById(int) : Optional<Customer> {abstract}
+ update(Customer) : boolean {abstract}
}
class CustomerSchemaSql {
+ CREATE_SCHEMA_SQL : String {static}
+ DELETE_SCHEMA_SQL : String {static}
- CustomerSchemaSql()
}
class DbCustomerDao {
- LOGGER : Logger {static}
- dataSource : DataSource
+ DbCustomerDao(dataSource : DataSource)
+ add(customer : Customer) : boolean
- createCustomer(resultSet : ResultSet) : Customer
+ delete(customer : Customer) : boolean
+ getAll() : Stream<Customer>
+ getById(id : int) : Optional<Customer>
- getConnection() : Connection
- mutedClose(connection : Connection, statement : PreparedStatement, resultSet : ResultSet)
+ update(customer : Customer) : boolean
}
class InMemoryCustomerDao {
- idToCustomer : Map<Integer, Customer>
+ InMemoryCustomerDao()
+ add(customer : Customer) : boolean
+ delete(customer : Customer) : boolean
+ getAll() : Stream<Customer>
+ getById(id : int) : Optional<Customer>
+ update(customer : Customer) : boolean
}
}
DbCustomerDao ..|> CustomerDao
InMemoryCustomerDao ..|> CustomerDao
@enduml
+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
The MIT License
Copyright © 2014-2022 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<artifactId>data-access-object</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.dao.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,125 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.dao;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
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.
*/
@Slf4j
public class App {
private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1";
private static final String ALL_CUSTOMERS = "customerDao.getAllCustomers(): ";
/**
* Program entry point.
*
* @param args command line args.
* @throws Exception if any error occurs.
*/
public static void main(final String[] args) throws Exception {
final var inMemoryDao = new InMemoryCustomerDao();
performOperationsUsing(inMemoryDao);
final var dataSource = createDataSource();
createSchema(dataSource);
final var dbDao = new DbCustomerDao(dataSource);
performOperationsUsing(dbDao);
deleteSchema(dataSource);
}
private static void deleteSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL);
}
}
private static void createSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL);
}
}
private static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
return dataSource;
}
private static void performOperationsUsing(final CustomerDao customerDao) throws Exception {
addCustomers(customerDao);
LOGGER.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) {
customerStream.forEach(customer -> LOGGER.info(customer.toString()));
}
LOGGER.info("customerDao.getCustomerById(2): " + customerDao.getById(2));
final var customer = new Customer(4, "Dan", "Danson");
customerDao.add(customer);
LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
customer.setFirstName("Daniel");
customer.setLastName("Danielson");
customerDao.update(customer);
LOGGER.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) {
customerStream.forEach(cust -> LOGGER.info(cust.toString()));
}
customerDao.delete(customer);
LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
}
private static void addCustomers(CustomerDao customerDao) throws Exception {
for (var customer : generateSampleCustomers()) {
customerDao.add(customer);
}
}
/**
* Generate customers.
*
* @return list of customers.
*/
public static List<Customer> generateSampleCustomers() {
final var customer1 = new Customer(1, "Adam", "Adamson");
final var customer2 = new Customer(2, "Bob", "Bobson");
final var customer3 = new Customer(3, "Carl", "Carlson");
return List.of(customer1, customer2, customer3);
}
}
@@ -0,0 +1,40 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.dao;
import java.io.Serial;
/**
* Custom exception.
*/
public class CustomException extends Exception {
@Serial
private static final long serialVersionUID = 1L;
public CustomException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,48 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.dao;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* A customer POJO that represents the data that will be read from the data source.
*/
@Setter
@Getter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor
public class Customer {
@EqualsAndHashCode.Include
private int id;
private String firstName;
private String lastName;
}
@@ -0,0 +1,92 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.dao;
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 {
/**
* Get all customers.
*
* @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;
/**
* Get customer as Optional by id.
*
* @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;
/**
* Add a customer.
*
* @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;
/**
* Update a customer.
*
* @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;
/**
* Delete a customer.
*
* @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;
}
@@ -0,0 +1,41 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
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";
}
@@ -0,0 +1,183 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* 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.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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* An implementation of {@link CustomerDao} that persists customers in RDBMS.
*/
@Slf4j
@RequiredArgsConstructor
public class DbCustomerDao implements CustomerDao {
private final DataSource dataSource;
/**
* Get all customers as Java Stream.
*
* @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 {
try {
var connection = getConnection();
var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); // NOSONAR
var 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 (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();
}
}
}
/**
* {@inheritDoc}
*/
@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);
}
}
/**
* {@inheritDoc}
*/
@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);
}
}
/**
* {@inheritDoc}
*/
@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);
}
}
}
@@ -0,0 +1,74 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* 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;
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 final 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;
}
}
@@ -0,0 +1,46 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.dao;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that DAO example runs without errors.
*/
class AppTest {
/**
* Issue: Add at least one assertion to this test case.
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test
void shouldExecuteDaoWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}
@@ -0,0 +1,95 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* 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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Tests {@link Customer}.
*/
class CustomerTest {
private Customer customer;
private static final int ID = 1;
private static final String FIRSTNAME = "Winston";
private static final String LASTNAME = "Churchill";
@BeforeEach
void setUp() {
customer = new Customer(ID, FIRSTNAME, LASTNAME);
}
@Test
void getAndSetId() {
final var newId = 2;
customer.setId(newId);
assertEquals(newId, customer.getId());
}
@Test
void getAndSetFirstname() {
final var newFirstname = "Bill";
customer.setFirstName(newFirstname);
assertEquals(newFirstname, customer.getFirstName());
}
@Test
void getAndSetLastname() {
final var newLastname = "Clinton";
customer.setLastName(newLastname);
assertEquals(newLastname, customer.getLastName());
}
@Test
void notEqualWithDifferentId() {
final var newId = 2;
final var otherCustomer = new Customer(newId, FIRSTNAME, LASTNAME);
assertNotEquals(customer, otherCustomer);
assertNotEquals(customer.hashCode(), otherCustomer.hashCode());
}
@Test
void equalsWithSameObjectValues() {
final var otherCustomer = new Customer(ID, FIRSTNAME, LASTNAME);
assertEquals(customer, otherCustomer);
assertEquals(customer.hashCode(), otherCustomer.hashCode());
}
@Test
void equalsWithSameObjects() {
assertEquals(customer, customer);
assertEquals(customer.hashCode(), customer.hashCode());
}
@Test
void testToString() {
assertEquals(String.format("Customer(id=%s, firstName=%s, lastName=%s)",
customer.getId(), customer.getFirstName(), customer.getLastName()), customer.toString());
}
}
@@ -0,0 +1,263 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* 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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
/**
* Tests {@link DbCustomerDao}.
*/
class DbCustomerDaoTest {
private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1";
private DbCustomerDao dao;
private final Customer existingCustomer = new Customer(1, "Freddy", "Krueger");
/**
* Creates customers schema.
*
* @throws SQLException if there is any error while creating schema.
*/
@BeforeEach
void createSchema() throws SQLException {
try (var connection = DriverManager.getConnection(DB_URL);
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL);
}
}
/**
* Represents the scenario where DB connectivity is present.
*/
@Nested
class ConnectionSuccess {
/**
* Setup for connection success scenario.
*
* @throws Exception if any error occurs.
*/
@BeforeEach
void setUp() throws Exception {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
dao = new DbCustomerDao(dataSource);
var result = dao.add(existingCustomer);
assertTrue(result);
}
/**
* Represents the scenario when DAO operations are being performed on a non-existing customer.
*/
@Nested
class NonExistingCustomer {
@Test
void addingShouldResultInSuccess() throws Exception {
try (var allCustomers = dao.getAll()) {
assumeTrue(allCustomers.count() == 1);
}
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.add(nonExistingCustomer);
assertTrue(result);
assertCustomerCountIs(2);
assertEquals(nonExistingCustomer, dao.getById(nonExistingCustomer.getId()).get());
}
@Test
void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception {
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.delete(nonExistingCustomer);
assertFalse(result);
assertCustomerCountIs(1);
}
@Test
void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception {
final var nonExistingId = getNonExistingCustomerId();
final var newFirstname = "Douglas";
final var newLastname = "MacArthur";
final var customer = new Customer(nonExistingId, newFirstname, newLastname);
var result = dao.update(customer);
assertFalse(result);
assertFalse(dao.getById(nonExistingId).isPresent());
}
@Test
void retrieveShouldReturnNoCustomer() throws Exception {
assertFalse(dao.getById(getNonExistingCustomerId()).isPresent());
}
}
/**
* Represents a scenario where DAO operations are being performed on an already existing
* customer.
*/
@Nested
class ExistingCustomer {
@Test
void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception {
var existingCustomer = new Customer(1, "Freddy", "Krueger");
var result = dao.add(existingCustomer);
assertFalse(result);
assertCustomerCountIs(1);
assertEquals(existingCustomer, dao.getById(existingCustomer.getId()).get());
}
@Test
void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception {
var result = dao.delete(existingCustomer);
assertTrue(result);
assertCustomerCountIs(0);
assertFalse(dao.getById(existingCustomer.getId()).isPresent());
}
@Test
void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws
Exception {
final var newFirstname = "Bernard";
final var newLastname = "Montgomery";
final var customer = new Customer(existingCustomer.getId(), newFirstname, newLastname);
var result = dao.update(customer);
assertTrue(result);
final var cust = dao.getById(existingCustomer.getId()).get();
assertEquals(newFirstname, cust.getFirstName());
assertEquals(newLastname, cust.getLastName());
}
}
}
/**
* Represents a scenario where DB connectivity is not present due to network issue, or DB service
* unavailable.
*/
@Nested
class ConnectivityIssue {
private static final String EXCEPTION_CAUSE = "Connection not available";
/**
* setup a connection failure scenario.
*
* @throws SQLException if any error occurs.
*/
@BeforeEach
void setUp() throws SQLException {
dao = new DbCustomerDao(mockedDatasource());
}
private DataSource mockedDatasource() throws SQLException {
var mockedDataSource = mock(DataSource.class);
var mockedConnection = mock(Connection.class);
var exception = new SQLException(EXCEPTION_CAUSE);
doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString());
doReturn(mockedConnection).when(mockedDataSource).getConnection();
return mockedDataSource;
}
@Test
void addingACustomerFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> dao.add(new Customer(2, "Bernard", "Montgomery")));
}
@Test
void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() {
assertThrows(Exception.class, () -> dao.delete(existingCustomer));
}
@Test
void updatingACustomerFailsWithFeedbackToTheClient() {
final var newFirstname = "Bernard";
final var newLastname = "Montgomery";
assertThrows(Exception.class, () -> dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname)));
}
@Test
void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> dao.getById(existingCustomer.getId()));
}
@Test
void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> dao.getAll());
}
}
/**
* Delete customer schema for fresh setup per test.
*
* @throws SQLException if any error occurs.
*/
@AfterEach
void deleteSchema() throws SQLException {
try (var connection = DriverManager.getConnection(DB_URL);
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL);
}
}
private void assertCustomerCountIs(int count) throws Exception {
try (var allCustomers = dao.getAll()) {
assertEquals(count, allCustomers.count());
}
}
/**
* An arbitrary number which does not correspond to an active Customer id.
*
* @return an int of a customer id which doesn't exist
*/
private int getNonExistingCustomerId() {
return 999;
}
}
@@ -0,0 +1,161 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* 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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Tests {@link InMemoryCustomerDao}.
*/
class InMemoryCustomerDaoTest {
private InMemoryCustomerDao dao;
private static final Customer CUSTOMER = new Customer(1, "Freddy", "Krueger");
@BeforeEach
void setUp() {
dao = new InMemoryCustomerDao();
assertTrue(dao.add(CUSTOMER));
}
/**
* Represents the scenario when the DAO operations are being performed on a non-existent
* customer.
*/
@Nested
class NonExistingCustomer {
@Test
void addingShouldResultInSuccess() throws Exception {
try (var allCustomers = dao.getAll()) {
assumeTrue(allCustomers.count() == 1);
}
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.add(nonExistingCustomer);
assertTrue(result);
assertCustomerCountIs(2);
assertEquals(nonExistingCustomer, dao.getById(nonExistingCustomer.getId()).get());
}
@Test
void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception {
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.delete(nonExistingCustomer);
assertFalse(result);
assertCustomerCountIs(1);
}
@Test
void updateShouldBeFailureAndNotAffectExistingCustomers() {
final var nonExistingId = getNonExistingCustomerId();
final var newFirstname = "Douglas";
final var newLastname = "MacArthur";
final var customer = new Customer(nonExistingId, newFirstname, newLastname);
var result = dao.update(customer);
assertFalse(result);
assertFalse(dao.getById(nonExistingId).isPresent());
}
@Test
void retrieveShouldReturnNoCustomer() throws Exception {
assertFalse(dao.getById(getNonExistingCustomerId()).isPresent());
}
}
/**
* Represents the scenario when the DAO operations are being performed on an already existing
* customer.
*/
@Nested
class ExistingCustomer {
@Test
void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception {
var result = dao.add(CUSTOMER);
assertFalse(result);
assertCustomerCountIs(1);
assertEquals(CUSTOMER, dao.getById(CUSTOMER.getId()).get());
}
@Test
void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception {
var result = dao.delete(CUSTOMER);
assertTrue(result);
assertCustomerCountIs(0);
assertFalse(dao.getById(CUSTOMER.getId()).isPresent());
}
@Test
void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws
Exception {
final var newFirstname = "Bernard";
final var newLastname = "Montgomery";
final var customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname);
var result = dao.update(customer);
assertTrue(result);
final var cust = dao.getById(CUSTOMER.getId()).get();
assertEquals(newFirstname, cust.getFirstName());
assertEquals(newLastname, cust.getLastName());
}
@Test
void retriveShouldReturnTheCustomer() {
var optionalCustomer = dao.getById(CUSTOMER.getId());
assertTrue(optionalCustomer.isPresent());
assertEquals(CUSTOMER, optionalCustomer.get());
}
}
/**
* An arbitrary number which does not correspond to an active Customer id.
*
* @return an int of a customer id which doesn't exist
*/
private int getNonExistingCustomerId() {
return 999;
}
private void assertCustomerCountIs(int count) throws Exception {
try (var allCustomers = dao.getAll()) {
assertEquals(count, allCustomers.count());
}
}
}