Skip to content

Commit b019c9c

Browse files
author
bwajtr
committed
Updated documentation in Scenarios class + uppdated README.md
Did some method renaming and refactoring
1 parent 9b687bf commit b019c9c

5 files changed

Lines changed: 106 additions & 92 deletions

File tree

README.md

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Java repository layer frameworks comparison
2+
23
Comparison of usage of non-JPA SQL mapping frameworks for Java (Jooq, Spring JDBCTemplate etc.).
34

45
We are not comparing performance (that'll be maybe added later), but rather how are these frameworks used for everyday tasks.
@@ -15,39 +16,36 @@ We tried to find optimal (== most readable) implementation in every framework, b
1516
## Scenarios implemented
1617

1718
These are the scenarios:
19+
1820
1. Fetch single entity based on primary key
1921
2. Fetch list of entities based on condition
2022
3. Save new single entity and return primary key
2123
4. Batch insert multiple entities of same type and return generated keys
2224
5. Update single existing entity - update all fields of entity at once
23-
6. Fetch entity and it's many-to-one relation (Company for Department)
24-
7. Fetch entity and it's one-to-many relation (Departments for Company)
25-
8. Update entities one-to-many relation - add two items, update two items and delete one item
25+
6. Fetch many-to-one relation (Company for Department)
26+
7. Fetch one-to-many relation (Departments for Company)
27+
8. Update entities one-to-many relation (Departments in Company) - add two items, update two items and delete one item - all at once
2628
9. Complex select - construct select where conditions based on some boolean conditions + throw in some joins
2729
10. Call stored procedure/function and process results
2830
11. Execute query using JDBC simple Statement (not PreparedStatement)
2931

30-
See javadoc of [Scenarios](src/main/java/com/clevergang/dbtests/Scenarios.java) class for description of each scenario.
32+
Each scenario has it's implementation in the Scenarios class. See javadoc of [Scenarios](src/main/java/com/clevergang/dbtests/Scenarios.java) methods for more detailed description of each scenario.
3133

3234
## Model used
3335

3436
![Simple company database model](/SimpleCompanyModel.png?raw=true "Simple company database model")
3537

3638
## Why only non-JPA?
37-
Well, we were trying to "stick with standard" in our projects so we used JPA in the past, but after many years of JPA usage
38-
(Hibernate mostly), we realized it's counterproductive. In most of our projects it caused more problems than
39-
it helped to solve - especially in big projects (with lots of tables and relations).
40-
There are many reasons of those failures - but the biggest issue is that JPA implementations simply turned into bloatware.
41-
Lot of strange magic is happening inside and the complexity is so high, that you need a high-class Hibernate uberexpert
42-
in every team so the app actually shows some performance and the code is manageable...
43-
44-
So we dropped JPA completely, started using JDBCTemplate and discovered that we can deliver apps sooner
45-
(which was kind of surprising), they are a lot faster (thanks to effective use of DB) and much more robust...
46-
This was really relaxing and we do not plan to return to JPA at all... (yes, even for CRUD applications!)
47-
48-
This project aims to explore other options in the SQL mapping area than just JDBCTemplate. It should serve us
39+
40+
Well, we were trying to "stick with standard" in our projects so we used JPA in the past, but after many years of JPA usage (Hibernate mostly), we realized it's counterproductive. In most of our projects it caused more problems than it helped to solve - especially in big projects (with lots of tables and relations). There are many reasons of those failures - but the biggest issue is that JPA implementations simply turned into bloatware. Lot of strange magic is happening inside and the complexity is so high, that you need a high-class Hibernate uberexpert in every team so the app actually shows some performance and the code is manageable...
41+
42+
So we dropped JPA completely, started using JDBCTemplate and discovered that we can deliver apps sooner (which was kind of surprising), they are a lot faster (thanks to effective use of DB) and much more robust... This was really relaxing and we do not plan to return to JPA at all... (yes, even for CRUD applications!)
43+
44+
This project aims to explore other options in the SQL mapping area than just JDBCTemplate. It should serve us
45+
4946
- as point of reference when deciding for SQL mapping framework
5047
- as a template of common DB usage scenarios
5148
- to document best practices of such common usages (**comments are welcomed!**)
5249

53-
Use code in the repository as you like (MIT License)
50+
51+
**Use code in the repository as you like (MIT License)**

dbscenarios.txt

Lines changed: 0 additions & 11 deletions
This file was deleted.

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

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

3-
import com.clevergang.dbtests.repository.api.data.*;
43
import com.clevergang.dbtests.repository.api.DataRepository;
4+
import com.clevergang.dbtests.repository.api.data.*;
55
import org.apache.commons.collections4.CollectionUtils;
66
import org.apache.commons.lang3.RandomStringUtils;
77
import org.slf4j.Logger;
@@ -27,32 +27,41 @@
2727
public class Scenarios {
2828
private static final Logger logger = LoggerFactory.getLogger(Scenarios.class);
2929

30-
private DataRepository repository;
30+
private final DataRepository repository;
3131

3232
public Scenarios(DataRepository repository) {
3333
this.repository = repository;
3434
}
3535

3636
/**
37-
* Load single entity based on primary key
37+
* 1. Fetch single entity based on primary key
38+
* <br/>
39+
* This is the case when pid comes from outside (typically from UI) and we need to fetch complete record from database.
40+
*
41+
* @param companyPid Primary key of the record coming from outside
3842
*/
39-
public void scenarioOne() {
40-
Integer pid = 1;
41-
repository.findCompany(pid);
43+
public void fetchSingleEntityScenario(Integer companyPid) {
44+
Company company = repository.findCompany(companyPid);
45+
46+
logger.info("Fetched result: {}", company);
4247
}
4348

4449
/**
45-
* Load list of entities based on condition
50+
* 2. Fetch list of entities based on condition
51+
* <br/>
52+
* This is a case when we want to get records from database using some kind of filter. Filter values typically come from UI.
53+
* @param employeeMinSalary Example of external filter value
4654
*/
47-
public void scenarioTwo() {
48-
Integer minSalary = 2000;
49-
repository.employeesWithSalaryGreaterThan(minSalary);
55+
public void fetchListOfEntitiesScenario(Integer employeeMinSalary) {
56+
List<Employee> employees = repository.employeesWithSalaryGreaterThan(employeeMinSalary);
57+
58+
logger.info("Fetched result: {}", employees);
5059
}
5160

5261
/**
53-
* Save new single entity and return primary key
62+
* 3. Save new single entity and return primary key
5463
*/
55-
public void scenarioThree() {
64+
public void saveNewEntityScenario() {
5665
Project project = new Project();
5766
project.setName("TestProject");
5867
project.setDate(LocalDate.now());
@@ -61,9 +70,15 @@ public void scenarioThree() {
6170
}
6271

6372
/**
64-
* Batch insertProject multiple entities of same type and return generated keys
73+
* 4. Batch insert multiple entities of same type and return generated keys
74+
* <br/>
75+
* This scenario represents a situation, when business method, as a result of it's execution, wants to store
76+
* multiple records of same type into database effectively.
77+
* </br>
78+
* In our scenario method, we create 1000 products first and then we want to store them into the database
79+
* as fast as we can - through batch insert functionality.
6580
*/
66-
public void scenarioFour() {
81+
public void batchInsertMultipleEntitiesScenario() {
6782
// create a list of thousand products
6883
List<Project> projects = new ArrayList<>();
6984
StopWatch watch = new StopWatch();
@@ -80,28 +95,37 @@ public void scenarioFour() {
8095
}
8196

8297
/**
83-
* Update single existing entity - update all fields of entity at once
98+
* 5. Update single existing entity - update all fields of entity at once
99+
* <br/>
100+
* This scenario covers typical situation in information systems where a detail of a record is displayed in UI, user
101+
* has possibility to modify any field of the record and then he/she presses Save button -> complete record data are sent
102+
* back to server and the record should be updated in database.
84103
*/
85-
public void scenarioFive() {
86-
// Complete already store entity with all fields set - imagine that this object comes from UI edit dialog,
87-
// which is typical scenario
104+
public void updateCompleteEntityScenario() {
105+
// Imagine that this object comes from UI edit dialog, which is typical scenario
106+
Employee employeeToUpdate = performSomeEmployeeRecordUpdateInUI();
107+
108+
// SCENARIO CODE STARTS HERE - update the employee in DB
109+
110+
repository.updateEmployee(employeeToUpdate);
111+
112+
}
113+
114+
private Employee performSomeEmployeeRecordUpdateInUI() {
88115
Employee employeeToUpdate = new Employee();
89116
employeeToUpdate.setPid(1);
90117
employeeToUpdate.setDepartmentPid(1);
91118
employeeToUpdate.setName("Curt");
92119
employeeToUpdate.setSurname("Odegaard");
93120
employeeToUpdate.setEmail("curt.odegaard@updated.com"); // <-- this is updated value
94121
employeeToUpdate.setSalary(new BigDecimal("15000")); // <-- this is updated value
95-
96-
// now update the employee in DB
97-
repository.updateEmployee(employeeToUpdate);
98-
122+
return employeeToUpdate;
99123
}
100124

101125
/**
102-
* Fetch many-to-one relation (Company for Department)
126+
* 6. Fetch many-to-one relation (Company for Department)
103127
*/
104-
public void scenarioSix() {
128+
public void fetchManyToOneRelationScenario() {
105129
Department softwareDevelopmentDepartment = repository.findDepartment(3);
106130

107131
// Getting Company for Department (many-to-one relation) in JPA is quite easy. You typically have
@@ -115,9 +139,9 @@ public void scenarioSix() {
115139
}
116140

117141
/**
118-
* Fetch one-to-many relation (Departments for Company)
142+
* 7. Fetch one-to-many relation (Departments for Company)
119143
*/
120-
public void scenarioSeven() {
144+
public void fetchOneToManyRelationScenario() {
121145
Company company = repository.findCompany(1);
122146

123147
// For one-to-many relations the situation is quite similar to many-to-one relations (scenario six). In JPA this
@@ -131,7 +155,7 @@ public void scenarioSeven() {
131155
}
132156

133157
/**
134-
* Update one-to-many relation (Departments in Company) at once - add two items, update one items and delete one item.
158+
* 8. Update entities one-to-many relation (Departments in Company) - add two items, update two items and delete one item - all at once
135159
* <br/>
136160
* This scenario covers situation where we have no idea what operations were performed by the user. We only
137161
* have new list of Departments and we have to efficiently update DB so it exactly reflects new Departments list.
@@ -141,7 +165,7 @@ public void scenarioSeven() {
141165
* which were only updated have some additional relations in the database? -> if we delete them, we will delete also those relations ->
142166
* always ask yourself if this is something you want (or you want risk).
143167
*/
144-
public void scenarioEight() {
168+
public void updateCompleteOneToManyRelationScenario() {
145169
Company company = repository.findCompany(1); // Clevergang company
146170

147171
// this call simulates what typically happens in UI - the user chooses new list of departments for company
@@ -150,6 +174,9 @@ public void scenarioEight() {
150174
// ones were updated - the business service has to determine these changes - which is what rest of the code in this method does
151175
List<Department> newDepartments = createNewDepartmentsList(company);
152176

177+
178+
// SCENARIO CODE STARTS HERE - update departments in DB
179+
153180
updateDepartments(company, newDepartments);
154181

155182
// now check the results
@@ -190,7 +217,7 @@ private List<Department> createNewDepartmentsList(Company company) {
190217
// delete one item
191218
departments.removeIf(department -> department.getName().equals("Lazy Department"));
192219

193-
// add two items (notice they don't have thier own pid yet)
220+
// add two items (notice they don't have their own pid yet)
194221
departments.add(new Department(company.getPid(), "New department 1"));
195222
departments.add(new Department(company.getPid(), "New department 2"));
196223

@@ -208,41 +235,41 @@ private List<Department> createNewDepartmentsList(Company company) {
208235

209236

210237
/**
211-
* Execute complex query
212-
*
213-
* In our case we are executing following query:
214-
* Query: get all projects, where the total cost of the project per month is greater than 70000. In the same resultset
238+
* 9. Complex select - construct select where conditions based on some boolean conditions + throw in some joins
239+
* <br/>
240+
* In our case we are executing following query:<br/>
241+
* Query: get all projects, where the total cost of the project per month is greater than 70000. In the same result set
215242
* get all companies participating on such project along with cost of the project for the company.
216243
*/
217-
public void scenarioNine() {
244+
public void executeComplexSelectScenario() {
218245
List<ProjectsWithCostsGreaterThanOutput> projectsWithCostsGreaterThan = repository.getProjectsWithCostsGreaterThan(70000);
219246

220-
logger.info("scenarioNine output: {}", projectsWithCostsGreaterThan);
247+
logger.info("executeComplexSelectScenario output: {}", projectsWithCostsGreaterThan);
221248
}
222249

223250
/**
224-
* Execute stored procedure/function and process results
251+
* 10. Call stored procedure/function and process results
225252
*/
226-
public void scenarioTen() {
253+
public void callStoredProcedureScenario() {
227254
RegisterEmployeeOutput output = repository.callRegisterEmployee("Bretislav", "Wajtr", "bretislav.wajtr@test.com", new BigDecimal(40000), "MyDepartment", "MyCompany");
228255

229-
logger.info("scenarioTen output: {}", output);
256+
logger.info("callStoredProcedureScenario output: {}", output);
230257
}
231258

232259

233260
/**
234-
* Execute query using JDBC simple Statement (not PreparedStatement)
235-
*
261+
* 11. Execute query using JDBC simple Statement (not PreparedStatement)
262+
* <br/>
236263
* Motivation why we need the "static statement" feature: In 96% of the cases, you’re better off writing
237264
* a PreparedStatement rather than a static statement - it's safer (sql injection),
238265
* easier (complex data types like dates) and sometimes faster (prepared statements reuse). However, there are
239266
* edge cases for complex queries and lot of data where it's actually faster to use simple statement query, because
240267
* your database’s cost-based optimiser or planner obtains some heads-up about what kind of data is really going to
241268
* be affected by the query and can therefore execute the query faster.
242-
*
269+
* <p>
243270
* Good SQL API framework should offer way how to execute simple static statements.
244271
*/
245-
public void scenarioEleven() {
272+
public void executeSimpleStaticStatementScenario() {
246273
Company output = repository.findCompanyUsingSimpleStaticStatement(1);
247274

248275
logger.info("Output: {}", output);

src/test/java/com/clevergang/dbtests/JdbcTemplateScenariosTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,57 +30,57 @@ public void setup() {
3030

3131
@Test
3232
public void scenarioOne() {
33-
scenarios.scenarioOne();
33+
scenarios.fetchSingleEntityScenario(1);
3434
}
3535

3636
@Test
3737
public void scenarioTwo() {
38-
scenarios.scenarioTwo();
38+
scenarios.fetchListOfEntitiesScenario(2000);
3939
}
4040

4141
@Test
4242
public void scenarioThree() {
43-
scenarios.scenarioThree();
43+
scenarios.saveNewEntityScenario();
4444
}
4545

4646
@Test
4747
public void scenarioFour() {
48-
scenarios.scenarioFour();
48+
scenarios.batchInsertMultipleEntitiesScenario();
4949
}
5050

5151
@Test
5252
public void scenarioFive() {
53-
scenarios.scenarioFive();
53+
scenarios.updateCompleteEntityScenario();
5454
}
5555

5656
@Test
5757
public void scenarioSix() {
58-
scenarios.scenarioSix();
58+
scenarios.fetchManyToOneRelationScenario();
5959
}
6060

6161
@Test
6262
public void scenarioSeven() {
63-
scenarios.scenarioSeven();
63+
scenarios.fetchOneToManyRelationScenario();
6464
}
6565

6666
@Test
6767
public void scenarioEight() {
68-
scenarios.scenarioEight();
68+
scenarios.updateCompleteOneToManyRelationScenario();
6969
}
7070

7171
@Test
7272
public void scenarioNine() {
73-
scenarios.scenarioNine();
73+
scenarios.executeComplexSelectScenario();
7474
}
7575

7676
@Test
7777
public void scenarioTen() {
78-
scenarios.scenarioTen();
78+
scenarios.callStoredProcedureScenario();
7979
}
8080

8181
@Test
8282
public void scenarioEleven() {
83-
scenarios.scenarioEleven();
83+
scenarios.executeSimpleStaticStatementScenario();
8484
}
8585

8686
}

0 commit comments

Comments
 (0)