This assignment is broken up into three mandatory sections.
The first two mandatory sections functionally work with Spring Data Repositories outside the scope of the AutoRentals workflow. You will create a "service" class that is a peer to your AutoRentals Service implementation — but this new class is there solely as a technology demonstration and wrapper for the provided JUnit tests. You will work with both JPA and Mongo Repositories as a part of these first two sections.
In the third mandatory section — you will select one of the two technologies, update the end-to-end thread with a Spring Data Repository, and add in some Pageable and Page aspects for unbounded collection query/results.
1. Assignment 5a: Spring Data JPA
1.1. Database Schema
1.1.1. Purpose
In this portion of the assignment, you will demonstrate your knowledge of preparing a relational database for use with an application. You will:
-
define a database schema that maps a single class to a single table
-
implement a primary key for each row of a table
-
define constraints for rows in a table
-
define an index for a table
-
define a DataSource to interface with the RDBMS
-
automate database schema migration with the Flyway tool
1.1.2. Overview
In this portion of the assignment you will be defining, instantiating, and performing minor population of a database schema for AutoRental. We will use a single, flat database design.
The assignment will use a 36-character UUID for the ID. No sequence will be necessary.
Use varchar(36) to allow consistency with Mongo portion of assignment
Use the varchar-based primary key to make the JPA and Mongo portions of the assignment as similar as possible.
We will use a UUID for the JPA portion, but any unique String fitting into 36 characters will work.
|
Postgres access with Docker/Docker Compose
If you have Docker/Docker Compose, you can instantiate a Postgres instance using the scripts in the ejava-springboot root directory.
You can also get client access to using the following command.
|
You can switch between in-memory H2 (default) and Postgres once you have your property files setup either by manual change of the source code or using runtime properties with the
|
1.1.3. Requirements
-
Create a set of SQL migrations below
src/main/resources/db/migration
that will define the database schemaRefer to the JPA songs example for a schema example. However, that example assumes that all schema is vendor-neutral and does not use vendor-specific sibling files. -
create SQL migration file(s) to define the base AutoRental schema. This can be hand-generated or metadata-generated once the
@Entity
class is later defined-
account for when the table(s)/sequence(s) already exist by defining a DROP before creating
drop table IF EXISTS (table name);
-
define a AutoRental table with the necessary columns to store a flattened AutoRental object. We are going to store every property of an AutoRental and its 1:1 relationship(s) in the same table.
-
use the
id
field as a primary key. Make this a char-based column type of at least 36 characters (varchar(36)
) to host a UUID string -
define column constraints for size and not-null
-
-
-
Create a separate SQL migration file to add indexes
-
define a non-unique index on auto_id
-
define a non-unique index on renter_id
-
-
Create a SQL migration file to add one sample row in the AutoRental table
CURRENT_DATE
(CURRENT_DATE+7
) can be used to generate a value for datesYou can manually test schema files by launching the Postgres client and reading the SQL file in from stdin
Execute SQL File Against Postgres using Docker Composedocker-compose exec -T postgres psql -U postgres < (path to file)
-
Place vendor-neutral SQL in a
common
and vendor-specific SQL in a{vendor}
directory as defined in your flyway properties. The example below shows a possible layout.src/main/resources/ `-- db `-- migration |-- common |-- h2 `-- postgres
I am not anticipating any vendor-specific schema population, but it is a good practice if you use multiple database vendors between development and production.
If you have Postgres running, you can test your schema with the following:
$ docker-compose -f .../docker-compose.yml exec -T postgres psql -U postgres < ./src/main/resources/db/migration/common/V1.0.0_0__autorentals_schema.sql NOTICE: table "rentals_autorental" does not exist, skipping DROP TABLE CREATE TABLE COMMENT COMMENT ... $ docker-compose -f .../docker-compose.yml exec -T postgres psql -U postgres < ./src/main/resources/db/migration/common/V1.0.0_1__autorentals_indexes.sql CREATE INDEX CREATE INDEX $ docker-compose -f .../docker-compose.yml exec -T postgres psql -U postgres < ./src/main/resources/db/migration/postgres/V1.0.0_2__autorentals_populate.sql INSERT 0 1 UPDATE 1 $ docker-compose -f .../docker-compose.yml exec postgres psql -U postgres -c '\d' List of relations Schema | Name | Type | Owner --------+--------------------+-------+---------- public | (table name) | table | postgres (1 row) $ docker-compose -f .../docker-compose.yml exec postgres psql -U postgres -c '\d (table name)' Column | Type | Collation | Nullable | Default ----------------+-----------------------+-----------+----------+--------- id | character varying(36) | | not null | auto_id | character varying(36) | | not null | renter_id | character varying(36) | | not null | ... Indexes: ... $ docker-compose -f .../docker-compose.yml exec postgres psql -U postgres -c '\d+ (table name)'
-
-
Configure database properties so that you are able to work with both in-memory and external database. In-memory will be good for automated testing. Docker Compose-launched Postgres will be good for interactive access to the database while developing. Testcontainers-based Postgres can also be used but can be harder to work with for interactive development.
-
make the default database in-memory
You can set the default database to
h2
and activate the console by setting the following properties.application.properties#default test database spring.datasource.url=jdbc:h2:mem:autorentals spring.h2.console.enabled=true
-
provide a "postgres" Spring profile option to use Postgres DB instead of in-memory
You can switch to an alternate database by overriding the URL in a Spring profile. Add a
postgres
Spring profile insrc/main
tree to optionally connect to an external Postgres server versus the in-memory H2 server. Include any necessary credentials. The following example assumes you will be connected to the Postgres DB launched by the class docker-compose.application-postgres.propertiesspring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres spring.datasource.password=secret
This is only a class assignment. Do not store credentials in files checked into CM or packaged within your Spring Boot executable JAR in a real environment. Make them available via a file location at runtime when outside of a classroom. -
define the location for your schema migrations for flyway to automate.
spring.flyway.locations=classpath:db/migration/common,classpath:db/migration/{vendor}
-
-
Configure the application to establish a connection to the database and establish a DataSource
-
declare a dependency on
spring-boot-starter-data-jpa
-
declare a dependency on the
h2
database driver for default testing and demonstration -
declare a dependency on the
postgresql
database driver for optional production-ready testing -
declare the database driver dependencies as
scope=runtime
See jpa-song-example
pom.xml for more details on declaring these dependencies.
-
-
Configure Flyway so that it automatically populates the database schema
-
declare a dependency on the
flyway-core
schema migration library -
declare the Flyway dependency as scope=runtime
See jpa-song-example
pom.xml for more details on declaring this plugin
-
-
Enable (and pass) the provided MyJpa5a_SchemaTest that extends Jpa5a_SchemaTest. This test will verify connectivity to the database and the presence of the AutoRental table.
-
supply necessary
@SpringBootTest
test configurations unique to your environment -
supply an implementation of the
DbTestHelper
to be injected into all testsIf the MyJpa5a_SchemaTest is launched without a web environment, Spring’s AutoConfiguration will not provide web-related components for injection (e.g.,
RestTemplate
). The web environment is unnecessary for this and most of the database portions of the assignment. Design your test helper(s) and test helper@Bean
factories so that they can detect the absence of unnecessary web-related components and ignore them.Example Detection of non-Web Environment@Bean @Lazy @ConditionalOnBean({RestTemplate.class,ServerConfig.class}) public DbTestHelper fullTestHelper(/*web-related resources*/) { @Bean @ConditionalOnMissingBean({RestTemplate.class,ServerConfig.class}) public DbTestHelper dbOnlyTestHelper() {
-
-
Package the JUnit test case such that it executes with Maven as a surefire test
1.1.4. Grading
Your solution will be evaluated on:
-
define a database schema that maps a single class to a single table
-
whether you have expressed your database schema in one or more files
-
-
implement a primary key for each row of a table
-
whether you have identified the primary key for the table
-
-
define constraints for rows in a table
-
whether you have defined size and nullable constrains for columns
-
-
define an index for a table
-
whether you have defined an index for any database columns
-
-
automate database schema migration with the Flyway tool
-
whether you have successfully populated the database schema from a set of files
-
-
define a DataSource to interface with the RDBMS
-
whether a DataSource was successfully injected into the JUnit class
-
1.1.5. Additional Details
-
This and the following RDBMS/JPA and MongoDB tests are all client-side DB interaction tests. Calls from JUnit are directed at the service class. The provided starter example supplies an alternate
@SpringBootConfiguration
test configuration to bypass the extra dependencies defined by the full@SpringBootApplication
server class — which can cause conflicts. The@SpringBootConfiguration
class is latched by the "assignment-tests" profile to keep it from being accidentally used by the later API tests.@SpringBootConfiguration @EnableAutoConfiguration @Profile("assignment-tests") (1) public class DbAssignmentTestConfiguration { @SpringBootTest(classes={DbAssignmentTestConfiguration.class, JpaAssignmentDBConfiguration.class, DbClientTestConfiguration.class}) @ActiveProfiles(profiles={"assignment-tests","test"}, resolver = TestProfileResolver.class)(2) //@ActiveProfiles(profiles={"assignment-tests","test", "postgres"}) @Slf4j public class MyJpa5a_SchemaTest extends Jpa5a_SchemaTest {
1 profile prevents @SpringBootConfiguration
from being used as a@Configuration
for other tests2 assignment-tests
profile is activated for these service/DB-level tests only -
The following
starter
configuration files are used by the tests in this section:-
DbAssignmentTestConfiguration
- discussed above. Provides a@SpringBootConfiguration
class that removes the@SpringBootApplication
dependencies from view. -
DbClientTestConfiguration
- this defines the@Bean
factories for theDbTestHelper
and any supporting components. -
JpaAssignmentDBConfiguration
- this defines server-side beans used in this DB-centric portion of the assignment. It provides@Bean
factories that will get replaced when running the application and performing the end-to-end tests.
-
1.2. Entity/BO Class
1.2.1. Purpose
In this portion of the assignment, you will demonstrate your knowledge of defining a JPA @Entity
class and performing basic CRUD actions.
You will:
-
define a PersistenceContext containing an
@Entity
class -
inject an EntityManager to perform actions on a Persistence Unit and database
-
map a simple
@Entity
class to the database using JPA mapping annotations -
perform basic database CRUD operations on an
@Entity
-
define transaction scopes
-
implement a mapping tier between BO and DTO objects
1.2.2. Overview
In this portion of the assignment you will be creating an @Entity/Business Object for a AutoRental, mapping that to a table, and performing CRUD actions with an EntityManager.
Your work will be focused in the following areas:
-
creating a business object (BO)/
@Entity
class to map to the database schema you have already completed -
creating a mapper class that will map properties to/from the DTO and BO instances
-
creating a test helper class, implementing
DbTestHelper
that will assist the provided JUnit tests to interact and inspect your persistence implementation. -
implementing a
JpaAssignmentService
component that will perform specific interactions with the database
The interfaces for the DbTestHelper
and JpaAssignmentService
are located in the support module containing the tests.
The DbTestHelper
interface extends the ApiTestHelper
interface you have previously implemented and will simply extend with the additional functionality.
The BO and mapper classes will be used throughout this overall assignment, including the end-to-end.
The testHelper class will be used for all provided JUnit tests.
The JpaAssignmentService
will only be used during the JPA-specific sections of this assignment.
It is a sibling to your AutoRentalsService
component(s) for the purpose of doing one-off database assignment tasks.
It will not be used in the Mongo portions of the assignment or the end-to-end portion.
1.2.3. Requirements
-
Create a Business Object (BO)/
@Entity
class that represents the AutoRental and will be mapped to the database. ARentalBO
"marker" interface has been provided for your BO class to implement. It has no properties. All interactions with this object by the JUnit test will be through calls to the testHelper and mapper classes. You must complete the following details:-
identify the class as a JPA
@Entity
-
map the class to the DB table you created in the previous section using the
@Table
annotation. -
identify a String primary key field with JPA
@Id
-
supply a default constructor
-
map the attributes of the class to the columns you created in the previous section using the
@Column
annotation. -
supply other constructs as desired to help use and interact with this business object
The BO class will map to a single, flat database row. Keep that in mind when accounting for the address. The properties/structure of the BO class do not have to be 1:1 with the properties/structure of the DTO class. -
supply a lifecycle event handler that will assign the string representation of a UUID to the
id
field if null when persisted@Entity @PrePersist Lifecycle Callback to assign Primary Keyimport jakarta.persistence.PrePersist; @PrePersist void prePersist() { if (id==null) { id= UUID.randomUUID().toString(); }
If your Entity class is not within the default scan path, you can manually register the package path using the @EntityScan.basePackageClasses
annotation property. This should be done within a@Configuration
class in thesrc/main
portion of your code. The JUnit test will make the condition and successful correction obvious.
-
-
Create a mapper class that will map to/from AutoRental BO and DTO. A templated
RentalsMapper
interface has been provided for this. You must complete the details.-
map from BO to DTO
-
map from DTO to BO
Remember — the structure of the BO and DTO classes do not have to match. Encapsulate any mapping details between the two within this mapper class implementation. The following code snippet shows an example implementation of the templated mapper interface.
public class AutoRentalMapper implements RentalMapper<AutoRentalDTO, AutoRentalBO> { public AutoRentalBO map(AutoRentalDTO dto) { ... } public AutoRentalDTO map(AutoRentalBO bo) { ... }
-
expose this class as a component so that it can be injected by the tests.
Use the
JpaAssignmentDBConfiguration
class used by the JUnit test to expose this through a@Bean
factory.@SpringBootTest(classes={DbAssignmentTestConfiguration.class, JpaAssignmentDBConfiguration.class, DbClientTestConfiguration.class}) @ActiveProfiles(profiles={"assignment-tests","test"}, resolver = TestProfileResolver.class) ... public class MyJpa5b_EntityTest extends Jpa5b_EntityTest {
-
-
Implement a class that implements the
JpaAssignmentService<DTO,BO>
interface.-
Instantiate this as a component in the "assignment-tests" profile.
-
-
Implement the
mapAndPersist
method in yourJpaAssignmentService
. It must perform the following:-
accept a AutoRental DTO
-
map the DTO to a AutoRental BO (using your mapper)
-
persist the BO
-
map the persisted BO to a DTO (will have a primary key assigned)
-
return the resulting DTO
The BO must be persisted. The returned DTO must match the input DTO and express the primary key assigned by the database.
Be sure to address @Transactional
details when modifying the database. -
-
Implement the
queryByRentalDateRange
method in theJpaAssignmentService
using a@NamedQuery
. It must perform the following:-
query the database using a JPA
@NamedQuery
with JPA query syntax to locateAutoRental BO
objects within a startDate/endDate range, inclusiveYou may use the following query to start with and add ordering to complete
select r from AutoRentalBO r where startDate <= :endDate and endDate <= :startDate
-
:startDate
and:endDate
are variable LocalDate values passed in at runtime withsetParameter()
-
order the results by
id
ascending -
name the query "<EntityName>.findByDatesBetween"
EntityName defaults to the Java SimpleName for the class. Make sure all uses of EntityName (i.e., JPQL queries and JPA @NamedQuery
name prefixes) match.
-
-
map the BO list returned from the query to a list of DTOs (using your mapper)
-
return the list of DTOs
-
-
Enable (and pass) the provided MyJpa5b_EntityTest that extends Jpa5b_EntityTest. This test will perform checks of the above functionality using:
-
DbTestHelper
-
mapper
-
your DTO and BO classes
-
a functional JPA environment
-
-
Package the JUnit test case such that it executes with Maven as a surefire test
1.2.4. Grading
Your solution will be evaluated on:
-
inject an EntityManager to perform actions on a Persistence Unit and database
-
whether an EntityManager/EntityManager was successfully injected into the JUnit test
-
whether an EntityManager was successfully injected into your
JpaAssignmentService
implementation
-
-
map a simple
@Entity
class to the database using JPA mapping annotations-
whether a new AutoRental BO class was created for mapping to the database
-
whether the class was successfully mapped to the database table and columns
-
-
implement a mapping tier between BO and DTO objects
-
whether the mapper was able to successfully map all fields between BO to DTO
-
whether the mapper was able to successfully map all fields between DTO to BO
-
-
perform basic database CRUD operations on an
@Entity
-
whether the AutoRental BO was successfully persisted to the database
-
whether a named JPA-QL query was used to locate the entity in the database
-
-
define transaction scopes
-
whether the test method was declared to use a single transaction for all steps of the test method
-
1.3. JPA Repository
1.3.1. Purpose
In this portion of the assignment, you will demonstrate your knowledge of defining a JPA Repository. You will:
-
declare a
JpaRepository
for an existing JPA@Entity
-
perform simple CRUD methods using provided repository methods
-
add paging and sorting to query methods
-
implement queries based on predicates derived from repository interface methods
-
implement queries based on POJO examples and configured matchers
-
implement queries based on
@NamedQuery
or@Query
specification
1.3.2. Overview
In this portion of the assignment, you will define a JPA Repository to perform basic CRUD and query actions.
Your work will be focused in the following areas:
-
creating a JPA Spring Data Repository for persisting AutoRental BO objects
-
implementing repository queries within your
JpaAssignmentService
component
1.3.3. Requirements
-
define a AutoRental JPARepository that can support basic CRUD and complete the queries defined below.
-
enable JpaRepository use with the
@EnableJpaRepositories
annotation on a@Configuration
classSpring Data Repositories are primarily interfaces and the implementation is written for you at runtime using proxies and declarative configuration information. If your Repository class is not within the default scan path, manually register the package path using the @EnableJpaRepositories.basePackageClasses
annotation property. This should be done within thesrc/main
portion of your code. The JUnit test will make the condition and successful correction obvious. -
inject the JPA Repository class into your
JpaAssignmentService
component. This will be enough to tell you whether the Repository is properly defined and registered with the Spring context. -
implement the
findByAutoIdByDerivedQuery
method details which must-
accept a autoId and a Pageable specification with pageNumber, pageSize, and sort specification
-
return a Page of matching BOs that comply with the input criteria
-
this query must use the Spring Data Derived Query technique **
-
-
implement the
findByExample
method details which must-
accept a AutoRental BO probe instance and a Pageable specification with pageNumber, pageSize, and sort specification
-
return a Page of matching BOs that comply with the input criteria
-
this query must use the Spring Data Query by Example technique **
Override the default ExampleMatcher to ignore any fields declared with built-in data types that cannot be null.
-
-
implement the
findByAgeRangeByAnnotatedQuery
method details which must-
accept a minimum and maximum age and a Pageable specification with pageNumber and pageSize
-
return a Page of matching BOs that comply with the input criteria and ordered by
id
-
this query must use the Spring Data Named Query technique and leverage the "AutoRentalBO.findByRenterAgeRange"
@NamedQuery
created in the previous section.Named Queries do not support adding Sort criteria from the Pageable parameter. An "order by" for
id
must be expressed within the@NamedQuery
.... order by r.id ASC
There is no technical relationship between the name of the service method you are implementing and the repository method defined on the JPA Spring Data Repository. The name of the service method is mangled to describe "how" you must implement it — not what the name of the repository method should be.
-
-
Enable (and pass) the provided MyJpa5c_RepositoryTest that extends Jpa5c_RepositoryTest. This test will populate the database with content and issue query requests to your
JpaAssignmentService
implementation. -
Package the JUnit test case such that it executes with Maven as a surefire test
1.3.4. Grading
Your solution will be evaluated on:
-
declare a
JpaRepository
for an existing JPA@Entity
-
whether a
JPARepository
was defined and injected into the assignment service helper
-
-
perform simple CRUD methods using provided repository methods
-
whether the database was populated with test instances
-
-
add paging and sorting to query methods
-
whether the query methods where implemented with pageable specifications
-
-
implement queries based on predicates derived from repository interface methods
-
whether a derived query based on method signature was successfully performed
-
-
implement queries based on POJO examples and configured matchers
-
whether a query by example query was successfully performed
-
-
implement queries based on
@NamedQuery
or@Query
specification-
whether a query using a
@NamedQuery
or@Query
source was successfully performed
-
2. Assignment 5b: Spring Data Mongo
2.1. Mongo Client Connection
2.1.1. Purpose
In this portion of the assignment, you will demonstrate your knowledge of setting up a project to work with Mongo. You will:
-
declare project dependencies required for using Spring’s MongoOperations/MongoTemplate API
-
define a connection to a MongoDB
-
inject a MongoOperations/MongoTemplate instance to perform actions on a database
2.1.2. Overview
In this portion of the assignment you will be adding required dependencies and configuration properties necessary to communicate with the Flapdoodle test database and an external MongoDB instance.
Postgres access with Docker/Docker Compose
If you have Docker/Docker Compose, you can instantiate a MongoDB instance using the docker-compose scripts in the ejava-springboot root directory. $ docker-compose up -d mongodb Creating ejava_mongodb_1 ... done You can also get client access to using the following command. $ docker-compose exec mongodb mongo -u admin -p secret ... Welcome to the MongoDB shell. > |
You can switch between Flapdoodle and Mongo in your tests once you have your property files setup.
|
2.1.3. Requirements
-
Configure database properties so that you are able to work with both the Flapdoodle test database and a Mongo instance. Flapdoodle will be good for automated testing. MongoDB will be good for interactive access to the database while developing. Spring Boot will automatically configure tests for Flapdoodle if it is in the classpath and in the absence of a Mongo database URI.
You can turn on verbose MongoDB-related DEBUG logging using the following properties
application.propertieslogging.level.org.springframework.data.mongodb=DEBUG
-
provide a
mongodb
profile option to use an external MongoDB server instead of the Flapdoodle test instanceapplication-mongodb.properties#spring.data.mongodb.host=localhost #spring.data.mongodb.port=27017 #spring.data.mongodb.database=test #spring.data.mongodb.authentication-database=admin #spring.data.mongodb.username=admin #spring.data.mongodb.password=secret spring.data.mongodb.uri=mongodb://admin:secret@localhost:27017/test?authSource=admin
Configure via Individual Properties or Compound URLSpring Data Mongo has the capability to set individual configuration properties or via one, compound URL.This is only a class assignment. Do not store credentials in files checked into CM or packaged within your Spring Boot executable JAR in a real environment. Make them available via a file location at runtime when outside of a classroom. Flapdoodle will be Default Database during TestingFlapdoodle will be the default during testing unless deactivated. I have provided a @Configuration class in the "starter" module, that will deactivate Flapdoodle by the presence of thespring.data.mongodb
connection properties.
-
-
Configure the application to establish a connection to the database and establish a MongoOperations (the interface)/MongoTemplate (the commonly referenced implementation class)
-
declare a dependency on
spring-boot-starter-data-mongo
-
declare a dependency on the
de.flapdoodle.embed.mongo
database driver for default testing withscope=test
See mongo-book-example
pom.xml for more details on declaring these dependencies.These dependencies may already be provided through transitive dependencies from the Auto/Renter modules.
-
-
Enable (and pass) the provided
MyMongo5a_ClientTest
that extendsMongo5a_ClientTest
. This test will verify connectivity to the database. -
Package the JUnit test case such that it executes with Maven as a surefire test
2.1.4. Grading
Your solution will be evaluated on:
-
declare project dependencies required for using Spring’s MongoOperations/MongoTemplate API
-
whether required Maven dependencies where declared to operate and test the application with Mongo
-
-
define a connection to a MongoDB
-
whether a URL to the database was defined when the
mongodb
profile was activated
-
-
inject an MongoOperations/MongoTemplate instance to perform actions on a database
-
whether a MongoOperations client could be injected
-
whether the MongoOperations client could successfully communicate with the database
-
2.1.5. Additional Details
-
As with the RDBMS/JPA tests, these MongoDB tests are all client-side DB interaction tests. Calls from JUnit are directed at the service class. The provided starter example supplies an alternate
@SpringBootConfiguration
test configuration to bypass the extra dependencies defined by the full@SpringBootApplication
server class — which can cause conflicts. The@SpringBootConfiguration
class is latched by the "assignment-tests" profile to keep it from being accidentally used by the later API tests.@SpringBootConfiguration @EnableAutoConfiguration @Profile("assignment-tests") (1) public class DbAssignmentTestConfiguration { @SpringBootTest(classes={DbAssignmentTestConfiguration.class, MongoAssignmentDBConfiguration.class, DbClientTestConfiguration.class }) @ActiveProfiles(profiles={"assignment-tests","test"}, resolver = TestProfileResolver.class)(2) //@ActiveProfiles(profiles={"assignment-tests","test", "mongodb"}) public class MyMongo5a_ClientTest extends Mongo5a_ClientTest {
1 profile prevents @SpringBootConfiguration
from being used as a@Configuration
for other tests2 assignment-tests
profile is activated for these service/DB-level tests only
2.2. Mongo Document
2.2.1. Purpose
In this portion of the assignment, you will demonstrate your knowledge of defining a Spring Data Mongo @Document class and performing basic CRUD actions. You will:
-
implement basic unit testing using an (seemingly) embedded MongoDB
-
define a
@Document
class to map to MongoDB collection -
perform whole-document CRUD operations on a
@Document
class using the Java API -
perform queries with paging properties
2.2.2. Overview
In this portion of the assignment you will be
creating a @Document
/Business Object for a AutoRental, mapping that to a collection, and performing CRUD actions with a MongoOperations/MongoTemplate.
Reuse BO and Mapper classes
It has been designed and expected that you will be able to re-use the same AutoRental BO and Mapper classes from the JPA portion of the assignment.
You should not need to create new ones.
The BO class will need a few Spring Data Mongo annotations but the mapper created for the JPA portion should be 100% reusable here as well.
|
2.2.3. Requirements
-
Create (reuse) a Business Object (BO) class that represents the AutoRental and will be mapped to the database. A RentalBO "marker" interface has been provided for your BO class to implement. It has no properties. All interactions with this object by the JUnit test will be through calls to the testHelper and mapper classes. You must complete the following details:
-
identify the class as a Spring Data Mongo
@Document
and map it to the "rentals" collection -
identify a String primary key field with Spring Data Mongo
@Id
This is a different @Id
annotation than the JPA@Id
annotation. -
supply a default constructor
-
-
Reuse the mapper class from the earlier JPA Entity portion of this assignment.
-
Implement a class that implements the
MongoAssignmentService<DTO,BO>
interface.-
Instantiate this as a component in the "assignment-tests" profile.
-
-
Implement the
mapAndPersist
method in yourMongoAssignmentService
. It must perform the following:-
accept a AutoRental DTO
-
map the DTO to a AutoRental BO (using your mapper)
-
save the BO to the database
-
map the persisted BO to a DTO (will have a primary key assigned)
-
return the resulting DTO
The BO must be saved. The returned DTO must match the input DTO and express the primary key assigned by the database.
-
-
Implement the
queryByRentalDateRange
method in yourMongoAssignmentService
. It must perform the following:-
query the database to locate matching
AutoRental BO
objects within a startDate/endDate range, inclusiveYou may use the injected MongoOperations client find command, a query, and the AutoRentalBO.class as a request parameter You may make use of the following query
Query.query(new Criteria().andOperator( Criteria.where("startDate").gte(startInclusive), Criteria.where("endDate").lte(endInclusive)))
-
:startInclusive
and:endInclusive
are variable LocalDate values -
order the results by
id
ascending
-
-
map the BO list returned from the query to a list of DTOs (using your mapper)
-
return the list of DTOs
-
-
Enable (and pass) the provided MyMongo5b_DocumentTest that extends Mongo5b_DocumentTest. This test will perform checks of the above functionality using:
-
DbTestHelper
-
mapper
-
your DTO and BO classes
-
a functional MongoDB environment
-
-
Package the JUnit test case such that it executes with Maven as a surefire test
2.2.4. Grading
Your solution will be evaluated on:
-
define a
@Document
class to map to MongoDB collection-
whether the BO class was properly mapped to the database, including document and primary key
-
-
perform whole-document CRUD operations on a
@Document
class using the Java API-
whether a successful insert and query of the database was performed with the injected MongoOperations / MongoTemplate
-
2.3. Mongo Repository
2.3.1. Purpose
In this portion of the assignment, you will demonstrate your knowledge of defining a Mongo Repository. You will:
-
declare a
MongoRepository
for an existing@Document
-
implement queries based on predicates derived from repository interface methods
-
implement queries based on POJO examples and configured matchers
-
implement queries based on annotations with JSON query expressions on interface methods
-
add paging and sorting to query methods
2.3.2. Overview
In this portion of the assignment you will define a Mongo Repository to perform basic CRUD and query actions.
Your work will be focused in the following areas:
-
creating a Mongo Spring Data Repository for persisting AutoRental BO objects
-
implementing repository queries within your
MongoAssignmentService
component
2.3.3. Requirements
-
define a AutoRental MongoRepository that can support basic CRUD and complete the queries defined below.
-
enable MongoRepository use with the
@EnableMongoRepositories
annotation on a@Configuration
classSpring Data Repositories are primarily interfaces and the implementation is written for you using proxies and declarative configuration information. If your Repository class is not within the default scan path, you can manually register the package path using the @EnableMongoRepositories.basePackageClasses
annotation property. This should be done within a@Configuration
class in thesrc/main
portion of your code. The JUnit test will make the condition and successful correction obvious. -
inject the Mongo Repository class into the
MongoAssignmentService
. This will be enough to tell you whether the Repository is properly defined and registered with the Spring context. -
implement the
findByAutoIdByDerivedQuery
method details which must-
accept a autoId and a Pageable specification with pageNumber, pageSize, and sort specification
-
return a Page of matching BOs that comply with the input criteria
-
this query must use the Spring Data Derived Query technique **
-
-
implement the
findByExample
method details which must-
accept a AutoRental BO probe instance and a Pageable specification with pageNumber, pageSize, and sort specification
-
return a Page of matching BOs that comply with the input criteria
-
this query must use the Spring Data Query by Example technique **
Override the default ExampleMatcher to ignore any fields declared with built-in data types that cannot be null.
-
-
implement the
findByDateRangeByAnnotatedQuery
method details which must-
accept a startDate and endDate (inclusive) and a Pageable specification with pageNumber and pageSize
-
return a Page of matching BOs that comply with the input criteria and ordered by
id
-
this query must use the Spring Data JSON-based Query Methods technique and annotate the repository method with a
@Query
definition.You may use the following JSON query expression for this query. Mongo JSON query expressions only support positional arguments and are zero-relative.
value="{ 'startDate':{$lte:?1}, 'endDate':{$gte:?0} }" (1)
1 endDate is position 0 and startDate is position 1 in the method signature Annotated Queries do not support adding Sort criteria from the Pageable parameter. You may use the following sort expression in the annotation
sort="{id:1}"
There is no technical relationship between the name of the service method you are implementing and the repository method defined on the Mongo Spring Data Repository. The name of the service method is mangled to describe "how" you must implement it — not what the name of the repository method should be.
-
-
Enable (and pass) the provided
MyMongo5c_RepositoryTest
that extendsMongo5c_RepositoryTest
. This test will populate the database with content and issue query requests to yourMongoAssignmentService
implementation. -
Package the JUnit test case such that it executes with Maven as a surefire test
2.3.4. Grading
Your solution will be evaluated on:
-
declare a
MongoRepository
for an existing@Document
-
whether a MongoRepository was declared and successfully integrated into the test case
-
-
implement queries based on predicates derived from repository interface methods
-
whether a dynamic query was implemented via the expression of the repository interface method name
-
-
implement queries based on POJO examples and configured matchers
-
whether a query was successfully implemented using an example with a probe document and matching rules
-
-
implement queries based on annotations with JSON query expressions on interface methods
-
whether a query was successfully implemented using annotated repository methods containing a JSON query and sort documents
-
-
add paging and sorting to query methods
-
whether queries were performed with sorting and paging
-
3. Assignment 5c: Spring Data Application
3.1. API/Service/DB End-to-End
3.1.1. Purpose
In this portion of the assignment, you will demonstrate your knowledge of integrating a Spring Data Repository into an end-to-end application, accessed through an API. You will:
-
implement a service tier that completes useful actions
-
implement controller/service layer interactions relative to DTO and BO classes
-
determine the correct transaction propagation property for a service tier method
-
implement paging requests through the API
-
implement page responses through the API
3.1.2. Overview
In this portion of the assignment you will be taking elements of the application that you have worked on and integrate them into an end-to-end application from the API, thru services, security, the repository, and to the database and back.
The fundamental scope of the assignment is to perform existing AutoRentals use cases (including security layer(s)) but updated with database and the impacts of database interaction related to eventual size and scale. You will:
-
chose either a RDBMS/JPA or Mongo target solution
-
replace your existing AutoRental Service and Repository implementation classes with implementations that are based upon your selected repository technology
For those that
-
augmented your assignment2/API solution in-place to meet the requirements of each follow-on assignment
-
you will continue that pattern by replacing the core service logic with mapper/BO/repository logic.
-
-
layered your solution along assignment boundaries
-
you will override the assignment2 service and repository components with a new service implementation based on the mapper/BO/repository logic. This layer will be inserted into your assignment3 SecurityWrapper. The support modules show an example of doing this.
-
-
-
update the controller and service interfaces to address paging
It is again, your option of whether to
-
simply add the new paging endpoint to your existing controller and API client class
-
subclass the controller and API class to add the functionality
The Autos/Renters support modules are provided in a layered approach to help identify what is new with each level and to keep what you are basing your solutions on consistent. It is much harder to implement the layered approach, but offers some challenges and experience in integrating multiple components.
-
-
leave the Autos and Renters implementation as in-memory repositories
There are two optional support modules supplied:
The tests within each module work but extensive testing with AutoRentals has not been performed. It is anticipated that you will continue to use the in-memory Autos and Renters that you have been using to date. However, it is your option to use those modules in any way. |
Continued use of in-memory Autos and Renters is expected
The autorenters-support-svcjpa and autorenters-support-svcmongo modules are provided as examples of how the flow can be implemented.
It is not a requirement that you change from the in-memory versions for Autos and Renters to complete this assignment.
|
3.1.3. Requirements
-
Select a database implementation choice (JpaRepository or MongoRepository).
This is a choice to move forward with. The option you don’t select will still be part of your dependencies, source tree, and completed unit integration tests. -
Update/Replace your legacy AutoRental Service and Repository components with a service and repository based on Spring Data Repository.
-
all CRUD calls will be handled by the Repository — no need for DataSource, EntityManager or MongoOperations/MongoTemplate
-
all end-to-end queries must accept Pageable and return Page
By following the rule early in assignment 2, you made this transition extremely easy on yourself. -
the service should
-
accept DTO types as input and map to BOs for interaction with the Repository
This is should be consistent with your original service interface. The only change should be the conversion of DTO to BO and back.
-
It is acceptable to retain much of your legacy service logic (e.g., validation) working primarily with DTOs. However, you will need to convert them to BOs to interact with the database. -
map BO types to DTOs as output
This should also be consistent with your original service interface. The Page class has a nice/easy way to map between Page<T1> to Page<T2>. When you combine that with your mapper — it can be a simple one-line of code.
dtos = bos.map(bo->map(bo));
-
-
Add the capability to your controller to accept full paging parameters. For those augmenting in-place, you may simply modify your existing finder methods to accept/return the additional information. For those adopting the layered approach, you may add an additional URI to accept/return the additional information.
Example new Paged URI for Layered Approachpublic interface HomesPageableAPI extends HomesAPI { public static final String HOMES_PAGED_PATH = "/api/homes/paged";
There is a convenience method within
PageableDTO
(fromejava-dto-util
) that will convert pageNumber, pageSize, and sort to a Spring DataPageable
.@GetMapping(path = HomesPageableAPI.HOMES_PAGED_PATH, ...) public ResponseEntity<HomePageDTO> getHomesPage( @RequestParam(value = "pageNumber", required = false) Integer pageNumber, @RequestParam(value = "pageSize", required = false) Integer pageSize, @RequestParam(value = "sort", required = false) String sort) { Pageable pageable = PageableDTO.of(pageNumber, pageSize, sort).toPageable();
-
Add the capability to your controller to return paging information with the contents. The current pageNumber, pageSize, and sort that relate to the supplied data must be returned with the contents.
You may use the
PageDTO<T>
class (fromejava-dto-util
) to automate/encapsulate much of this. The primary requirement is to convey the information. The option is yours of whether to use this library demonstrated in the class JPA and Mongo examples as well as the Autos and Renters examples (fromautorenters-support-pageable-svc
)public class HomePageDTO extends PageDTO<HomeDTO> { protected HomePageDTO() {} public HomePageDTO(Page<HomeDTO> page) { super(page); } }
Page<HomeDTO> result = service.getHomes(pageable); HomePageDTO resultDTO = new HomePageDTO(result); return ResponseEntity.ok(resultDTO);
-
Add the capability to your API calls to provide and process the additional page information.
There is a convenience method within
PageableDTO
(fromejava-dto-util
) that will serialize the pageNumber, pageSize, and sort of Spring Data’s Pageable into query parameters.PageableDTO pageableDTO = PageableDTO.fromPageable(pageable); (1) URI url = UriComponentsBuilder .fromUri(homesUrl) .queryParams(pageableDTO.getQueryParams()) (2) .build().toUri();
1 create DTO abstraction from Spring Data’s local Pageable abstraction 2 transfer DTO representation into query parameters -
Write a JUnit Integration Test Case that will
-
populate the database with multiple AutoRentals with different and similar properties
-
query for AutoRentals based on a criteria that will match some of the AutoRentals and return a page of contents that is less than the total matches in the database. (i.e., make the pageSize smaller that total number of matches)
-
page through the results until the end of data is encountered
Skeletal XxxPagingNTest tests are provided within the starter module to form the basis of these tests. You are to implement either the Jpa or Mongo version. Again — the DTO Paging framework in common and the JPA Songs and Mongo Books examples should make this less heroic than it may sound.
The requirement is not that you integrate with the provided DTO Paging framework. The requirement is that you implement end-to-end paging and the provided framework can take a lot of the API burden off of you. You may implement page specification and page results in a unique manner as long as it is end-to-end. -
-
Package the JUnit test case such that it executes with Maven as a surefire test
3.1.4. Grading
Your solution will be evaluated on:
-
implement a service tier that completes useful actions
-
whether you successfully implemented a query for AutoRentals for a specific autoId
-
whether the service tier implemented the required query with Pageable inputs and a Page response
-
whether this was demonstrated thru a JUnit test
-
-
implement controller/service layer interactions when it comes to using DTO and BO classes
-
whether the controller worked exclusively with DTO business classes and implemented a thin API facade
-
whether the service layer mapped DTO and BO business classes and encapsulated the details of the service
-
-
determine the correct transaction propagation property for a service tier method
-
depending on the technology you select and the usecase you have implemented — whether the state of the database can ever reach an inconsistent state
-
-
implement paging requests through the API
-
whether your controller implemented a means to express Pageable request parameters for queries
-
-
implement page responses through the API
-
whether your controller supported returning a page-worth of results for query results
-
3.1.5. Additional Details
-
There is a set of tests under the "regression" folder that can be enabled. These will run a series of tests ported forward from the API and Security modules to better verify your implementation logic is sound. You must set:
-
rentals.impl=jpa (the default) to activate the JPA configuration
-
rentals.impl=mongo to activate the Mongo configuration
Service Configuration Defaults to JPA//rentals.impl=jpa @ConditionalOnProperty(prefix = "rentals", name="impl", havingValue = "jpa", matchIfMissing = true) ... public class JpaAutoRentalsConfiguration { //rentals.impl=mongo @ConditionalOnProperty(prefix = "rentals", name="impl", havingValue = "mongo", matchIfMissing = false) ... public class MongoAutoRentalsConfiguration {
-