|
1 | 1 | package com.clevergang.dbtests; |
2 | 2 |
|
3 | | -import java.math.BigDecimal; |
4 | | -import java.time.LocalDate; |
5 | | -import java.util.ArrayList; |
6 | | -import java.util.List; |
7 | | - |
8 | 3 | import com.clevergang.dbtests.model.Company; |
9 | 4 | import com.clevergang.dbtests.model.Department; |
10 | 5 | import com.clevergang.dbtests.model.Employee; |
11 | 6 | import com.clevergang.dbtests.model.Project; |
12 | 7 | import com.clevergang.dbtests.repository.DataRepository; |
| 8 | +import org.apache.commons.collections4.CollectionUtils; |
13 | 9 | import org.apache.commons.lang3.RandomStringUtils; |
14 | 10 | import org.slf4j.Logger; |
15 | 11 | import org.slf4j.LoggerFactory; |
16 | 12 | import org.springframework.util.StopWatch; |
17 | 13 |
|
| 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 | + |
18 | 21 | /** |
19 | 22 | * Common scenarios |
20 | 23 | * |
@@ -70,7 +73,7 @@ public void scenarioFour() { |
70 | 73 | project.setDate(LocalDate.now()); |
71 | 74 | projects.add(project); |
72 | 75 | } |
73 | | - repository.insertAllProjects(projects); |
| 76 | + repository.insertProjects(projects); |
74 | 77 | watch.stop(); |
75 | 78 | logger.info("Total time {} ms", watch.getTotalTimeMillis()); |
76 | 79 | } |
@@ -121,15 +124,88 @@ public void scenarioSeven() { |
121 | 124 | // a lazy select is issued and Departments are fetched from DB. However you can also use EAGER FetchType strategy which |
122 | 125 | // causes the relation to load along with the primary entity - this behavior is (by our opinion) the source of all evil |
123 | 126 | // 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); |
125 | 128 |
|
126 | 129 | logger.info("There are {} departments in {} company", departments.size(), company.getName()); |
127 | 130 | } |
128 | 131 |
|
| 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 | + */ |
129 | 143 | 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); |
130 | 153 |
|
| 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; |
131 | 206 | } |
132 | 207 | } |
133 | 208 |
|
134 | 209 |
|
135 | 210 |
|
| 211 | + |
0 commit comments