Compare commits

..

43 Commits

Author SHA1 Message Date
68a5df1cc9 delete unwanted files 2021-12-29 19:05:16 +02:00
1c70cdc333 Update throttling pattern example 2021-12-29 19:00:53 +02:00
55f4ecc234 Create UpdateTest.java 2021-12-29 17:07:48 +02:00
3855f0bc15 Create AppTest.java 2021-12-29 17:07:48 +02:00
d71d2a4abd Delete UpdateTest.java 2021-12-29 17:07:48 +02:00
67f0a201e2 Delete AppTest.java 2021-12-29 17:07:47 +02:00
3f08aebcd3 Create PhysicsComponent.java 2021-12-29 17:07:47 +02:00
8604e65fce Create InputComponent.java 2021-12-29 17:07:46 +02:00
618b90147a Create GraphicsComponent.java 2021-12-29 17:07:46 +02:00
405495f0a8 Create GameObject.java 2021-12-29 17:07:45 +02:00
f9ca7f752e Create Component.java 2021-12-29 17:07:45 +02:00
b2a22e506e Create BjornPhysicsComponent.java 2021-12-29 17:07:44 +02:00
1fb1073436 Create BjornInputComponent.java 2021-12-29 17:07:44 +02:00
c28b8edf9e Create BjornGraphicsComponent.java 2021-12-29 17:07:43 +02:00
35ad973730 Update App.java 2021-12-29 17:07:43 +02:00
a4d77e65f8 Update App.java 2021-12-29 17:07:42 +02:00
0d1e226258 Create App.java 2021-12-29 17:07:42 +02:00
dc73d57f3e Delete PhysicsComponent.java 2021-12-29 17:07:41 +02:00
4f2becf96f Delete InputComponent.java 2021-12-29 17:07:41 +02:00
6fed7cbbb2 Delete GraphicsComponent.java 2021-12-29 17:07:40 +02:00
c7c3cb8e19 Delete GameObject.java 2021-12-29 17:07:40 +02:00
6bd6f07bc2 Delete Component.java 2021-12-29 17:07:39 +02:00
b0d17a00cd Delete BjornPhysicsComponent.java 2021-12-29 17:07:39 +02:00
18b3fa312f Delete BjornInputComponent.java 2021-12-29 17:07:39 +02:00
fc75edc7ca Delete BjornGraphicsComponent.java 2021-12-29 17:07:38 +02:00
c78d5a3220 Delete App.java 2021-12-29 17:07:38 +02:00
11dcf5a4aa Update App.java 2021-12-29 17:07:37 +02:00
df84a3cc74 Update Component.java 2021-12-29 17:07:37 +02:00
6523047361 Update BjornPhysicsComponent.java 2021-12-29 17:07:36 +02:00
4a4c136757 Update BjornInputComponent.java 2021-12-29 17:07:36 +02:00
1b5da55d45 Update BjornGraphicsComponent.java 2021-12-29 17:07:35 +02:00
e9b20f674e Update App.java 2021-12-29 17:07:35 +02:00
23162f474f Update pom.xml 2021-12-29 17:07:34 +02:00
1610876fdd Update README.md 2021-12-29 17:07:34 +02:00
be55b8ebb8 Update README.md 2021-12-29 17:07:33 +02:00
72aaea5333 Add files via upload 2021-12-29 17:07:33 +02:00
e62a412318 Create AppTest.java 2021-12-29 17:07:33 +02:00
ff955b4d95 Add files via upload 2021-12-29 17:07:32 +02:00
a7650013a9 Add files via upload 2021-12-29 17:07:32 +02:00
876d2a5466 Add files via upload 2021-12-29 17:07:31 +02:00
636a826bb7 Add files via upload 2021-12-29 17:07:31 +02:00
2230c55582 Create App.java 2021-12-29 17:07:30 +02:00
f003844c25 Create component.urm.puml 2021-12-29 17:07:30 +02:00
39 changed files with 350 additions and 1343 deletions

View File

@ -1776,15 +1776,6 @@
"contributions": [
"code"
]
},
{
"login": "castleKing1997",
"name": "DragonDreamer",
"avatar_url": "https://avatars.githubusercontent.com/u/35420129?v=4",
"profile": "http://rosaecrucis.cn",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@ -1,18 +1,25 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
#
# The MIT License
# Copyright © 2014-2021 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.
#
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

View File

@ -10,7 +10,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-196-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-195-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/>
@ -325,7 +325,6 @@ This project is licensed under the terms of the MIT license.
<td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yuhangbin"><img src="https://avatars.githubusercontent.com/u/17566866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CharlieYu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yuhangbin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Leisterbecker"><img src="https://avatars.githubusercontent.com/u/20650323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leisterbecker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Leisterbecker" title="Code">💻</a></td>
<td align="center"><a href="http://rosaecrucis.cn"><img src="https://avatars.githubusercontent.com/u/35420129?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DragonDreamer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=castleKing1997" title="Code">💻</a></td>
</tr>
</table>

View File

@ -9,10 +9,6 @@ tags:
- Reactive
---
## Name
Event Aggregator
## Intent
A system with lots of objects can lead to complexities when a
client wants to subscribe to events. The client has to find and register for
@ -21,136 +17,6 @@ requires a separate subscription. An Event Aggregator acts as a single source
of events for many objects. It registers for all the events of the many objects
allowing clients to register with just the aggregator.
## Explanation
Real-world example
> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most
> of his critical information from King's Hand, the second in command. King's hand has many
> close advisors himself, feeding him with relevant information about events occurring in the
> kingdom.
In Plain Words
> Event Aggregator is an event mediator that collects events from multiple sources and delivers
> them to registered observers.
**Programmatic Example**
In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of
the objects are event listeners, some are event emitters, and the event aggregator does both.
```java
public interface EventObserver {
void onEvent(Event e);
}
public abstract class EventEmitter {
private final Map<Event, List<EventObserver>> observerLists;
public EventEmitter() {
observerLists = new HashMap<>();
}
public final void registerObserver(EventObserver obs, Event e) {
...
}
protected void notifyObservers(Event e) {
...
}
}
```
`KingJoffrey` is listening to events from `KingsHand`.
```java
@Slf4j
public class KingJoffrey implements EventObserver {
@Override
public void onEvent(Event e) {
LOGGER.info("Received event from the King's Hand: {}", e.toString());
}
}
```
`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`.
Whatever he hears from them, he delivers to `KingJoffrey`.
```java
public class KingsHand extends EventEmitter implements EventObserver {
public KingsHand() {
}
public KingsHand(EventObserver obs, Event e) {
super(obs, e);
}
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
}
```
For example, `LordVarys` finds a traitor every Sunday and notifies the `KingsHand`.
```java
@Slf4j
public class LordVarys extends EventEmitter implements EventObserver {
@Override
public void timePasses(Weekday day) {
if (day == Weekday.SATURDAY) {
notifyObservers(Event.TRAITOR_DETECTED);
}
}
}
```
The following snippet demonstrates how the objects are constructed and wired together.
```java
var kingJoffrey = new KingJoffrey();
var kingsHand = new KingsHand();
kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED);
kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED);
var varys = new LordVarys();
varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED);
varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED);
var scout = new Scout();
scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING);
scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED);
var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED);
var emitters = List.of(
kingsHand,
baelish,
varys,
scout
);
Arrays.stream(Weekday.values())
.<Consumer<? super EventEmitter>>map(day -> emitter -> emitter.timePasses(day))
.forEachOrdered(emitters::forEach);
```
The console output after running the example.
```
18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected
```
## Class diagram
![alt text](./etc/classes.png "Event Aggregator")
@ -160,13 +26,9 @@ Use the Event Aggregator pattern when
* Event Aggregator is a good choice when you have lots of objects that are
potential event sources. Rather than have the observer deal with registering
with them all, you can centralize the registration logic to the Event
Aggregator. As well as simplifying registration, an Event Aggregator also
Aggregator. As well as simplifying registration, a Event Aggregator also
simplifies the memory management issues in using observers.
## Related patterns
* [Observer](https://java-design-patterns.com/patterns/observer/)
## Credits
* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html)

View File

@ -17,10 +17,10 @@ the user to specify only what to do with the resource.
## Explanation
Real-world example
Real world example
> A class needs to be provided for writing text strings to files. To make it easy for
> the user, the service class opens and closes the file automatically. The user only has to
> We need to provide a class that can be used to write text strings to files. To make it easy for
> the user we let our service class open and close the file automatically, the user only has to
> specify what is written into which file.
In plain words
@ -35,50 +35,35 @@ In plain words
**Programmatic Example**
`SimpleFileWriter` class implements the Execute Around idiom. It takes `FileWriterAction` as a
constructor argument allowing the user to specify what gets written into the file.
Let's introduce our file writer class.
```java
@FunctionalInterface
public interface FileWriterAction {
void writeFile(FileWriter writer) throws IOException;
}
@Slf4j
public class SimpleFileWriter {
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
LOGGER.info("Opening the file");
try (var writer = new FileWriter(filename)) {
LOGGER.info("Executing the action");
action.writeFile(writer);
LOGGER.info("Closing the file");
}
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
try (var writer = new FileWriter(filename)) {
action.writeFile(writer);
}
}
}
```
The following code demonstrates how `SimpleFileWriter` is used. `Scanner` is used to print the file
contents after the writing finishes.
To utilize the file writer the following code is needed.
```java
FileWriterAction writeHello = writer -> {
writer.write("Gandalf was here");
};
new SimpleFileWriter("testfile.txt", writeHello);
var scanner = new Scanner(new File("testfile.txt"));
while (scanner.hasNextLine()) {
LOGGER.info(scanner.nextLine());
}
```
Here's the console output.
```
21:18:07.185 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Opening the file
21:18:07.188 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Executing the action
21:18:07.189 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Closing the file
21:18:07.199 [main] INFO com.iluwatar.execute.around.App - Gandalf was here
FileWriterAction writeHello = writer -> {
writer.write("Hello");
writer.append(" ");
writer.append("there!");
};
new SimpleFileWriter("testfile.txt", writeHello);
```
## Class diagram
@ -89,7 +74,8 @@ Here's the console output.
Use the Execute Around idiom when
* An API requires methods to be called in pairs such as open/close or allocate/deallocate.
* You use an API that requires methods to be called in pairs such as open/close or
allocate/deallocate.
## Credits

View File

@ -23,14 +23,10 @@
package com.iluwatar.execute.around;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import lombok.extern.slf4j.Slf4j;
/**
* The Execute Around idiom specifies executable code before and after a method. Typically
* The Execute Around idiom specifies some code to be executed before and after a method. Typically
* the idiom is used when the API has methods to be executed in pairs, such as resource
* allocation/deallocation or lock acquisition/release.
*
@ -38,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
* the user. The user specifies only what to do with the file by providing the {@link
* FileWriterAction} implementation.
*/
@Slf4j
public class App {
/**
@ -46,16 +41,11 @@ public class App {
*/
public static void main(String[] args) throws IOException {
// create the file writer and execute the custom action
FileWriterAction writeHello = writer -> {
writer.write("Gandalf was here");
writer.write("Hello");
writer.append(" ");
writer.append("there!");
};
new SimpleFileWriter("testfile.txt", writeHello);
// print the file contents
var scanner = new Scanner(new File("testfile.txt"));
while (scanner.hasNextLine()) {
LOGGER.info(scanner.nextLine());
}
}
}

View File

@ -26,24 +26,18 @@ package com.iluwatar.execute.around;
import java.io.FileWriter;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
/**
* SimpleFileWriter handles opening and closing file for the user. The user only has to specify what
* to do with the file resource through {@link FileWriterAction} parameter.
*/
@Slf4j
public class SimpleFileWriter {
/**
* Constructor.
*/
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
LOGGER.info("Opening the file");
try (var writer = new FileWriter(filename)) {
LOGGER.info("Executing the action");
action.writeFile(writer);
LOGGER.info("Closing the file");
}
}
}

View File

@ -10,115 +10,19 @@ tags:
---
## Intent
Define a factory of immutable content with separated builder and factory interfaces.
## Explanation
Real-world example
> Imagine a magical weapon factory that can create any type of weapon wished for. When the factory
> is unboxed, the master recites the weapon types needed to prepare it. After that, any of those
> weapon types can be summoned in an instant.
In plain words
> Factory kit is a configurable object builder.
**Programmatic Example**
Let's first define the simple `Weapon` hierarchy.
```java
public interface Weapon {
}
public enum WeaponType {
SWORD,
AXE,
BOW,
SPEAR
}
public class Sword implements Weapon {
@Override
public String toString() {
return "Sword";
}
}
// Axe, Bow, and Spear are defined similarly
```
Next, we define a functional interface that allows adding a builder with a name to the factory.
```java
public interface Builder {
void add(WeaponType name, Supplier<Weapon> supplier);
}
```
The meat of the example is the `WeaponFactory` interface that effectively implements the factory
kit pattern. The method `#factory` is used to configure the factory with the classes it needs to
be able to construct. The method `#create` is then used to create object instances.
```java
public interface WeaponFactory {
static WeaponFactory factory(Consumer<Builder> consumer) {
var map = new HashMap<WeaponType, Supplier<Weapon>>();
consumer.accept(map::put);
return name -> map.get(name).get();
}
Weapon create(WeaponType name);
}
```
Now, we can show how `WeaponFactory` can be used.
```java
var factory = WeaponFactory.factory(builder -> {
builder.add(WeaponType.SWORD, Sword::new);
builder.add(WeaponType.AXE, Axe::new);
builder.add(WeaponType.SPEAR, Spear::new);
builder.add(WeaponType.BOW, Bow::new);
});
var list = new ArrayList<Weapon>();
list.add(factory.create(WeaponType.AXE));
list.add(factory.create(WeaponType.SPEAR));
list.add(factory.create(WeaponType.SWORD));
list.add(factory.create(WeaponType.BOW));
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
```
Here is the console output when the example is run.
```
21:15:49.709 [main] INFO com.iluwatar.factorykit.App - Axe
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Spear
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Sword
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Bow
```
## Class diagram
![alt text](./etc/factory-kit.png "Factory Kit")
## Applicability
Use the Factory Kit pattern when
* The factory class can't anticipate the types of objects it must create
* A new instance of a custom builder is needed instead of a global one
* The types of objects that the factory can build need to be defined outside the class
* The builder and creator interfaces need to be separated
## Related patterns
* [Builder](https://java-design-patterns.com/patterns/builder/)
* [Factory](https://java-design-patterns.com/patterns/factory/)
* a class can't anticipate the class of objects it must create
* you just want a new instance of a custom builder instead of the global one
* you explicitly want to define types of objects, that factory can build
* you want a separated builder and creator interface
## Credits
* [Design Pattern Reloaded by Remi Forax](https://www.youtube.com/watch?v=-k2X7guaArU)
* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU)

View File

@ -23,16 +23,14 @@
package com.iluwatar.factorykit;
import java.util.ArrayList;
import lombok.extern.slf4j.Slf4j;
/**
* Factory kit is a creational pattern that defines a factory of immutable content with separated
* Factory-kit is a creational pattern which defines a factory of immutable content with separated
* builder and factory interfaces to deal with the problem of creating one of the objects specified
* directly in the factory kit instance.
* directly in the factory-kit instance.
*
* <p>In the given example {@link WeaponFactory} represents the factory kit, that contains four
* <p>In the given example {@link WeaponFactory} represents the factory-kit, that contains four
* {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface.
*
* <p>Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with
@ -54,11 +52,7 @@ public class App {
builder.add(WeaponType.SPEAR, Spear::new);
builder.add(WeaponType.BOW, Bow::new);
});
var list = new ArrayList<Weapon>();
list.add(factory.create(WeaponType.AXE));
list.add(factory.create(WeaponType.SPEAR));
list.add(factory.create(WeaponType.SWORD));
list.add(factory.create(WeaponType.BOW));
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
var axe = factory.create(WeaponType.AXE);
LOGGER.info(axe.toString());
}
}

View File

@ -1,182 +0,0 @@
---
layout: pattern
title: Metadata Mapping
folder: metadata-mapping
permalink: /patterns/metadata-mapping/
categories: Architectural
language: en
tags:
- Data access
---
## Intent
Holds details of object-relational mapping in the metadata.
## Explanation
Real world example
> Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code.
In plain words
> Metadata Mapping specifies the mapping between classes and tables so that we could treat a table of any database like a Java class.
Wikipedia says
> Create a "virtual [object database](https://en.wikipedia.org/wiki/Object_database)" that can be used from within the programming language.
**Programmatic Example**
We give an example about visiting the information of `USER` table in `h2` database. Firstly, we create `USER` table with `h2`:
```java
@Slf4j
public class DatabaseUtil {
private static final String DB_URL = "jdbc:h2:mem:metamapping";
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
+ "CREATE TABLE `user` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `username` varchar(255) NOT NULL,\n"
+ " `password` varchar(255) NOT NULL,\n"
+ " PRIMARY KEY (`id`)\n"
+ ");";
/**
* Create database.
*/
static {
LOGGER.info("create h2 database");
var source = new JdbcDataSource();
source.setURL(DB_URL);
try (var statement = source.getConnection().createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.error("unable to create h2 data source", e);
}
}
}
```
Correspondingly, here's the basic `User` entity.
```java
@Setter
@Getter
@ToString
public class User {
private Integer id;
private String username;
private String password;
/**
* Get a user.
* @param username user name
* @param password user password
*/
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
```
Then we write a `xml` file to show the mapping between the table and the object:
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.iluwatar.metamapping.model.User" table="user">
<id name="id" type="java.lang.Integer" column="id">
<generator class="native"/>
</id>
<property name="username" column="username" type="java.lang.String"/>
<property name="password" column="password" type="java.lang.String"/>
</class>
</hibernate-mapping>
```
We use `Hibernate` to resolve the mapping and connect to our database, here's its configuration:
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.url">jdbc:h2:mem:metamapping</property>
<property name="connection.driver_class">org.h2.Driver</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
```
Then we can get access to the table just like an object with `Hibernate`, here's some CRUDs:
```java
@Slf4j
public class UserService {
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
/**
* List all users.
* @return list of users
*/
public List<User> listUser() {
LOGGER.info("list all users.");
List<User> users = new ArrayList<>();
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
List<User> userIter = session.createQuery("FROM User").list();
for (var iterator = userIter.iterator(); iterator.hasNext();) {
users.add(iterator.next());
}
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get users", e);
}
return users;
}
// other CRUDs ->
...
public void close() {
HibernateUtil.shutdown();
}
}
```
## Class diagram
![metamapping](etc/metamapping.png)
## Applicability
Use the Metadata Mapping when:
- you want reduce the amount of work needed to handle database mapping.
## Known uses
[Hibernate](https://hibernate.org/), [EclipseLink](https://www.eclipse.org/eclipselink/), [MyBatis](https://blog.mybatis.org/)......
## 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,32 +0,0 @@
@startuml
interface com.iluwatar.metamapping.service.UserService {
+ List<User> listUser()
+ int createUser(User)
+ void updateUser(Integer,User)
+ void deleteUser(Integer)
+ User getUser(Integer)
+ void close()
}
class com.iluwatar.metamapping.utils.DatabaseUtil {
+ {static} void createDataSource()
}
class com.iluwatar.metamapping.model.User {
- Integer id
- String username
- String password
+ User(String username, String password)
}
class com.iluwatar.metamapping.utils.HibernateUtil {
+ {static} SessionFactory getSessionFactory()
+ {static} void shutdown()
}
class com.iluwatar.metamapping.App {
+ {static} void main(String[])
+ {static} List<User> generateSampleUsers()
}
com.iluwatar.metamapping.service.UserService <.. com.iluwatar.metamapping.App
com.iluwatar.metamapping.model.User <.. com.iluwatar.metamapping.service.UserService
com.iluwatar.metamapping.utils.HibernateUtil <.. com.iluwatar.metamapping.service.UserService
com.iluwatar.metamapping.utils.DatabaseUtil <-- com.iluwatar.metamapping.utils.HibernateUtil
@enduml

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright © 2014-2021 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>metadata-mapping</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.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
<dependency>
<groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.metamapping.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,72 +0,0 @@
package com.iluwatar.metamapping;
import com.iluwatar.metamapping.model.User;
import com.iluwatar.metamapping.service.UserService;
import com.iluwatar.metamapping.utils.DatabaseUtil;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.ServiceRegistry;
/**
* Metadata Mapping specifies the mapping
* between classes and tables so that
* we could treat a table of any database like a Java class.
*
* <p>With hibernate, we achieve list/create/update/delete/get operations:
* 1)Create the H2 Database in {@link DatabaseUtil}.
* 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete.
* For learning metadata mapping pattern, we go deeper into Hibernate here:
* a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml
* b)create session factory to generate session interacting with database
* c)generate session with factory pattern
* d)create query object or use basic api with session,
* hibernate will convert all query to database query according to metadata
* 3)We encapsulate hibernate service in {@link UserService} for our use.
* @see org.hibernate.cfg.Configuration#configure(String)
* @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry)
* @see org.hibernate.internal.SessionFactoryImpl#openSession()
*/
@Slf4j
public class App {
/**
* Program entry point.
*
* @param args command line args.
* @throws Exception if any error occurs.
*/
public static void main(String[] args) throws Exception {
// get service
var userService = new UserService();
// use create service to add users
for (var user: generateSampleUsers()) {
var id = userService.createUser(user);
LOGGER.info("Add user" + user + "at" + id + ".");
}
// use list service to get users
var users = userService.listUser();
LOGGER.info(String.valueOf(users));
// use get service to get a user
var user = userService.getUser(1);
LOGGER.info(String.valueOf(user));
// change password of user 1
user.setPassword("new123");
// use update service to update user 1
userService.updateUser(1, user);
// use delete service to delete user 2
userService.deleteUser(2);
// close service
userService.close();
}
/**
* Generate users.
*
* @return list of users.
*/
public static List<User> generateSampleUsers() {
final var user1 = new User("ZhangSan", "zhs123");
final var user2 = new User("LiSi", "ls123");
final var user3 = new User("WangWu", "ww123");
return List.of(user1, user2, user3);
}
}

View File

@ -1,29 +0,0 @@
package com.iluwatar.metamapping.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* User Entity.
*/
@Setter
@Getter
@ToString
public class User {
private Integer id;
private String username;
private String password;
public User() {}
/**
* Get a user.
* @param username user name
* @param password user password
*/
public User(String username, String password) {
this.username = username;
this.password = password;
}
}

View File

@ -1,114 +0,0 @@
package com.iluwatar.metamapping.service;
import com.iluwatar.metamapping.model.User;
import com.iluwatar.metamapping.utils.HibernateUtil;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
/**
* Service layer for user.
*/
@Slf4j
public class UserService {
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
/**
* List all users.
* @return list of users
*/
public List<User> listUser() {
LOGGER.info("list all users.");
List<User> users = new ArrayList<>();
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
List<User> userIter = session.createQuery("FROM User").list();
for (var iterator = userIter.iterator(); iterator.hasNext();) {
users.add(iterator.next());
}
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get users", e);
}
return users;
}
/**
* Add a user.
* @param user user entity
* @return user id
*/
public int createUser(User user) {
LOGGER.info("create user: " + user.getUsername());
var id = -1;
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
id = (Integer) session.save(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to create user", e);
}
LOGGER.info("create user " + user.getUsername() + " at " + id);
return id;
}
/**
* Update user.
* @param id user id
* @param user new user entity
*/
public void updateUser(Integer id, User user) {
LOGGER.info("update user at " + id);
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
user.setId(id);
session.update(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to update user", e);
}
}
/**
* Delete user.
* @param id user id
*/
public void deleteUser(Integer id) {
LOGGER.info("delete user at: " + id);
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
var user = session.get(User.class, id);
session.delete(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to delete user", e);
}
}
/**
* Get user.
* @param id user id
* @return deleted user
*/
public User getUser(Integer id) {
LOGGER.info("get user at: " + id);
User user = null;
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
user = session.get(User.class, id);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get user", e);
}
return user;
}
/**
* Close hibernate.
*/
public void close() {
HibernateUtil.shutdown();
}
}

View File

@ -1,39 +0,0 @@
package com.iluwatar.metamapping.utils;
import java.sql.SQLException;
import lombok.extern.slf4j.Slf4j;
import org.h2.jdbcx.JdbcDataSource;
/**
* Create h2 database.
*/
@Slf4j
public class DatabaseUtil {
private static final String DB_URL = "jdbc:h2:mem:metamapping";
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
+ "CREATE TABLE `user` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `username` varchar(255) NOT NULL,\n"
+ " `password` varchar(255) NOT NULL,\n"
+ " PRIMARY KEY (`id`)\n"
+ ");";
/**
* Hide constructor.
*/
private DatabaseUtil() {}
/**
* Create database.
*/
static {
LOGGER.info("create h2 database");
var source = new JdbcDataSource();
source.setURL(DB_URL);
try (var statement = source.getConnection().createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.error("unable to create h2 data source", e);
}
}
}

View File

@ -1,45 +0,0 @@
package com.iluwatar.metamapping.utils;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* Manage hibernate.
*/
@Slf4j
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
/**
* Hide constructor.
*/
private HibernateUtil() {}
/**
* Build session factory.
* @return session factory
*/
private static SessionFactory buildSessionFactory() {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
}
/**
* Get session factory.
* @return session factory
*/
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* Close session factory.
*/
public static void shutdown() {
// Close caches and connection pools
getSessionFactory().close();
}
}

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.iluwatar.metamapping.model.User" table="user">
<id name="id" type="java.lang.Integer" column="id">
<generator class="native"/>
</id>
<property name="username" column="username" type="java.lang.String"/>
<property name="password" column="password" type="java.lang.String"/>
</class>
</hibernate-mapping>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.url">jdbc:h2:mem:metamapping</property>
<property name="connection.driver_class">org.h2.Driver</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
</session-factory>
</hibernate-configuration>

View File

@ -1,20 +0,0 @@
package com.iluwatar.metamapping;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that metadata mapping 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 shouldExecuteMetaMappingWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -41,7 +41,7 @@
<zk.version>9.0.0</zk.version>
<guava.version>19.0</guava.version>
<jetty-maven-plugin.version>9.4.28.v20200408</jetty-maven-plugin.version>
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
<maven-war-plugin.version>2.1.1</maven-war-plugin.version>
<maven-assembly-plugin.version>2.2</maven-assembly-plugin.version>
<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
<packname>-${project.version}-FL-${maven.build.timestamp}</packname>

18
mvnw vendored
View File

@ -36,10 +36,6 @@
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
@ -149,7 +145,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
JAVACMD="`which java`"
fi
fi
@ -216,9 +212,9 @@ else
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
@ -237,9 +233,9 @@ else
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
@ -309,8 +305,6 @@ WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

66
mvnw.cmd vendored
View File

@ -1,21 +1,25 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM The MIT License
@REM Copyright © 2014-2021 Ilkka Seppälä
@REM
@REM Permission is hereby granted, free of charge, to any person obtaining a copy
@REM of this software and associated documentation files (the "Software"), to deal
@REM in the Software without restriction, including without limitation the rights
@REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
@REM copies of the Software, and to permit persons to whom the Software is
@REM furnished to do so, subject to the following conditions:
@REM
@REM The above copyright notice and this permission notice shall be included in
@REM all copies or substantial portions of the Software.
@REM
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
@REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
@REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
@REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
@REM THE SOFTWARE.
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@ -46,8 +50,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
@ -120,9 +124,9 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@ -134,7 +138,7 @@ if exist %WRAPPER_JAR% (
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
@ -158,13 +162,7 @@ if exist %WRAPPER_JAR% (
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
@ -174,15 +172,15 @@ set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@ -150,7 +150,7 @@
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<version>2.4</version>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>

View File

@ -75,7 +75,6 @@
<license-maven-plugin.version>3.0</license-maven-plugin.version>
<urm-maven-plugin.version>1.4.8</urm-maven-plugin.version>
<commons-io.version>2.7</commons-io.version>
<istack-commons-runtime.version>4.0.1</istack-commons-runtime.version>
<!-- SonarCloud -->
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>iluwatar</sonar.organization>
@ -228,7 +227,6 @@
<module>lockable-object</module>
<module>fanout-fanin</module>
<module>domain-model</module>
<module>metadata-mapping</module>
</modules>
<repositories>
<repository>
@ -379,11 +377,6 @@
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId>
<version>${istack-commons-runtime.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>

View File

@ -17,19 +17,19 @@ and to interleave the execution of functions without hard coding them together.
## Explanation
Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer
style. For example, calculating Fibonacci accumulating sum and factorials. In these kinds of
problems, recursion is more straightforward than its loop counterpart. Furthermore, recursion may
need less code and looks more concise. There is a saying that every recursion problem can be solved
using a loop with the cost of writing code that is more difficult to understand.
style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems
recursion is more straightforward than their loop counterpart. Furthermore recursion may need less
code and looks more concise. There is a saying that every recursion problem can be solved using
a loop with the cost of writing code that is more difficult to understand.
However, recursion-type solutions have one big caveat. For each recursive call, it typically needs
However recursion type solutions have one big caveat. For each recursive call it typically needs
an intermediate value stored and there is a limited amount of stack memory available. Running out of
stack memory creates a stack overflow error and halts the program execution.
Trampoline pattern is a trick that allows defining recursive algorithms in Java without blowing the
Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the
stack.
Real-world example
Real world example
> A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern.
@ -105,26 +105,24 @@ public interface Trampoline<T> {
Using the `Trampoline` to get Fibonacci values.
```java
public static void main(String[] args) {
LOGGER.info("Start calculating war casualties");
var result = loop(10, 1).result();
LOGGER.info("The number of orcs perished in the war: {}", result);
}
public static Trampoline<Integer> loop(int times, int prod) {
public static Trampoline<Integer> loop(int times, int prod) {
if (times == 0) {
return Trampoline.done(prod);
return Trampoline.done(prod);
} else {
return Trampoline.more(() -> loop(times - 1, prod * times));
return Trampoline.more(() -> loop(times - 1, prod * times));
}
}
}
log.info("start pattern");
var result = loop(10, 1).result();
log.info("result {}", result);
```
Program output:
```
19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties
19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800
start pattern
result 3628800
```
## Class diagram
@ -135,8 +133,8 @@ Program output:
Use the Trampoline pattern when
* For implementing tail-recursive functions. This pattern allows to switch on a stackless operation.
* For interleaving execution of two or more functions on the same thread.
* For implementing tail recursive function. This pattern allows to switch on a stackless operation.
* For interleaving the execution of two or more functions on the same thread.
## Known uses

View File

@ -107,4 +107,6 @@ public interface Trampoline<T> {
}
};
}
}

View File

@ -39,9 +39,9 @@ public class TrampolineApp {
* Main program for showing pattern. It does loop with factorial function.
*/
public static void main(String[] args) {
LOGGER.info("Start calculating war casualties");
LOGGER.info("start pattern");
var result = loop(10, 1).result();
LOGGER.info("The number of orcs perished in the war: {}", result);
LOGGER.info("result {}", result);
}
@ -55,4 +55,5 @@ public class TrampolineApp {
return Trampoline.more(() -> loop(times - 1, prod * times));
}
}
}

View File

@ -12,20 +12,20 @@ tags:
## Intent
When a business transaction is completed, all the updates are sent as one big unit of work to be
When a business transaction is completed, all the the updates are sent as one big unit of work to be
persisted in one go to minimize database round-trips.
## Explanation
Real-world example
Real world example
> Arms dealer has a database containing weapon information. Merchants all over the town are
> constantly updating this information and it causes a high load on the database server. To make the
> We have a database containing student information. Administrators all over the country are
> constantly updating this information and it causes high load on the database server. To make the
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
In plain words
> Unit of Work merges many small database updates in a single batch to optimize the number of
> Unit of Work merges many small database updates in single batch to optimize the number of
> round-trips.
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
@ -35,20 +35,37 @@ In plain words
**Programmatic Example**
Here's the `Weapon` entity that is being persisted in the database.
Here's the `Student` entity that is being persisted to the database.
```java
@Getter
@RequiredArgsConstructor
public class Weapon {
private final Integer id;
private final String name;
public class Student {
private final Integer id;
private final String name;
private final String address;
public Student(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Integer getId() {
return id;
}
public String getAddress() {
return address;
}
}
```
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern.
It maintains a map of database operations (`context`) that need to be done and when `commit` is
called it applies them in a single batch.
called it applies them in single batch.
```java
public interface IUnitOfWork<T> {
@ -67,117 +84,96 @@ public interface IUnitOfWork<T> {
}
@Slf4j
@RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> {
public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Weapon>> context;
private final WeaponDatabase weaponDatabase;
private final Map<String, List<Student>> context;
private final StudentDatabase studentDatabase;
@Override
public void registerNew(Weapon weapon) {
LOGGER.info("Registering {} for insert in context.", weapon.getName());
register(weapon, UnitActions.INSERT.getActionValue());
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
this.context = context;
this.studentDatabase = studentDatabase;
}
@Override
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, IUnitOfWork.INSERT);
}
@Override
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, IUnitOfWork.MODIFY);
}
@Override
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, IUnitOfWork.DELETE);
}
private void register(Student student, String operation) {
var studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(IUnitOfWork.INSERT)) {
commitInsert();
}
@Override
public void registerModified(Weapon weapon) {
LOGGER.info("Registering {} for modify in context.", weapon.getName());
register(weapon, UnitActions.MODIFY.getActionValue());
if (context.containsKey(IUnitOfWork.MODIFY)) {
commitModify();
}
@Override
public void registerDeleted(Weapon weapon) {
LOGGER.info("Registering {} for delete in context.", weapon.getName());
register(weapon, UnitActions.DELETE.getActionValue());
if (context.containsKey(IUnitOfWork.DELETE)) {
commitDelete();
}
LOGGER.info("Commit finished.");
}
private void register(Weapon weapon, String operation) {
var weaponsToOperate = context.get(operation);
if (weaponsToOperate == null) {
weaponsToOperate = new ArrayList<>();
}
weaponsToOperate.add(weapon);
context.put(operation, weaponsToOperate);
private void commitInsert() {
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
for (var student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
/**
* All UnitOfWork operations are batched and executed together on commit only.
*/
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
commitInsert();
}
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
commitModify();
}
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
commitDelete();
}
LOGGER.info("Commit finished.");
private void commitModify() {
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
for (var student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
weaponDatabase.insert(weapon);
}
}
private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
weaponDatabase.modify(weapon);
}
}
private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) {
LOGGER.info("Scrapping {}.", weapon.getName());
weaponDatabase.delete(weapon);
}
private void commitDelete() {
var deletedStudents = context.get(IUnitOfWork.DELETE);
for (var student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}
```
Here is how the whole app is put together.
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
```java
// create some weapons
var enchantedHammer = new Weapon(1, "enchanted hammer");
var brokenGreatSword = new Weapon(2, "broken great sword");
var silverTrident = new Weapon(3, "silver trident");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(), new WeaponDatabase());
// perform operations on the weapons
weaponRepository.registerNew(enchantedHammer);
weaponRepository.registerModified(silverTrident);
weaponRepository.registerDeleted(brokenGreatSword);
weaponRepository.commit();
```
Here is the console output.
```
21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished.
studentRepository.registerNew(ram);
studentRepository.registerModified(shyam);
studentRepository.registerDeleted(gopi);
studentRepository.commit();
```
## Class diagram
@ -190,7 +186,7 @@ Use the Unit Of Work pattern when
* To optimize the time taken for database transactions.
* To send changes to database as a unit of work which ensures atomicity of the transaction.
* To reduce the number of database calls.
* To reduce number of database calls.
## Tutorials

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<class id="1" language="java" name="com.iluwatar.unitofwork.WeaponDatabase" project="unit-of-work"
<class id="1" language="java" name="com.iluwatar.unitofwork.StudentDatabase" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="170" y="406"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
@ -28,7 +28,7 @@
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.unitofwork.ArmsDealer" project="unit-of-work"
<class id="4" language="java" name="com.iluwatar.unitofwork.StudentRepository" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="377" y="166"/>
@ -38,7 +38,7 @@
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.unitofwork.Weapon" project="unit-of-work"
<class id="5" language="java" name="com.iluwatar.unitofwork.Student" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="696" y="130"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"

View File

@ -27,7 +27,7 @@ import java.util.HashMap;
import java.util.List;
/**
* {@link App} Application demonstrating unit of work pattern.
* {@link App} Application for managing student data.
*/
public class App {
/**
@ -37,19 +37,17 @@ public class App {
*/
public static void main(String[] args) {
// create some weapons
var enchantedHammer = new Weapon(1, "enchanted hammer");
var brokenGreatSword = new Weapon(2, "broken great sword");
var silverTrident = new Weapon(3, "silver trident");
var ram = new Student(1, "Ram", "Street 9, Cupertino");
var shyam = new Student(2, "Shyam", "Z bridge, Pune");
var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
new WeaponDatabase());
var context = new HashMap<String, List<Student>>();
var studentDatabase = new StudentDatabase();
var studentRepository = new StudentRepository(context, studentDatabase);
// perform operations on the weapons
weaponRepository.registerNew(enchantedHammer);
weaponRepository.registerModified(silverTrident);
weaponRepository.registerDeleted(brokenGreatSword);
weaponRepository.commit();
studentRepository.registerNew(ram);
studentRepository.registerModified(shyam);
studentRepository.registerDeleted(gopi);
studentRepository.commit();
}
}

View File

@ -27,12 +27,14 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* {@link Weapon} is an entity.
* {@link Student} is an entity.
*/
@Getter
@RequiredArgsConstructor
public class Weapon {
public class Student {
private final Integer id;
private final String name;
private final String address;
}

View File

@ -24,19 +24,19 @@
package com.iluwatar.unitofwork;
/**
* Act as database for weapon records.
* Act as Database for student records.
*/
public class WeaponDatabase {
public class StudentDatabase {
public void insert(Weapon weapon) {
public void insert(Student student) {
//Some insert logic to DB
}
public void modify(Weapon weapon) {
public void modify(Student student) {
//Some modify logic to DB
}
public void delete(Weapon weapon) {
public void delete(Student student) {
//Some delete logic to DB
}
}

View File

@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* {@link ArmsDealer} Weapon repository that supports unit of work for weapons.
* {@link StudentRepository} Student database repository. supports unit of work for student data.
*/
@Slf4j
@RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> {
public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Weapon>> context;
private final WeaponDatabase weaponDatabase;
private final Map<String, List<Student>> context;
private final StudentDatabase studentDatabase;
@Override
public void registerNew(Weapon weapon) {
LOGGER.info("Registering {} for insert in context.", weapon.getName());
register(weapon, UnitActions.INSERT.getActionValue());
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, UnitActions.INSERT.getActionValue());
}
@Override
public void registerModified(Weapon weapon) {
LOGGER.info("Registering {} for modify in context.", weapon.getName());
register(weapon, UnitActions.MODIFY.getActionValue());
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, UnitActions.MODIFY.getActionValue());
}
@Override
public void registerDeleted(Weapon weapon) {
LOGGER.info("Registering {} for delete in context.", weapon.getName());
register(weapon, UnitActions.DELETE.getActionValue());
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, UnitActions.DELETE.getActionValue());
}
private void register(Weapon weapon, String operation) {
var weaponsToOperate = context.get(operation);
if (weaponsToOperate == null) {
weaponsToOperate = new ArrayList<>();
private void register(Student student, String operation) {
var studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
weaponsToOperate.add(weapon);
context.put(operation, weaponsToOperate);
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
/**
@ -90,26 +90,26 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
}
private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
weaponDatabase.insert(weapon);
var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
weaponDatabase.modify(weapon);
var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue());
for (var student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) {
LOGGER.info("Scrapping {}.", weapon.getName());
weaponDatabase.delete(weapon);
var deletedStudents = context.get(UnitActions.DELETE.getActionValue());
for (var student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}

View File

@ -36,102 +36,102 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* tests {@link ArmsDealer}
* tests {@link StudentRepository}
*/
class ArmsDealerTest {
private final Weapon weapon1 = new Weapon(1, "battle ram");
private final Weapon weapon2 = new Weapon(1, "wooden lance");
class StudentRepositoryTest {
private final Student student1 = new Student(1, "Ram", "street 9, cupertino");
private final Student student2 = new Student(1, "Sham", "Z bridge, pune");
private final Map<String, List<Weapon>> context = new HashMap<>();
private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class);
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);;
private final Map<String, List<Student>> context = new HashMap<>();
private final StudentDatabase studentDatabase = mock(StudentDatabase.class);
private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);;
@Test
void shouldSaveNewStudentWithoutWritingToDb() {
armsDealer.registerNew(weapon1);
armsDealer.registerNew(weapon2);
studentRepository.registerNew(student1);
studentRepository.registerNew(student2);
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveDeletedStudentWithoutWritingToDb() {
armsDealer.registerDeleted(weapon1);
armsDealer.registerDeleted(weapon2);
studentRepository.registerDeleted(student1);
studentRepository.registerDeleted(student2);
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveModifiedStudentWithoutWritingToDb() {
armsDealer.registerModified(weapon1);
armsDealer.registerModified(weapon2);
studentRepository.registerModified(student1);
studentRepository.registerModified(student2);
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveAllLocalChangesToDb() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, times(1)).insert(weapon1);
verify(weaponDatabase, times(1)).modify(weapon1);
verify(weaponDatabase, times(1)).delete(weapon1);
verify(studentDatabase, times(1)).insert(student1);
verify(studentDatabase, times(1)).modify(student1);
verify(studentDatabase, times(1)).delete(student1);
}
@Test
void shouldNotWriteToDbIfContextIsNull() {
var weaponRepository = new ArmsDealer(null, weaponDatabase);
var studentRepository = new StudentRepository(null, studentDatabase);
weaponRepository.commit();
studentRepository.commit();
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldNotWriteToDbIfNothingToCommit() {
var weaponRepository = new ArmsDealer(new HashMap<>(), weaponDatabase);
var studentRepository = new StudentRepository(new HashMap<>(), studentDatabase);
weaponRepository.commit();
studentRepository.commit();
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).insert(weapon1);
verify(studentDatabase, never()).insert(student1);
}
@Test
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).modify(weapon1);
verify(studentDatabase, never()).modify(student1);
}
@Test
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).delete(weapon1);
verify(studentDatabase, never()).delete(student1);
}
}

View File

@ -10,80 +10,19 @@ tags:
---
## Intent
Provide objects which follow value semantics rather than reference semantics.
This means value objects' equality is not based on identity. Two value objects are
This means value objects' equality are not based on identity. Two value objects are
equal when they have the same value, not necessarily being the same object.
## Explanation
Real-world example
> There is a class for hero statistics in a role-playing game. The statistics contain attributes
> such as strength, intelligence, and luck. The statistics of different heroes should be equal
> when all the attributes are equal.
In plain words
> Value objects are equal when their attributes have the same value
Wikipedia says
> In computer science, a value object is a small object that represents a simple entity whose
> equality is not based on identity: i.e. two value objects are equal when they have the same
> value, not necessarily being the same object.
**Programmatic Example**
Here is the `HeroStat` class that is the value object. Notice the use of
[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation.
```java
@Value(staticConstructor = "valueOf")
class HeroStat {
int strength;
int intelligence;
int luck;
}
```
The example creates three different `HeroStat`s and compares their equality.
```java
var statA = HeroStat.valueOf(10, 5, 0);
var statB = HeroStat.valueOf(10, 5, 0);
var statC = HeroStat.valueOf(5, 1, 8);
LOGGER.info(statA.toString());
LOGGER.info(statB.toString());
LOGGER.info(statC.toString());
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
```
Here's the console output.
```
20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true
20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false
```
## Class diagram
![alt text](./etc/value-object.png "Value Object")
## Applicability
Use the Value Object when
* The object's equality needs to be based on the object's value
* You need to measure the objects' equality based on the objects' value
## Known uses
## Real world examples
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
@ -92,7 +31,6 @@ Use the Value Object when
## Credits
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
* [ValueObject](https://martinfowler.com/bliki/ValueObject.html)
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)
* [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=f27d2644fbe5026ea448791a8ad09c94)

View File

@ -43,7 +43,7 @@ import lombok.extern.slf4j.Slf4j;
public class App {
/**
* This example creates three HeroStats (value objects) and checks equality between those.
* This practice creates three HeroStats(Value object) and checks equality between those.
*/
public static void main(String[] args) {
var statA = HeroStat.valueOf(10, 5, 0);
@ -51,8 +51,6 @@ public class App {
var statC = HeroStat.valueOf(5, 1, 8);
LOGGER.info(statA.toString());
LOGGER.info(statB.toString());
LOGGER.info(statC.toString());
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));

View File

@ -23,7 +23,10 @@
package com.iluwatar.value.object;
import lombok.Value;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
/**
* HeroStat is a value object.
@ -32,10 +35,23 @@ import lombok.Value;
* http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
* </a>
*/
@Value(staticConstructor = "valueOf")
class HeroStat {
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
public class HeroStat {
// Stats for a hero
private final int strength;
private final int intelligence;
private final int luck;
// Static factory method to create new instances.
public static HeroStat valueOf(int strength, int intelligence, int luck) {
return new HeroStat(strength, intelligence, luck);
}
// The clone() method should not be public. Just don't override it.
int strength;
int intelligence;
int luck;
}