mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-13 14:16:02 +00:00
refactoring: unit of work (#1940)
Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
This commit is contained in:
1 parent
11f20593b2
commit
3cc9bc2dea
7 files changed
+205
-201
No files matched your search
+110
-106
@@ -12,20 +12,20 @@ tags:
|
||||
|
||||
## Intent
|
||||
|
||||
When a business transaction is completed, all the the updates are sent as one big unit of work to be
|
||||
When a business transaction is completed, all 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
|
||||
|
||||
> 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
|
||||
> 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
|
||||
> 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 single batch to optimize the number of
|
||||
> Unit of Work merges many small database updates in a single batch to optimize the number of
|
||||
> round-trips.
|
||||
|
||||
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
|
||||
@@ -35,37 +35,20 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Here's the `Student` entity that is being persisted to the database.
|
||||
Here's the `Weapon` entity that is being persisted in the database.
|
||||
|
||||
```java
|
||||
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;
|
||||
}
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Weapon {
|
||||
private final Integer id;
|
||||
private final String name;
|
||||
}
|
||||
```
|
||||
|
||||
The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern.
|
||||
The essence of the implementation is the `ArmsDealer` 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 single batch.
|
||||
called it applies them in a single batch.
|
||||
|
||||
```java
|
||||
public interface IUnitOfWork<T> {
|
||||
@@ -84,96 +67,117 @@ public interface IUnitOfWork<T> {
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class StudentRepository implements IUnitOfWork<Student> {
|
||||
@RequiredArgsConstructor
|
||||
public class ArmsDealer implements IUnitOfWork<Weapon> {
|
||||
|
||||
private final Map<String, List<Student>> context;
|
||||
private final StudentDatabase studentDatabase;
|
||||
private final Map<String, List<Weapon>> context;
|
||||
private final WeaponDatabase weaponDatabase;
|
||||
|
||||
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 registerNew(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for insert in context.", weapon.getName());
|
||||
register(weapon, UnitActions.INSERT.getActionValue());
|
||||
}
|
||||
|
||||
if (context.containsKey(IUnitOfWork.MODIFY)) {
|
||||
commitModify();
|
||||
}
|
||||
if (context.containsKey(IUnitOfWork.DELETE)) {
|
||||
commitDelete();
|
||||
}
|
||||
LOGGER.info("Commit finished.");
|
||||
}
|
||||
@Override
|
||||
public void registerModified(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for modify in context.", weapon.getName());
|
||||
register(weapon, UnitActions.MODIFY.getActionValue());
|
||||
|
||||
private void commitInsert() {
|
||||
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
|
||||
for (var student : studentsToBeInserted) {
|
||||
LOGGER.info("Saving {} to database.", student.getName());
|
||||
studentDatabase.insert(student);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitModify() {
|
||||
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
|
||||
for (var student : modifiedStudents) {
|
||||
LOGGER.info("Modifying {} to database.", student.getName());
|
||||
studentDatabase.modify(student);
|
||||
@Override
|
||||
public void registerDeleted(Weapon weapon) {
|
||||
LOGGER.info("Registering {} for delete in context.", weapon.getName());
|
||||
register(weapon, UnitActions.DELETE.getActionValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void commitDelete() {
|
||||
var deletedStudents = context.get(IUnitOfWork.DELETE);
|
||||
for (var student : deletedStudents) {
|
||||
LOGGER.info("Deleting {} to database.", student.getName());
|
||||
studentDatabase.delete(student);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
|
||||
Here is how the whole app is put together.
|
||||
|
||||
```java
|
||||
studentRepository.registerNew(ram);
|
||||
studentRepository.registerModified(shyam);
|
||||
studentRepository.registerDeleted(gopi);
|
||||
studentRepository.commit();
|
||||
// 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.
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@@ -186,7 +190,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 number of database calls.
|
||||
* To reduce the number of database calls.
|
||||
|
||||
## Tutorials
|
||||
|
||||
|
||||
Reference in new issue
Block a user