Skip to content

Commit d2599a2

Browse files
authored
feature: iluwatar#1299 Add Identity Map Pattern (iluwatar#2094)
* iluwatar#1299 IMPLEMENT IDENTITY MAP PATTERN. * iluwatar#1299 Add docstrings, README, testCases and class diagram. * iluwatar#1299 Update a comment string in DB implementation. * iluwatar#1299 Fix code smells. * iluwatar#1299 Fix code smells and add comments to App.java * iluwatar#1299 Update constant name. * iluwatar#1299 Remove java version dependency and update README.md. * iluwatar#1299 Add lombok to PersonFinder.java. * iluwatar#1299 Add dependency to maven-assembly-plugin. * iluwatar#1299 Use java streams in PersonDbSimulatorImplementation.java. * iluwatar#1299 Add print statements while returning the person object. * iluwatar#1299 Update README.md. * iluwatar#1299 Add puml file. * Update README.md
1 parent 3831a82 commit d2599a2

File tree

17 files changed

+1176
-0
lines changed

17 files changed

+1176
-0
lines changed

identity-map/README.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
---
2+
title: Identity Map
3+
categories: Data access
4+
language: en
5+
tags:
6+
- Performance
7+
---
8+
9+
## Intent
10+
11+
Ensures that each object gets loaded only once by keeping every loaded object in a map.
12+
Looks up objects using the map when referring to them.
13+
14+
## Explanation
15+
16+
Real world example
17+
18+
> We are writing a program which the user may use to find the records of a given person in a database.
19+
20+
In plain words
21+
22+
> Construct an Identity map which stores the records of recently searched for items in the database. When we look
23+
> for the same record next time load it from the map do not go to the database.
24+
25+
Wikipedia says
26+
27+
> In the design of DBMS, the identity map pattern is a database access design pattern used to improve performance by providing
28+
a context-specific, in-memory cache to prevent duplicate retrieval of the same object data from the database
29+
30+
**Programmatic Example**
31+
32+
* For the purpose of this demonstration assume we have already created a database instance **db**.
33+
* Let's first look at the implementation of a person entity, and it's fields:
34+
35+
```java
36+
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
37+
@Getter
38+
@Setter
39+
@AllArgsConstructor
40+
public final class Person implements Serializable {
41+
42+
private static final long serialVersionUID = 1L;
43+
44+
@EqualsAndHashCode.Include
45+
private int personNationalId;
46+
private String name;
47+
private long phoneNum;
48+
49+
@Override
50+
public String toString() {
51+
52+
return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum;
53+
54+
}
55+
56+
}
57+
58+
```
59+
60+
* The following is the implementation of the personFinder which is the entity that the user will utilize in order
61+
to search for a record in our database. It has the relevant DB attached to it. It also maintains an IdentityMap
62+
to store the recently read records.
63+
64+
```java
65+
@Slf4j
66+
@Getter
67+
@Setter
68+
public class PersonFinder {
69+
private static final long serialVersionUID = 1L;
70+
// Access to the Identity Map
71+
private IdentityMap identityMap = new IdentityMap();
72+
private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
73+
/**
74+
* get person corresponding to input ID.
75+
*
76+
* @param key : personNationalId to look for.
77+
*/
78+
public Person getPerson(int key) {
79+
// Try to find person in the identity map
80+
Person person = this.identityMap.getPerson(key);
81+
if (person != null) {
82+
LOGGER.info("Person found in the Map");
83+
return person;
84+
} else {
85+
// Try to find person in the database
86+
person = this.db.find(key);
87+
if (person != null) {
88+
this.identityMap.addPerson(person);
89+
LOGGER.info("Person found in DB.");
90+
return person;
91+
}
92+
LOGGER.info("Person with this ID does not exist.");
93+
return null;
94+
}
95+
}
96+
}
97+
98+
```
99+
100+
* The identity map field in the above class is simply an abstraction of a hashMap with **personNationalId**
101+
as the keys and the corresponding person object as the value. Here is its implementation:
102+
103+
```java
104+
@Slf4j
105+
@Getter
106+
public class IdentityMap {
107+
private Map<Integer, Person> personMap = new HashMap<>();
108+
/**
109+
* Add person to the map.
110+
*/
111+
public void addPerson(Person person) {
112+
if (!personMap.containsKey(person.getPersonNationalId())) {
113+
personMap.put(person.getPersonNationalId(), person);
114+
} else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes.
115+
LOGGER.info("Key already in Map");
116+
}
117+
}
118+
119+
/**
120+
* Get Person with given id.
121+
*
122+
* @param id : personNationalId as requested by user.
123+
*/
124+
public Person getPerson(int id) {
125+
Person person = personMap.get(id);
126+
if (person == null) {
127+
LOGGER.info("ID not in Map.");
128+
}
129+
return person;
130+
}
131+
132+
/**
133+
* Get the size of the map.
134+
*/
135+
public int size() {
136+
if (personMap == null) {
137+
return 0;
138+
}
139+
return personMap.size();
140+
}
141+
142+
}
143+
144+
```
145+
146+
* Now we should construct a dummy person for demonstration purposes and put that person in our database.
147+
148+
```java
149+
Person person1 = new Person(1, "John", 27304159);
150+
db.insert(person1);
151+
```
152+
153+
* Now let's create a person finder object and look for person with personNationalId = 1(assume that the personFinder
154+
object already has the db and an IdentityMap attached to it.):
155+
156+
```java
157+
PersonFinder finder = new PersonFinder();
158+
finder.getPerson(1);
159+
```
160+
161+
* At this stage this record will be loaded from the database and the output would be:
162+
163+
```java
164+
ID not in Map.
165+
Person ID is:1;Person Name is:John;Phone Number is:27304159
166+
Person found in DB.
167+
```
168+
169+
* However, the next we search for this record again we will find it in the map hence we will not need to go
170+
to the database.
171+
172+
```java
173+
Person ID is:1;Person Name is:John;Phone Number is:27304159
174+
Person found in Map.
175+
```
176+
177+
* If the corresponding record is not in the DB at all then an Exception is thrown. Here is its implementation.
178+
179+
```java
180+
public class IdNotFoundException extends RuntimeException {
181+
public IdNotFoundException(final String message) {
182+
super(message);
183+
}
184+
}
185+
```
186+
## Class diagram
187+
188+
![alt text](./etc/IdentityMap.png "Identity Map Pattern")
189+
190+
## Applicability
191+
192+
* The idea behind the Identity Map pattern is that every time we read a record from the database,
193+
we first check the Identity Map to see if the record has already been retrieved.
194+
This allows us to simply return a new reference to the in-memory record rather than creating a new object,
195+
maintaining referential integrity.
196+
* A secondary benefit to the Identity Map is that, since it acts as a cache,
197+
it reduces the number of database calls needed to retrieve objects, which yields a performance enhancement.
198+
199+
## Credits
200+
201+
* [Identity Map](https://www.sourcecodeexamples.net/2018/04/identity-map-pattern.html)

identity-map/etc/IdentityMap.png

81.1 KB
Loading
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
@startuml
2+
package com.iluwatar.identitymap {
3+
class App {
4+
- LOGGER : Logger {static}
5+
+ App()
6+
+ main(args : String[]) {static}
7+
}
8+
class IdentityMap {
9+
- LOGGER : Logger {static}
10+
- personMap : Map<Integer, Person>
11+
+ IdentityMap()
12+
+ addPerson(person : Person)
13+
+ getPerson(id : int) : Person
14+
+ getPersonMap() : Map<Integer, Person>
15+
+ size() : int
16+
}
17+
class Person {
18+
- name : String
19+
- personNationalId : int
20+
- phoneNum : long
21+
- serialVersionUID : long {static}
22+
+ Person(personNationalId : int, name : String, phoneNum : long)
23+
+ equals(o : Object) : boolean
24+
+ getName() : String
25+
+ getPersonNationalId() : int
26+
+ getPhoneNum() : long
27+
+ hashCode() : int
28+
+ setName(name : String)
29+
+ setPersonNationalId(personNationalId : int)
30+
+ setPhoneNum(phoneNum : long)
31+
+ toString() : String
32+
}
33+
interface PersonDbSimulator {
34+
+ delete(int) {abstract}
35+
+ find(int) : Person {abstract}
36+
+ insert(Person) {abstract}
37+
+ update(Person) {abstract}
38+
}
39+
class PersonDbSimulatorImplementation {
40+
~ ID_STR : String {static}
41+
- LOGGER : Logger {static}
42+
~ NOT_IN_DATA_BASE : String {static}
43+
- personList : List<Person>
44+
+ PersonDbSimulatorImplementation()
45+
+ delete(id : int)
46+
+ find(personNationalID : int) : Person
47+
+ insert(person : Person)
48+
+ size() : int
49+
+ update(person : Person)
50+
}
51+
class PersonFinder {
52+
- LOGGER : Logger {static}
53+
- db : PersonDbSimulatorImplementation
54+
- identityMap : IdentityMap
55+
+ PersonFinder()
56+
+ getDB() : PersonDbSimulatorImplementation
57+
+ getIdentityMap() : IdentityMap
58+
+ getPerson(key : int) : Person
59+
+ setDb(db : PersonDbSimulatorImplementation)
60+
+ setIdentityMap(identityMap : IdentityMap)
61+
}
62+
}
63+
PersonFinder --> "-db" PersonDbSimulatorImplementation
64+
PersonFinder --> "-identityMap" IdentityMap
65+
PersonDbSimulatorImplementation --> "-personList" Person
66+
PersonDbSimulatorImplementation ..|> PersonDbSimulator
67+
@enduml

identity-map/pom.xml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
5+
6+
The MIT License
7+
Copyright © 2014-2022 Ilkka Seppälä
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
-->
28+
<project xmlns="http://maven.apache.org/POM/4.0.0"
29+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
30+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
31+
<parent>
32+
<artifactId>java-design-patterns</artifactId>
33+
<groupId>com.iluwatar</groupId>
34+
<version>1.26.0-SNAPSHOT</version>
35+
</parent>
36+
<modelVersion>4.0.0</modelVersion>
37+
<artifactId>identity-map</artifactId>
38+
<dependencies>
39+
<dependency>
40+
<groupId>org.junit.jupiter</groupId>
41+
<artifactId>junit-jupiter-api</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
<dependency>
45+
<groupId>junit</groupId>
46+
<artifactId>junit</artifactId>
47+
<scope>test</scope>
48+
</dependency>
49+
</dependencies>
50+
<build>
51+
<plugins>
52+
<plugin>
53+
<groupId>org.apache.maven.plugins</groupId>
54+
<artifactId>maven-assembly-plugin</artifactId>
55+
<executions>
56+
<execution>
57+
<configuration>
58+
<archive>
59+
<manifest>
60+
<mainClass>com.iluwatar.identitymap.App</mainClass>
61+
</manifest>
62+
</archive>
63+
</configuration>
64+
</execution>
65+
</executions>
66+
</plugin>
67+
</plugins>
68+
</build>
69+
</project>

0 commit comments

Comments
 (0)