Skip to content

Commit 0cc9cfb

Browse files
committed
Better Examples in comments, better error handling of some exceptions - additional test case.
1 parent a7e679d commit 0cc9cfb

9 files changed

Lines changed: 134 additions & 66 deletions

File tree

memex/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ classes for an Entity including all the new
392392
derived classes by running the maven goal. This adds a Generic flexible model.
393393

394394
```shell
395-
mvn generate-sources -Pgenerate-entity -Dentity=Company -Dplural=companies -DidFieldName=companyNumber -DidFieldType=String
395+
mvn generate-sources -Pgenerate-entity -Dentity=MyEntity -Dplural=entities -DidFieldName=entityId -DidFieldType=Long
396396
```
397397

398398
You can delete it with
@@ -409,7 +409,7 @@ field in the JSON
409409

410410
```shell
411411

412-
mvn generate-sources -Pgenerate-models-from-json -DjsonFile=../companies250k.json -DbasePackage=com.johnlpage.memex -Dentity=Company -DidFieldName=companyNumber
412+
mvn generate-sources -Pgenerate-models-from-json -DjsonFile=../DataGen/mot.json -DbasePackage=com.johnlpage.memex -Dentity=MyEntity -DidFieldName=myentityId
413413

414414
```
415415

memex/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
<plugin>
234234
<groupId>org.jacoco</groupId>
235235
<artifactId>jacoco-maven-plugin</artifactId>
236-
<version>0.8.12</version>
236+
<version>0.8.14</version>
237237
<executions>
238238
<execution>
239239
<id>jacoco-initialize</id>

memex/src/main/java/com/johnlpage/memex/VehicleInspection/controller/VehicleInspectionController.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public class VehicleInspectionController {
6363

6464
@GetMapping("/vehicles/id/{id}")
6565
public ResponseEntity<Vehicle> vehicleById(@PathVariable Long id) {
66-
// While you can to straight to the repository, it's best practise to use a service.
66+
// Vehicles are embedde din inspections but can still have their own repository
6767
return vehicleRepository
6868
.findByVehicleIdWithInspections(id)
6969
.map(ResponseEntity::ok)
@@ -161,25 +161,27 @@ public ResponseEntity<PageDto<VehicleInspection>> getInspectionsByModel(
161161
*/
162162
@PostMapping(value = "/inspections/query", produces = MediaType.APPLICATION_JSON_VALUE)
163163
public ResponseEntity<String> mongoQuery(@RequestBody String requestBody) {
164-
List<VehicleInspection> result = queryService.mongoDbNativeQuery(requestBody);
164+
165165
try {
166+
List<VehicleInspection> result = queryService.mongoDbNativeQuery(requestBody);
166167
// Convert list to JSON string
167168
String jsonResult = objectMapper.writeValueAsString(result);
168169
return ResponseEntity.ok(jsonResult);
169-
} catch (JsonProcessingException e) {
170-
return ResponseEntity.status(500).body("Error converting vehicles to JSON");
170+
} catch (Exception e) {
171+
return ResponseEntity.status(500).body("Error " + e.getMessage());
171172
}
172173
}
173174

174175
@PostMapping("/inspections/search")
175176
public ResponseEntity<String> atlasSearchQuery(@RequestBody String requestBody) {
176-
List<VehicleInspection> result = queryService.atlasSearchQuery(requestBody);
177+
177178
try {
179+
List<VehicleInspection> result = queryService.atlasSearchQuery(requestBody);
178180
// Convert list to JSON string
179181
String jsonResult = objectMapper.writeValueAsString(result);
180182
return ResponseEntity.ok(jsonResult);
181-
} catch (JsonProcessingException e) {
182-
return ResponseEntity.status(500).body("Error converting vehicles to JSON");
183+
} catch (Exception e) {
184+
return ResponseEntity.status(500).body("Error "+e.getMessage()) ;
183185
}
184186
}
185187

memex/src/main/java/com/johnlpage/memex/VehicleInspection/repository/VehicleInspectionRepository.java

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
import com.johnlpage.memex.generics.repository.OptimizedMongoLoadRepository;
77
import com.johnlpage.memex.generics.repository.OptimizedMongoQueryRepository;
88

9+
import java.util.List;
910
import java.util.stream.Stream;
1011

12+
import org.bson.Document;
13+
import org.springframework.data.mongodb.repository.Aggregation;
1114
import org.springframework.data.mongodb.repository.MongoRepository;
15+
import org.springframework.data.mongodb.repository.Query;
16+
import org.springframework.data.mongodb.repository.Update;
1217
import org.springframework.stereotype.Repository;
1318

14-
/* The repository lets you define simple operations by name of function or by Javascript
15-
versions of MongoDB queries
19+
/* The repository Shows examples of the different ways you can define Queries.
20+
Annotation based (@Quer, @Aggregation), Derived query Methods and
21+
Custom methods using a variety of types/
1622
*/
1723
@Repository
1824
public interface VehicleInspectionRepository
@@ -22,31 +28,32 @@ public interface VehicleInspectionRepository
2228
OptimizedMongoDownstreamRepository<VehicleInspection>,
2329
MongoHistoryRepository<VehicleInspection, Long> {
2430

25-
/*
26-
* EXAMPLE REPOSITORY METHODS
27-
*
28-
*
29-
// Find inspections by engine capacity - auto generated query
30-
List<VehicleInspection> findAllByCapacityGreaterThan(Long engineCapacity);
31-
32-
// Custom query to find vehicle inspections by vehicle make and model
33-
@Query("{ 'vehicle.make': ?0, 'vehicle.model': ?1 }")
34-
List<VehicleInspection> findByVehicleMakeAndModel(String make, String model);
35-
36-
// Custom aggregation to compute mean mileage of a given model
37-
@Aggregation(
38-
pipeline = {
39-
"{ '$match': { 'vehicle.model': ?0 } }",
40-
"{ '$group': { '_id': null, 'averageMileage': { '$avg': '$vehicle.mileage' } } }"
41-
})
42-
List<Document> findAverageMileageByModel(String model);
43-
44-
// Simple efficient update without reading or sending whole document (N.B won't do history)
45-
@Query("{ 'testid' : ?0 }")
46-
@Update("{ 'inc' : { 'testmileage' : ?1 } }")
47-
void adjustTestMileage(Long testid, int increment);
48-
*/
49-
50-
// You have to explicitly call out a streaming version here if you want it.
31+
32+
// Find inspections by engine capacity - Automatically derived query
33+
List<VehicleInspection> findByCapacityGreaterThan(Long engineCapacity);
34+
35+
// Derived Query with Boolean and
36+
List<VehicleInspection> findByVehicleColourAndVehicleModel(String colour, String model);
37+
38+
// Annotation-based aggregation (Group By in this case) returning a Docuement as a generic type
39+
@Aggregation(
40+
pipeline = {
41+
"{ '$match': { 'vehicle.model': ?0 } }",
42+
"{ '$group': { '_id': null, 'averageMileage': { '$avg': '$vehicle.mileage' } } }"
43+
})
44+
List<Document> findAverageMileageByModel(String model);
45+
46+
47+
// Simple efficient annotation update without reading or sending the whole document (N.B won't do history)
48+
@Query("{ 'testid' : ?0 }")
49+
@Update("{ 'inc' : { 'testmileage' : ?1 } }")
50+
void adjustTestMileage(Long testid, int increment);
51+
52+
// An Annotation using MongoDB Query Language - opens up more query operators
53+
@Query("{ 'vehicle.make': ?0, 'vehicle.model': ?1 }")
54+
List<VehicleInspection> findByVehicleMakeAndModel(String make, String model);
55+
56+
57+
// Fetch whole collection as a stream.
5158
Stream<VehicleInspection> findAllBy();
5259
}

memex/src/main/java/com/johnlpage/memex/VehicleInspection/repository/VehicleRepository.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
import org.springframework.data.mongodb.repository.MongoRepository;
99
import org.springframework.stereotype.Repository;
1010

11-
/* The repository lets you define simple operations by name of function or by Javascript
12-
versions of MongoDB queries
11+
/* The Vehicle Model is not top level, Vehicles are a child of VehicleInspection
12+
This shows how you can still have a Vehicle repository that works on a child
13+
element including arrays.
1314
*/
1415
@Repository
1516
public interface VehicleRepository extends MongoRepository<Vehicle, Long> {
1617

17-
// We don't have a separate vehicle collection so any Queries need to be sent to VehicleInspection
18+
// As we don't have a vehicle collection all Queries need to be sent to VehicleInspection
1819

1920
// VehicleInspection - Start with matching nothing then use $unionWith to swap collections
2021
// Find inside the embedded object then use replaceWith to just keep the vehicle object
@@ -30,6 +31,7 @@ public interface VehicleRepository extends MongoRepository<Vehicle, Long> {
3031
Optional<Vehicle> findByVehicleId(Long id);
3132

3233
// Fetch a Vehicle then add all it's inspections to it. - N.B make sure this is indexed.
34+
// This is a pivot compared to the storage format
3335

3436
@Aggregation(
3537
pipeline = {

memex/src/main/java/com/johnlpage/memex/generics/repository/OptimizedMongoQueryRepositoryImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public List<T> mongoDbNativeQuery(String jsonString, Class<T> clazz) {
9797
}
9898

9999
public int costMongoDbNativeQuery(String jsonString, Class<T> clazz) {
100+
100101
Document queryRequest = Document.parse(jsonString);
101102
Document filter = queryRequest.get("filter", new Document());
102103
Document projection = queryRequest.get("projection", new Document());

memex/src/main/java/com/johnlpage/memex/generics/service/HistoryTriggerService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import org.springframework.stereotype.Service;
2424

2525
/* This trigger writes out a change history for documents modified in OptimizedMonogoLoadRepository*/
26+
//TODO - Change this to diff all arrays with prior version and store only elements that
27+
//Have Changed, for elements that are the same store the BSON MinKey byte
28+
//This will need new aggreagtion based mering code to match
2629

2730
@Service
2831
public abstract class HistoryTriggerService<T> extends PostWriteTriggerService<T> {

memex/src/test/resources/features/inspections.rest.mongo-query.feature

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,27 @@ Feature: Vehicle Inspection REST API - Native MongoDB Query Execution
4646
And each item in the response array should contain "vehicle.model": "Corolla"
4747

4848
@post @rainy_day
49-
Scenario: Fail to execute a native MongoDB query due to invalid syntax
49+
Scenario: Fail to execute a native MongoDB query due to invalid query syntax
5050
When I send a POST request to "/api/inspections/query" with the payload:
51-
"""{
51+
"""
52+
{
5253
"filter": {
5354
"$invalid_operator": "value"
5455
}
5556
}
5657
"""
5758
Then the response status code should be 500
58-
And the response should contain "Internal Server Error"
59+
And the response should contain "unknown top level operator"
60+
61+
@post @rainy_day
62+
Scenario: Fail to execute a native MongoDB query due to invalid JSON
63+
When I send a POST request to "/api/inspections/query" with the payload:
64+
"""
65+
66+
"filter": {
67+
"vehicle.model": "Corolla"
68+
}
69+
}
70+
"""
71+
Then the response status code should be 500
72+
And the response should contain "readStartDocument"

memex/templates/repository/Repository.java.template

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,67 @@ public interface __className__Repository
2222
OptimizedMongoDownstreamRepository<__className__>,
2323
MongoHistoryRepository<__className__, __idType__> {
2424

25-
/*
26-
* EXAMPLE REPOSITORY METHODS
27-
*
28-
* // Find by field using auto-generated query
29-
* List<__className__> findAllBySomeFieldGreaterThan(Long value);
30-
*
31-
* // Custom query for fields that are explicit
32-
* @Query("{ 'someField': ?0 }")
33-
* List<__className__> findBySomeField(String value);
34-
*
35-
* // Custom aggregation
36-
* @Aggregation(pipeline = {
37-
* "{ '$match': { 'someField': ?0 } }",
38-
* "{ '$group': { '_id': null, 'average': { '$avg': '$numericField' } } }"
39-
* })
40-
* List<Document> findAverageBySomeField(String value);
41-
*
42-
* // Simple efficient update without reading whole document
43-
* @Query("{ 'id' : ?0 }")
44-
* @Update("{ '$inc' : { 'counter' : ?1 } }")
45-
* void incrementCounter(__idType__ id, int increment);
46-
*/
25+
26+
/*
27+
* =====================================================================
28+
* SPRING DATA MONGODB REPOSITORY QUERY EXAMPLES
29+
* =====================================================================
30+
*
31+
* These examples demonstrate the various ways to define queries in a
32+
* Spring Data MongoDB repository interface. Replace the placeholder
33+
* names with your actual field names and types.
34+
*
35+
* Terminology:
36+
* - FieldA, FieldB : Top-level fields on the document
37+
* - SubObject : A nested/embedded document
38+
* - SubObject.FieldA : A field within the nested document
39+
*
40+
* ---------------------------------------------------------------------
41+
* 1. AUTOMATICALLY DERIVED QUERIES
42+
* ---------------------------------------------------------------------
43+
* Spring Data parses the method name to generate the query.
44+
*
45+
* // Find where a top-level numeric field exceeds a threshold
46+
* List<MyDocument> findByFieldAGreaterThan(Long value);
47+
*
48+
* // Find by two fields within a nested subdocument (AND)
49+
* List<MyDocument> findBySubObjectFieldAAndSubObjectFieldB(
50+
* String fieldAValue, String fieldBValue);
51+
*
52+
* ---------------------------------------------------------------------
53+
* 2. ANNOTATION-BASED AGGREGATION
54+
* ---------------------------------------------------------------------
55+
* Use @Aggregation for pipelines such as $group, $project, $unwind.
56+
* Returns Document when the result shape differs from the entity.
57+
*
58+
* @Aggregation(pipeline = {
59+
* "{ '$match': { 'subObject.fieldA': ?0 } }",
60+
* "{ '$group': { '_id': null, 'averageFieldB': { '$avg': '$subObject.fieldB' } } }"
61+
* })
62+
* List<Document> findAverageFieldBByFieldA(String fieldAValue);
63+
*
64+
* ---------------------------------------------------------------------
65+
* 3. ANNOTATION-BASED UPDATE (WITHOUT FULL DOCUMENT READ/WRITE)
66+
* ---------------------------------------------------------------------
67+
* Combines @Query to match and @Update to modify in place.
68+
* Efficient for targeted field updates; bypasses entity serialisation.
69+
*
70+
* @Query("{ 'fieldA': ?0 }")
71+
* @Update("{ '$inc': { 'fieldB': ?1 } }")
72+
* void adjustFieldB(Long fieldAValue, int increment);
73+
*
74+
* ---------------------------------------------------------------------
75+
* 4. @Query WITH MONGODB QUERY LANGUAGE (MQL)
76+
* ---------------------------------------------------------------------
77+
* Gives access to the full range of MQL operators while still
78+
* binding method parameters positionally.
79+
*
80+
* @Query("{ 'subObject.fieldA': ?0, 'subObject.fieldB': ?1 }")
81+
* List<MyDocument> findBySubObjectFieldAAndFieldB(
82+
* String fieldAValue, String fieldBValue);
83+
*
84+
* =====================================================================
85+
*/
4786

4887
// Streaming version for large result sets
4988
Stream<__className__> findAllBy();

0 commit comments

Comments
 (0)