Skip to content

Commit 320fee8

Browse files
author
bwajtr
committed
Implementation of scenario 8 + improvements of names of several methods in the DataRepository interface
1 parent 1839d00 commit 320fee8

9 files changed

Lines changed: 316 additions & 91 deletions

File tree

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2016 bwajtr
3+
Copyright (c) 2016 Bretislav Wajtr
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

dbscenarios.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
5. Update single existing entity - update all fields of entity at once
66
6. Fetch entity and it's many-to-one relation (Company for Department)
77
7. Fetch entity and it's one-to-many relation (Departments for Company)
8-
8. Update entitie's one-to-many relation - add two items, update two items and delete one item
8+
8. Update entities one-to-many relation - add two items, update two items and delete one item
99
9. Complex select - construct select where conditions based on some boolean conditions + throw in some outer joins
1010
10. Transactions handling
1111
11. Call stored procedure

pom.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,22 @@
4141
<dependency>
4242
<groupId>org.postgresql</groupId>
4343
<artifactId>postgresql</artifactId>
44-
<version>9.4-1201-jdbc41</version>
45-
<scope>runtime</scope>
44+
<version>9.4.1211.jre7</version>
4645
</dependency>
4746

4847
<dependency>
4948
<groupId>org.apache.commons</groupId>
5049
<artifactId>commons-lang3</artifactId>
5150
<version>3.4</version>
5251
</dependency>
52+
53+
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
54+
<dependency>
55+
<groupId>org.apache.commons</groupId>
56+
<artifactId>commons-collections4</artifactId>
57+
<version>4.1</version>
58+
</dependency>
59+
5360
</dependencies>
5461

5562
<build>

sql-updates/create-script.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- Meant for PostgreSQL --
1+
-- Meant for PostgreSQL 9.5 --
22

33
-- (re)create tables first
44

@@ -53,6 +53,7 @@ INSERT INTO Department (company_pid, name) VALUES (1, 'Software Development');
5353
INSERT INTO Department (company_pid, name) VALUES (2, 'Help desk');
5454
INSERT INTO Department (company_pid, name) VALUES (2, 'Sales');
5555
INSERT INTO Department (company_pid, name) VALUES (3, 'Hardware Development');
56+
INSERT INTO Department (company_pid, name) VALUES (1, 'Lazy Department');
5657

5758
-- names generated using listofrandomnames.com ;)
5859
INSERT INTO Employee (department_pid, name, surname, email, salary)

src/main/java/com/clevergang/dbtests/Scenarios.java

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
package com.clevergang.dbtests;
22

3-
import java.math.BigDecimal;
4-
import java.time.LocalDate;
5-
import java.util.ArrayList;
6-
import java.util.List;
7-
83
import com.clevergang.dbtests.model.Company;
94
import com.clevergang.dbtests.model.Department;
105
import com.clevergang.dbtests.model.Employee;
116
import com.clevergang.dbtests.model.Project;
127
import com.clevergang.dbtests.repository.DataRepository;
8+
import org.apache.commons.collections4.CollectionUtils;
139
import org.apache.commons.lang3.RandomStringUtils;
1410
import org.slf4j.Logger;
1511
import org.slf4j.LoggerFactory;
1612
import org.springframework.util.StopWatch;
1713

14+
import java.math.BigDecimal;
15+
import java.time.LocalDate;
16+
import java.util.*;
17+
import java.util.stream.Collectors;
18+
19+
import static java.util.stream.Collectors.toList;
20+
1821
/**
1922
* Common scenarios
2023
*
@@ -70,7 +73,7 @@ public void scenarioFour() {
7073
project.setDate(LocalDate.now());
7174
projects.add(project);
7275
}
73-
repository.insertAllProjects(projects);
76+
repository.insertProjects(projects);
7477
watch.stop();
7578
logger.info("Total time {} ms", watch.getTotalTimeMillis());
7679
}
@@ -121,15 +124,88 @@ public void scenarioSeven() {
121124
// a lazy select is issued and Departments are fetched from DB. However you can also use EAGER FetchType strategy which
122125
// causes the relation to load along with the primary entity - this behavior is (by our opinion) the source of all evil
123126
// in JPA... So, in non-JPA approach, we don't have any "eager" loads, just explicit calls for data:
124-
List<Department> departments = repository.getDepartmentsForCompany(company.getPid());
127+
List<Department> departments = repository.findDepartmentsOfCompany(company);
125128

126129
logger.info("There are {} departments in {} company", departments.size(), company.getName());
127130
}
128131

132+
/**
133+
* Update one-to-many relation (Departments in Company) at once - add two items, update one items and delete one item.
134+
* <br/>
135+
* This scenario covers situation where we have no idea what operations were performed by the user. We only
136+
* have new list of Departments and we have to efficiently update DB so it exactly reflects new Departments list.
137+
* <br/>
138+
* At the same time, we DON'T want to take the tempting path and do it by "removing all the existing departments of the company from
139+
* database and then inserting new values". This is actually not what the user did nor it's what we should do -> what if the departments
140+
* which were only updated have some additional relations in the database? -> if we delete them, we will delete also those relations ->
141+
* always ask yourself if this is something you want (or you want risk).
142+
*/
129143
public void scenarioEight() {
144+
Company company = repository.findCompany(1); // Clevergang company
145+
146+
// this call simulates what typically happens in UI - the user chooses new list of departments for company
147+
// (adds new ones, updates some other or removes some departments). The new list is transferred from
148+
// UI to business service a List<Department> with no information about what departments were deleted or which
149+
// ones were updated - the business service has to determine these changes - which is what rest of the code in this method does
150+
List<Department> newDepartments = createNewDepartmentsList(company);
151+
152+
updateDepartments(company, newDepartments);
130153

154+
// now check the results
155+
List<Department> departmentsForCompany = repository.findDepartmentsOfCompany(company);
156+
logger.info("State of database at the end of scenario eight: {}", departmentsForCompany);
157+
}
158+
159+
private void updateDepartments(Company company, List<Department> newDepartments) {
160+
// --------------------------------------------------------------------------------------------------------------------------------------
161+
// The pattern for one-to-many relation update begins here - but BEWARE, it'll work nicely only for few items in the one to many relation
162+
// --------------------------------------------------------------------------------------------------------------------------------------
163+
164+
// first get current departments
165+
List<Department> currentDepartments = repository.findDepartmentsOfCompany(company);
166+
167+
// now determine which departments were deleted
168+
Collection<Integer> newPIDs = CollectionUtils.collect(newDepartments, Department::getPid);
169+
List<Department> deletedDepartments = currentDepartments.stream()
170+
.filter(department -> !newPIDs.contains(department.getPid()))
171+
.collect(toList());
172+
173+
// ... and which were added or updated
174+
Map<Boolean, List<Department>> addedOrUpdatedDepartments = newDepartments.stream()
175+
.filter(department -> !currentDepartments.contains(department)) // filtering out not changed items, assumes that equals and hashCode is properly coded in Department class
176+
.collect(Collectors.partitioningBy(d -> d.getPid() == null)); // split (partition) by new or updated (pid is either null or not)
177+
List<Department> addedDepartments = addedOrUpdatedDepartments.get(Boolean.TRUE);
178+
List<Department> updatedDepartments = addedOrUpdatedDepartments.get(Boolean.FALSE);
179+
180+
// now perform relevant operations on each list:
181+
repository.deleteDepartments(deletedDepartments);
182+
repository.insertDepartments(addedDepartments);
183+
repository.updateDepartments(updatedDepartments);
184+
}
185+
186+
private List<Department> createNewDepartmentsList(Company company) {
187+
List<Department> departments = repository.findDepartmentsOfCompany(company);
188+
189+
// delete one item
190+
departments.removeIf(department -> department.getName().equals("Lazy Department"));
191+
192+
// add two items (notice they don't have thier own pid yet)
193+
departments.add(new Department(company.getPid(), "New department 1"));
194+
departments.add(new Department(company.getPid(), "New department 2"));
195+
196+
// update one item
197+
Optional<Department> itDepartment = departments.stream()
198+
.filter(it -> it.getName().equals("IT Department"))
199+
.findFirst();
200+
201+
if (itDepartment.isPresent()) {
202+
itDepartment.get().setName("IT Department Updated");
203+
}
204+
205+
return departments;
131206
}
132207
}
133208

134209

135210

211+

src/main/java/com/clevergang/dbtests/model/Department.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ public void setCompanyPid(Integer company_pid) {
3232
this.company_pid = company_pid;
3333
}
3434

35+
public Department() {
36+
}
37+
38+
public Department(Integer company_pid, String name) {
39+
this.company_pid = company_pid;
40+
this.name = name;
41+
}
42+
3543
@Override
3644
public boolean equals(Object o) {
3745
if (this == o)
Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package com.clevergang.dbtests.repository;
22

3-
import java.util.List;
4-
53
import com.clevergang.dbtests.model.Company;
64
import com.clevergang.dbtests.model.Department;
75
import com.clevergang.dbtests.model.Employee;
86
import com.clevergang.dbtests.model.Project;
97

8+
import java.util.List;
9+
1010
/**
1111
* In "normal" application we would rather split the repository to multiple classes based on entity
1212
* we want to work with (CompanyRepository, DepartmentRepository etc.). We decided
@@ -16,17 +16,32 @@
1616
*/
1717
public interface DataRepository {
1818

19+
// companies
20+
1921
Company findCompany(Integer pid);
2022

23+
// departments
24+
2125
Department findDepartment(Integer pid);
2226

23-
List<Employee> employeesWithSalaryGreaterThan(Integer minSalary);
27+
List<Department> findDepartmentsOfCompany(Company company);
28+
29+
void deleteDepartments(List<Department> departmentsToDelete);
30+
31+
void updateDepartments(List<Department> departmentsToUpdate);
32+
33+
void insertDepartments(List<Department> departmentsToInsert);
34+
35+
// projects
2436

2537
Integer insertProject(Project project);
2638

27-
List<Integer> insertAllProjects(List<Project> projects);
39+
List<Integer> insertProjects(List<Project> projects);
40+
41+
// employees
42+
43+
List<Employee> employeesWithSalaryGreaterThan(Integer minSalary);
2844

2945
void updateEmployee(Employee employeeToUpdate);
3046

31-
List<Department> getDepartmentsForCompany(Integer pid);
3247
}

0 commit comments

Comments
 (0)