feature: #1299 Add Identity Map Pattern (#2094)

* #1299 IMPLEMENT IDENTITY MAP PATTERN.

* #1299 Add docstrings, README, testCases and class diagram.

* #1299 Update a comment string in DB implementation.

* #1299 Fix code smells.

* #1299 Fix code smells and add comments to App.java

* #1299 Update constant name.

* #1299 Remove java version dependency and update README.md.

* #1299 Add lombok to PersonFinder.java.

* #1299 Add dependency to maven-assembly-plugin.

* #1299 Use java streams in PersonDbSimulatorImplementation.java.

* #1299 Add print statements while returning the person object.

* #1299 Update README.md.

* #1299 Add puml file.

* Update README.md
This commit is contained in:
u7275858
2022-11-10 06:55:44 +11:00
committed by GitHub
parent 3831a82b69
commit d2599a2904
17 changed files with 1176 additions and 0 deletions
+201
View File
@@ -0,0 +1,201 @@
---
title: Identity Map
categories: Data access
language: en
tags:
- Performance
---
## Intent
Ensures that each object gets loaded only once by keeping every loaded object in a map.
Looks up objects using the map when referring to them.
## Explanation
Real world example
> We are writing a program which the user may use to find the records of a given person in a database.
In plain words
> Construct an Identity map which stores the records of recently searched for items in the database. When we look
> for the same record next time load it from the map do not go to the database.
Wikipedia says
> In the design of DBMS, the identity map pattern is a database access design pattern used to improve performance by providing
a context-specific, in-memory cache to prevent duplicate retrieval of the same object data from the database
**Programmatic Example**
* For the purpose of this demonstration assume we have already created a database instance **db**.
* Let's first look at the implementation of a person entity, and it's fields:
```java
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Getter
@Setter
@AllArgsConstructor
public final class Person implements Serializable {
private static final long serialVersionUID = 1L;
@EqualsAndHashCode.Include
private int personNationalId;
private String name;
private long phoneNum;
@Override
public String toString() {
return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum;
}
}
```
* The following is the implementation of the personFinder which is the entity that the user will utilize in order
to search for a record in our database. It has the relevant DB attached to it. It also maintains an IdentityMap
to store the recently read records.
```java
@Slf4j
@Getter
@Setter
public class PersonFinder {
private static final long serialVersionUID = 1L;
// Access to the Identity Map
private IdentityMap identityMap = new IdentityMap();
private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
/**
* get person corresponding to input ID.
*
* @param key : personNationalId to look for.
*/
public Person getPerson(int key) {
// Try to find person in the identity map
Person person = this.identityMap.getPerson(key);
if (person != null) {
LOGGER.info("Person found in the Map");
return person;
} else {
// Try to find person in the database
person = this.db.find(key);
if (person != null) {
this.identityMap.addPerson(person);
LOGGER.info("Person found in DB.");
return person;
}
LOGGER.info("Person with this ID does not exist.");
return null;
}
}
}
```
* The identity map field in the above class is simply an abstraction of a hashMap with **personNationalId**
as the keys and the corresponding person object as the value. Here is its implementation:
```java
@Slf4j
@Getter
public class IdentityMap {
private Map<Integer, Person> personMap = new HashMap<>();
/**
* Add person to the map.
*/
public void addPerson(Person person) {
if (!personMap.containsKey(person.getPersonNationalId())) {
personMap.put(person.getPersonNationalId(), person);
} else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes.
LOGGER.info("Key already in Map");
}
}
/**
* Get Person with given id.
*
* @param id : personNationalId as requested by user.
*/
public Person getPerson(int id) {
Person person = personMap.get(id);
if (person == null) {
LOGGER.info("ID not in Map.");
}
return person;
}
/**
* Get the size of the map.
*/
public int size() {
if (personMap == null) {
return 0;
}
return personMap.size();
}
}
```
* Now we should construct a dummy person for demonstration purposes and put that person in our database.
```java
Person person1 = new Person(1, "John", 27304159);
db.insert(person1);
```
* Now let's create a person finder object and look for person with personNationalId = 1(assume that the personFinder
object already has the db and an IdentityMap attached to it.):
```java
PersonFinder finder = new PersonFinder();
finder.getPerson(1);
```
* At this stage this record will be loaded from the database and the output would be:
```java
ID not in Map.
Person ID is:1;Person Name is:John;Phone Number is:27304159
Person found in DB.
```
* However, the next we search for this record again we will find it in the map hence we will not need to go
to the database.
```java
Person ID is:1;Person Name is:John;Phone Number is:27304159
Person found in Map.
```
* If the corresponding record is not in the DB at all then an Exception is thrown. Here is its implementation.
```java
public class IdNotFoundException extends RuntimeException {
public IdNotFoundException(final String message) {
super(message);
}
}
```
## Class diagram
![alt text](./etc/IdentityMap.png "Identity Map Pattern")
## Applicability
* The idea behind the Identity Map pattern is that every time we read a record from the database,
we first check the Identity Map to see if the record has already been retrieved.
This allows us to simply return a new reference to the in-memory record rather than creating a new object,
maintaining referential integrity.
* A secondary benefit to the Identity Map is that, since it acts as a cache,
it reduces the number of database calls needed to retrieve objects, which yields a performance enhancement.
## Credits
* [Identity Map](https://www.sourcecodeexamples.net/2018/04/identity-map-pattern.html)
Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

+67
View File
@@ -0,0 +1,67 @@
@startuml
package com.iluwatar.identitymap {
class App {
- LOGGER : Logger {static}
+ App()
+ main(args : String[]) {static}
}
class IdentityMap {
- LOGGER : Logger {static}
- personMap : Map<Integer, Person>
+ IdentityMap()
+ addPerson(person : Person)
+ getPerson(id : int) : Person
+ getPersonMap() : Map<Integer, Person>
+ size() : int
}
class Person {
- name : String
- personNationalId : int
- phoneNum : long
- serialVersionUID : long {static}
+ Person(personNationalId : int, name : String, phoneNum : long)
+ equals(o : Object) : boolean
+ getName() : String
+ getPersonNationalId() : int
+ getPhoneNum() : long
+ hashCode() : int
+ setName(name : String)
+ setPersonNationalId(personNationalId : int)
+ setPhoneNum(phoneNum : long)
+ toString() : String
}
interface PersonDbSimulator {
+ delete(int) {abstract}
+ find(int) : Person {abstract}
+ insert(Person) {abstract}
+ update(Person) {abstract}
}
class PersonDbSimulatorImplementation {
~ ID_STR : String {static}
- LOGGER : Logger {static}
~ NOT_IN_DATA_BASE : String {static}
- personList : List<Person>
+ PersonDbSimulatorImplementation()
+ delete(id : int)
+ find(personNationalID : int) : Person
+ insert(person : Person)
+ size() : int
+ update(person : Person)
}
class PersonFinder {
- LOGGER : Logger {static}
- db : PersonDbSimulatorImplementation
- identityMap : IdentityMap
+ PersonFinder()
+ getDB() : PersonDbSimulatorImplementation
+ getIdentityMap() : IdentityMap
+ getPerson(key : int) : Person
+ setDb(db : PersonDbSimulatorImplementation)
+ setIdentityMap(identityMap : IdentityMap)
}
}
PersonFinder --> "-db" PersonDbSimulatorImplementation
PersonFinder --> "-identityMap" IdentityMap
PersonDbSimulatorImplementation --> "-personList" Person
PersonDbSimulatorImplementation ..|> PersonDbSimulator
@enduml
+69
View File
@@ -0,0 +1,69 @@
<?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">
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>identity-map</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</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.identitymap.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,73 @@
/*
* 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.identitymap;
import lombok.extern.slf4j.Slf4j;
/**
* The basic idea behind the Identity Map is to have a series of maps containing objects that have been pulled from the database.
* The below example demonstrates the identity map pattern by creating a sample DB.
* Since only 1 DB has been created we only have 1 map corresponding to it for the purpose of this demo.
* When you load an object from the database, you first check the map.
* If theres an object in it that corresponds to the one youre loading, you return it. If not, you go to the database,
* putting the objects on the map for future reference as you load them.
*/
@Slf4j
public class App {
/**
* Program entry point.
*
* @param args command line args.
*/
public static void main(String[] args) {
// Dummy Persons
Person person1 = new Person(1, "John", 27304159);
Person person2 = new Person(2, "Thomas", 42273631);
Person person3 = new Person(3, "Arthur", 27489171);
Person person4 = new Person(4, "Finn", 20499078);
Person person5 = new Person(5, "Michael", 40599078);
// Init database
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
db.insert(person1);
db.insert(person2);
db.insert(person3);
db.insert(person4);
db.insert(person5);
// Init a personFinder
PersonFinder finder = new PersonFinder();
finder.setDb(db);
// Find persons in DataBase not the map.
LOGGER.info(finder.getPerson(2).toString());
LOGGER.info(finder.getPerson(4).toString());
LOGGER.info(finder.getPerson(5).toString());
// Find the person in the map.
LOGGER.info(finder.getPerson(2).toString());
}
}
@@ -0,0 +1,31 @@
/*
* 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.identitymap;
public class IdNotFoundException extends RuntimeException {
public IdNotFoundException(final String message) {
super(message);
}
}
@@ -0,0 +1,77 @@
/*
* 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.identitymap;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/**
* This class stores the map into which we will be caching records after loading them from a DataBase.
* Stores the records as a Hash Map with the personNationalIDs as keys.
*/
@Slf4j
@Getter
public class IdentityMap {
private Map<Integer, Person> personMap = new HashMap<>();
/**
* Add person to the map.
*/
public void addPerson(Person person) {
if (!personMap.containsKey(person.getPersonNationalId())) {
personMap.put(person.getPersonNationalId(), person);
} else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes.
LOGGER.info("Key already in Map");
}
}
/**
* Get Person with given id.
*
* @param id : personNationalId as requested by user.
*/
public Person getPerson(int id) {
Person person = personMap.get(id);
if (person == null) {
LOGGER.info("ID not in Map.");
return null;
}
LOGGER.info(person.toString());
return person;
}
/**
* Get the size of the map.
*/
public int size() {
if (personMap == null) {
return 0;
}
return personMap.size();
}
}
@@ -0,0 +1,57 @@
/*
* 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.identitymap;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
/**
* Person definition.
*/
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Getter
@Setter
@AllArgsConstructor
public final class Person implements Serializable {
private static final long serialVersionUID = 1L;
@EqualsAndHashCode.Include
private int personNationalId;
private String name;
private long phoneNum;
@Override
public String toString() {
return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum;
}
}
@@ -0,0 +1,36 @@
/*
* 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.identitymap;
public interface PersonDbSimulator {
Person find(int personNationalID);
void insert(Person person);
void update(Person person);
void delete(int personNationalID);
}
@@ -0,0 +1,108 @@
/*
* 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.identitymap;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
/**
* This is a sample database implementation. The database is in the form of an arraylist which stores records of
* different persons. The personNationalId acts as the primary key for a record.
* Operations :
* -> find (look for object with a particular ID)
* -> insert (insert record for a new person into the database)
* -> update (update the record of a person). To do this, create a new person instance with the same ID as the record you
* want to update. Then call this method with that person as an argument.
* -> delete (delete the record for a particular ID)
*/
@Slf4j
public class PersonDbSimulatorImplementation implements PersonDbSimulator {
//This simulates a table in the database. To extend logic to multiple tables just add more lists to the implementation.
private List<Person> personList = new ArrayList<>();
static final String NOT_IN_DATA_BASE = " not in DataBase";
static final String ID_STR = "ID : ";
@Override
public Person find(int personNationalID) throws IdNotFoundException {
Optional<Person> elem = personList.stream().filter(p -> p.getPersonNationalId() == personNationalID).findFirst();
if (elem.isEmpty()) {
throw new IdNotFoundException(ID_STR + personNationalID + NOT_IN_DATA_BASE);
}
LOGGER.info(elem.get().toString());
return elem.get();
}
@Override
public void insert(Person person) {
Optional<Person> elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst();
if (elem.isPresent()) {
LOGGER.info("Record already exists.");
return;
}
personList.add(person);
}
@Override
public void update(Person person) throws IdNotFoundException {
Optional<Person> elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst();
if (elem.isPresent()) {
elem.get().setName(person.getName());
elem.get().setPhoneNum(person.getPhoneNum());
LOGGER.info("Record updated successfully");
return;
}
throw new IdNotFoundException(ID_STR + person.getPersonNationalId() + NOT_IN_DATA_BASE);
}
/**
* Delete the record corresponding to given ID from the DB.
*
* @param id : personNationalId for person whose record is to be deleted.
*/
public void delete(int id) throws IdNotFoundException {
Optional<Person> elem = personList.stream().filter(p -> p.getPersonNationalId() == id).findFirst();
if (elem.isPresent()) {
personList.remove(elem.get());
LOGGER.info("Record deleted successfully.");
return;
}
throw new IdNotFoundException(ID_STR + id + NOT_IN_DATA_BASE);
}
/**
* Return the size of the database.
*/
public int size() {
if (personList == null) {
return 0;
}
return personList.size();
}
}
@@ -0,0 +1,69 @@
/*
* 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.identitymap;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
* Any object of this class stores a DataBase and an Identity Map. When we try to look for a key we first check if
* it has been cached in the Identity Map and return it if it is indeed in the map.
* If that is not the case then go to the DataBase, get the record, store it in the
* Identity Map and then return the record. Now if we look for the record again we will find it in the table itself which
* will make lookup faster.
*/
@Slf4j
@Getter
@Setter
public class PersonFinder {
private static final long serialVersionUID = 1L;
// Access to the Identity Map
private IdentityMap identityMap = new IdentityMap();
private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
/**
* get person corresponding to input ID.
*
* @param key : personNationalId to look for.
*/
public Person getPerson(int key) {
// Try to find person in the identity map
Person person = this.identityMap.getPerson(key);
if (person != null) {
LOGGER.info("Person found in the Map");
return person;
} else {
// Try to find person in the database
person = this.db.find(key);
if (person != null) {
this.identityMap.addPerson(person);
LOGGER.info("Person found in DB.");
return person;
}
LOGGER.info("Person with this ID does not exist.");
return null;
}
}
}
@@ -0,0 +1,35 @@
/*
* 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.identitymap;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class AppTest {
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}
@@ -0,0 +1,75 @@
/*
* 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.identitymap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class IdentityMapTest {
@Test
void addToMap(){
// new instance of an identity map(not connected to any DB here)
IdentityMap idMap = new IdentityMap();
// Dummy person instances
Person person1 = new Person(11, "Michael", 27304159);
Person person2 = new Person(22, "John", 42273631);
Person person3 = new Person(33, "Arthur", 27489171);
Person person4 = new Person(44, "Finn", 20499078);
// id already in map
Person person5 = new Person(11, "Michael", 40599078);
// All records go into identity map
idMap.addPerson(person1);
idMap.addPerson(person2);
idMap.addPerson(person3);
idMap.addPerson(person4);
idMap.addPerson(person5);
// Test no duplicate in our Map.
Assertions.assertEquals(4,idMap.size(),"Size of the map is incorrect");
// Test record not updated by add method.
Assertions.assertEquals(27304159,idMap.getPerson(11).getPhoneNum(),"Incorrect return value for phone number");
}
@Test
void testGetFromMap() {
// new instance of an identity map(not connected to any DB here)
IdentityMap idMap = new IdentityMap();
// Dummy person instances
Person person1 = new Person(11, "Michael", 27304159);
Person person2 = new Person(22, "John", 42273631);
Person person3 = new Person(33, "Arthur", 27489171);
Person person4 = new Person(44, "Finn", 20499078);
Person person5 = new Person(55, "Michael", 40599078);
// All records go into identity map
idMap.addPerson(person1);
idMap.addPerson(person2);
idMap.addPerson(person3);
idMap.addPerson(person4);
idMap.addPerson(person5);
// Test for dummy persons in the map
Assertions.assertEquals(person1,idMap.getPerson(11),"Incorrect person record returned");
Assertions.assertEquals(person4,idMap.getPerson(44),"Incorrect person record returned");
// Test for person with given id not in map
Assertions.assertNull(idMap.getPerson(1), "Incorrect person record returned");
}
}
@@ -0,0 +1,122 @@
/*
* 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.identitymap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class PersonDbSimulatorImplementationTest {
@Test
void testInsert(){
// DataBase initialization.
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
Assertions.assertEquals(0,db.size(),"Size of null database should be 0");
// Dummy persons.
Person person1 = new Person(1, "Thomas", 27304159);
Person person2 = new Person(2, "John", 42273631);
Person person3 = new Person(3, "Arthur", 27489171);
db.insert(person1);
db.insert(person2);
db.insert(person3);
// Test size after insertion.
Assertions.assertEquals(3,db.size(),"Incorrect size for database.");
Person person4 = new Person(4, "Finn", 20499078);
Person person5 = new Person(5, "Michael", 40599078);
db.insert(person4);
db.insert(person5);
// Test size after more insertions.
Assertions.assertEquals(5,db.size(),"Incorrect size for database.");
Person person5duplicate = new Person(5,"Kevin",89589122);
db.insert(person5duplicate);
// Test size after attempt to insert record with duplicate key.
Assertions.assertEquals(5,db.size(),"Incorrect size for data base");
}
@Test
void findNotInDb(){
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
Person person1 = new Person(1, "Thomas", 27304159);
Person person2 = new Person(2, "John", 42273631);
db.insert(person1);
db.insert(person2);
// Test if IdNotFoundException is thrown where expected.
Assertions.assertThrows(IdNotFoundException.class,()->db.find(3));
}
@Test
void findInDb(){
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
Person person1 = new Person(1, "Thomas", 27304159);
Person person2 = new Person(2, "John", 42273631);
db.insert(person1);
db.insert(person2);
Assertions.assertEquals(person2,db.find(2),"Record that was found was incorrect.");
}
@Test
void updateNotInDb(){
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
Person person1 = new Person(1, "Thomas", 27304159);
Person person2 = new Person(2, "John", 42273631);
db.insert(person1);
db.insert(person2);
Person person3 = new Person(3,"Micheal",25671234);
// Test if IdNotFoundException is thrown when person with ID 3 is not in DB.
Assertions.assertThrows(IdNotFoundException.class,()->db.update(person3));
}
@Test
void updateInDb(){
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
Person person1 = new Person(1, "Thomas", 27304159);
Person person2 = new Person(2, "John", 42273631);
db.insert(person1);
db.insert(person2);
Person person = new Person(2,"Thomas",42273690);
db.update(person);
Assertions.assertEquals(person,db.find(2),"Incorrect update.");
}
@Test
void deleteNotInDb(){
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
Person person1 = new Person(1, "Thomas", 27304159);
Person person2 = new Person(2, "John", 42273631);
db.insert(person1);
db.insert(person2);
// Test if IdNotFoundException is thrown when person with this ID not in DB.
Assertions.assertThrows(IdNotFoundException.class,()->db.delete(3));
}
@Test
void deleteInDb(){
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
Person person1 = new Person(1, "Thomas", 27304159);
Person person2 = new Person(2, "John", 42273631);
db.insert(person1);
db.insert(person2);
// delete the record.
db.delete(1);
// test size of database after deletion.
Assertions.assertEquals(1,db.size(),"Size after deletion is incorrect.");
// try to find deleted record in db.
Assertions.assertThrows(IdNotFoundException.class,()->db.find(1));
}
}
@@ -0,0 +1,112 @@
/*
* 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.identitymap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class PersonFinderTest {
@Test
void personFoundInDB(){
// personFinderInstance
PersonFinder personFinder = new PersonFinder();
// init database for our personFinder
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
// Dummy persons
Person person1 = new Person(1, "John", 27304159);
Person person2 = new Person(2, "Thomas", 42273631);
Person person3 = new Person(3, "Arthur", 27489171);
Person person4 = new Person(4, "Finn", 20499078);
Person person5 = new Person(5, "Michael", 40599078);
// Add data to the database.
db.insert(person1);
db.insert(person2);
db.insert(person3);
db.insert(person4);
db.insert(person5);
personFinder.setDb(db);
Assertions.assertEquals(person1,personFinder.getPerson(1),"Find person returns incorrect record.");
Assertions.assertEquals(person3,personFinder.getPerson(3),"Find person returns incorrect record.");
Assertions.assertEquals(person2,personFinder.getPerson(2),"Find person returns incorrect record.");
Assertions.assertEquals(person5,personFinder.getPerson(5),"Find person returns incorrect record.");
Assertions.assertEquals(person4,personFinder.getPerson(4),"Find person returns incorrect record.");
}
@Test
void personFoundInIdMap(){
// personFinderInstance
PersonFinder personFinder = new PersonFinder();
// init database for our personFinder
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
// Dummy persons
Person person1 = new Person(1, "John", 27304159);
Person person2 = new Person(2, "Thomas", 42273631);
Person person3 = new Person(3, "Arthur", 27489171);
Person person4 = new Person(4, "Finn", 20499078);
Person person5 = new Person(5, "Michael", 40599078);
// Add data to the database.
db.insert(person1);
db.insert(person2);
db.insert(person3);
db.insert(person4);
db.insert(person5);
personFinder.setDb(db);
// Assure key is not in the ID map.
Assertions.assertFalse(personFinder.getIdentityMap().getPersonMap().containsKey(3));
// Assure key is in the database.
Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record.");
// Assure that the record for this key is cached in the Map now.
Assertions.assertTrue(personFinder.getIdentityMap().getPersonMap().containsKey(3));
// Find the record again. This time it will be found in the map.
Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record.");
}
@Test
void personNotFoundInDB(){
PersonFinder personFinder = new PersonFinder();
// init database for our personFinder
PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
personFinder.setDb(db);
Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(1));
// Dummy persons
Person person1 = new Person(1, "John", 27304159);
Person person2 = new Person(2, "Thomas", 42273631);
Person person3 = new Person(3, "Arthur", 27489171);
Person person4 = new Person(4, "Finn", 20499078);
Person person5 = new Person(5, "Michael", 40599078);
db.insert(person1);
db.insert(person2);
db.insert(person3);
db.insert(person4);
db.insert(person5);
personFinder.setDb(db);
// Assure that the database has been updated.
Assertions.assertEquals(person4,personFinder.getPerson(4),"Find returns incorrect record");
// Assure key is in DB now.
Assertions.assertDoesNotThrow(()->personFinder.getPerson(1));
// Assure key not in DB.
Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(6));
}
}
@@ -0,0 +1,43 @@
/*
* 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.identitymap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class PersonTest {
@Test
void testEquality(){
// dummy persons.
Person person1 = new Person(1,"Harry",989950022);
Person person2 = new Person(2,"Kane",989920011);
Assertions.assertNotEquals(person1,person2,"Incorrect equality condition");
// person with duplicate nationalID.
Person person3 = new Person(2,"John",789012211);
// If nationalID is equal then persons are equal(even if name or phoneNum are different).
// This situation will never arise in this implementation. Only for testing.
Assertions.assertEquals(person2,person3,"Incorrect inequality condition");
}
}
+1
View File
@@ -215,6 +215,7 @@
<module>composite-view</module>
<module>metadata-mapping</module>
<module>service-to-worker</module>
<module>identity-map</module>
</modules>
<repositories>
<repository>