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:

  1. define a database schema that maps a single class to a single table

  2. implement a primary key for each row of a table

  3. define constraints for rows in a table

  4. define an index for a table

  5. define a DataSource to interface with the RDBMS

  6. 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.

assignment5a autorentals jpa schema
Figure 1. AutoRentals Schema

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.

$ docker-compose up -d postgres
Creating network "ejava_default" with the default driver
Creating ejava_postgres_1 ... done

You can also get client access to using the following command.

$ docker-compose exec postgres psql -U postgres
psql (12.3)
Type "help" for help.

postgres=#

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 TestProfileResolver class provided in the starter module. You may also leverage Testcontainers to stick with the same database at all times.

@SpringBootTest(...)
@ActiveProfiles(profiles={"assignment-tests","test"}, resolver = TestProfileResolver.class)
//@ActiveProfiles(profiles={"assignment-tests","test", "postgres"})
public class Jpa5a_SchemaTest {

1.1.3. Requirements

  1. Create a set of SQL migrations below src/main/resources/db/migration that will define the database schema

    Refer 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.
    1. 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

      1. account for when the table(s)/sequence(s) already exist by defining a DROP before creating

        drop table IF EXISTS (table name);
      2. 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.

        1. 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

        2. define column constraints for size and not-null

    2. Create a separate SQL migration file to add indexes

      1. define a non-unique index on auto_id

      2. define a non-unique index on renter_id

    3. 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 dates

      You 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 Compose
      docker-compose exec -T postgres psql -U postgres < (path to file)
    4. 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)'
  2. 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.

    1. 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
    2. 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 in src/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.properties
      spring.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.
    3. define the location for your schema migrations for flyway to automate.

      spring.flyway.locations=classpath:db/migration/common,classpath:db/migration/{vendor}
  3. Configure the application to establish a connection to the database and establish a DataSource

    1. declare a dependency on spring-boot-starter-data-jpa

    2. declare a dependency on the h2 database driver for default testing and demonstration

    3. declare a dependency on the postgresql database driver for optional production-ready testing

    4. declare the database driver dependencies as scope=runtime

      See jpa-song-example pom.xml for more details on declaring these dependencies.
  4. Configure Flyway so that it automatically populates the database schema

    1. declare a dependency on the flyway-core schema migration library

    2. declare the Flyway dependency as scope=runtime

      See jpa-song-example pom.xml for more details on declaring this plugin
  5. 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.

    1. supply necessary @SpringBootTest test configurations unique to your environment

    2. supply an implementation of the DbTestHelper to be injected into all tests

      If 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() {
  6. 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:

  1. define a database schema that maps a single class to a single table

    1. whether you have expressed your database schema in one or more files

  2. implement a primary key for each row of a table

    1. whether you have identified the primary key for the table

  3. define constraints for rows in a table

    1. whether you have defined size and nullable constrains for columns

  4. define an index for a table

    1. whether you have defined an index for any database columns

  5. automate database schema migration with the Flyway tool

    1. whether you have successfully populated the database schema from a set of files

  6. define a DataSource to interface with the RDBMS

    1. whether a DataSource was successfully injected into the JUnit class

1.1.5. Additional Details

  1. 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 tests
    2 assignment-tests profile is activated for these service/DB-level tests only
  2. The following starter configuration files are used by the tests in this section:

    1. DbAssignmentTestConfiguration - discussed above. Provides a @SpringBootConfiguration class that removes the @SpringBootApplication dependencies from view.

    2. DbClientTestConfiguration - this defines the @Bean factories for the DbTestHelper and any supporting components.

    3. 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:

  1. define a PersistenceContext containing an @Entity class

  2. inject an EntityManager to perform actions on a Persistence Unit and database

  3. map a simple @Entity class to the database using JPA mapping annotations

  4. perform basic database CRUD operations on an @Entity

  5. define transaction scopes

  6. 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.

assignment5a autorentals jpa2
Figure 2. AutoRental Entity

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

  1. Create a Business Object (BO)/@Entity 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:

    1. identify the class as a JPA @Entity

    2. map the class to the DB table you created in the previous section using the @Table annotation.

    3. identify a String primary key field with JPA @Id

    4. supply a default constructor

    5. map the attributes of the class to the columns you created in the previous section using the @Column annotation.

    6. 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.
    7. 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 Key
      import 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 the src/main portion of your code. The JUnit test will make the condition and successful correction obvious.
  2. 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.

    1. map from BO to DTO

    2. 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) { ... }
    3. 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  {
  3. Implement a class that implements the JpaAssignmentService<DTO,BO> interface.

    1. Instantiate this as a component in the "assignment-tests" profile.

  4. Implement the mapAndPersist method in your JpaAssignmentService. It must perform the following:

    1. accept a AutoRental DTO

    2. map the DTO to a AutoRental BO (using your mapper)

    3. persist the BO

    4. map the persisted BO to a DTO (will have a primary key assigned)

    5. 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.
  5. Implement the queryByRentalDateRange method in the JpaAssignmentService using a @NamedQuery. It must perform the following:

    1. query the database using a JPA @NamedQuery with JPA query syntax to locate AutoRental BO objects within a startDate/endDate range, inclusive

      You may use the following query to start with and add ordering to complete

      select r from AutoRentalBO r where startDate <= :endDate and endDate <= :startDate
      1. :startDate and :endDate are variable LocalDate values passed in at runtime with setParameter()

      2. order the results by id ascending

      3. 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.
    2. map the BO list returned from the query to a list of DTOs (using your mapper)

    3. return the list of DTOs

  6. Enable (and pass) the provided MyJpa5b_EntityTest that extends Jpa5b_EntityTest. This test will perform checks of the above functionality using:

    1. DbTestHelper

    2. mapper

    3. your DTO and BO classes

    4. a functional JPA environment

  7. 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:

  1. inject an EntityManager to perform actions on a Persistence Unit and database

    1. whether an EntityManager/EntityManager was successfully injected into the JUnit test

    2. whether an EntityManager was successfully injected into your JpaAssignmentService implementation

  2. map a simple @Entity class to the database using JPA mapping annotations

    1. whether a new AutoRental BO class was created for mapping to the database

    2. whether the class was successfully mapped to the database table and columns

  3. implement a mapping tier between BO and DTO objects

    1. whether the mapper was able to successfully map all fields between BO to DTO

    2. whether the mapper was able to successfully map all fields between DTO to BO

  4. perform basic database CRUD operations on an @Entity

    1. whether the AutoRental BO was successfully persisted to the database

    2. whether a named JPA-QL query was used to locate the entity in the database

  5. define transaction scopes

    1. 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:

  1. declare a JpaRepository for an existing JPA @Entity

  2. perform simple CRUD methods using provided repository methods

  3. add paging and sorting to query methods

  4. implement queries based on predicates derived from repository interface methods

  5. implement queries based on POJO examples and configured matchers

  6. 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.

assignment5a autorentals repo
Figure 3. AutoRental Repository

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

  1. define a AutoRental JPARepository that can support basic CRUD and complete the queries defined below.

  2. enable JpaRepository use with the @EnableJpaRepositories annotation on a @Configuration class

    Spring 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 the src/main portion of your code. The JUnit test will make the condition and successful correction obvious.
  3. 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.

  4. implement the findByAutoIdByDerivedQuery method details which must

    1. accept a autoId and a Pageable specification with pageNumber, pageSize, and sort specification

    2. return a Page of matching BOs that comply with the input criteria

    3. this query must use the Spring Data Derived Query technique **

  5. implement the findByExample method details which must

    1. accept a AutoRental BO probe instance and a Pageable specification with pageNumber, pageSize, and sort specification

    2. return a Page of matching BOs that comply with the input criteria

    3. 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.
  6. implement the findByAgeRangeByAnnotatedQuery method details which must

    1. accept a minimum and maximum age and a Pageable specification with pageNumber and pageSize

    2. return a Page of matching BOs that comply with the input criteria and ordered by id

    3. 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.
  7. 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.

  8. 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:

  1. declare a JpaRepository for an existing JPA @Entity

    1. whether a JPARepository was defined and injected into the assignment service helper

  2. perform simple CRUD methods using provided repository methods

    1. whether the database was populated with test instances

  3. add paging and sorting to query methods

    1. whether the query methods where implemented with pageable specifications

  4. implement queries based on predicates derived from repository interface methods

    1. whether a derived query based on method signature was successfully performed

  5. implement queries based on POJO examples and configured matchers

    1. whether a query by example query was successfully performed

  6. implement queries based on @NamedQuery or @Query specification

    1. 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:

  1. declare project dependencies required for using Spring’s MongoOperations/MongoTemplate API

  2. define a connection to a MongoDB

  3. 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.

assignment5b autorentals mongo client
Figure 4. Mongo Client Connection
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.

@SpringBootTest(...)
@ActiveProfiles(profiles={"assignment-tests","test"}, resolver = TestProfileResolver.class)
//@ActiveProfiles(profiles={"assignment-tests","test", "mongodb"})
public class MyMongo5a_ClientTest extends Mongo5a_ClientTest {

2.1.3. Requirements

  1. 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.properties
    logging.level.org.springframework.data.mongodb=DEBUG
    1. provide a mongodb profile option to use an external MongoDB server instead of the Flapdoodle test instance

      application-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 URL
      Spring 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 Testing
      Flapdoodle 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 the spring.data.mongodb connection properties.
  2. Configure the application to establish a connection to the database and establish a MongoOperations (the interface)/MongoTemplate (the commonly referenced implementation class)

    1. declare a dependency on spring-boot-starter-data-mongo

    2. declare a dependency on the de.flapdoodle.embed.mongo database driver for default testing with scope=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.
  3. Enable (and pass) the provided MyMongo5a_ClientTest that extends Mongo5a_ClientTest. This test will verify connectivity to the database.

  4. 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:

  1. declare project dependencies required for using Spring’s MongoOperations/MongoTemplate API

    1. whether required Maven dependencies where declared to operate and test the application with Mongo

  2. define a connection to a MongoDB

    1. whether a URL to the database was defined when the mongodb profile was activated

  3. inject an MongoOperations/MongoTemplate instance to perform actions on a database

    1. whether a MongoOperations client could be injected

    2. 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 tests
    2 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:

  1. implement basic unit testing using an (seemingly) embedded MongoDB

  2. define a @Document class to map to MongoDB collection

  3. perform whole-document CRUD operations on a @Document class using the Java API

  4. 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.

assignment5b autorentals mongo doc
Figure 5. AutoRental Entity
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

  1. 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:

    1. identify the class as a Spring Data Mongo @Document and map it to the "rentals" collection

    2. identify a String primary key field with Spring Data Mongo @Id

      This is a different @Id annotation than the JPA @Id annotation.
    3. supply a default constructor

  2. Reuse the mapper class from the earlier JPA Entity portion of this assignment.

  3. Implement a class that implements the MongoAssignmentService<DTO,BO> interface.

    1. Instantiate this as a component in the "assignment-tests" profile.

  4. Implement the mapAndPersist method in your MongoAssignmentService. It must perform the following:

    1. accept a AutoRental DTO

    2. map the DTO to a AutoRental BO (using your mapper)

    3. save the BO to the database

    4. map the persisted BO to a DTO (will have a primary key assigned)

    5. 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.

  5. Implement the queryByRentalDateRange method in your MongoAssignmentService. It must perform the following:

    1. query the database to locate matching AutoRental BO objects within a startDate/endDate range, inclusive

      You 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)))
      1. :startInclusive and :endInclusive are variable LocalDate values

      2. order the results by id ascending

    2. map the BO list returned from the query to a list of DTOs (using your mapper)

    3. return the list of DTOs

  6. 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

  7. 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:

  1. define a @Document class to map to MongoDB collection

    1. whether the BO class was properly mapped to the database, including document and primary key

  2. perform whole-document CRUD operations on a @Document class using the Java API

    1. 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:

  1. declare a MongoRepository for an existing @Document

  2. implement queries based on predicates derived from repository interface methods

  3. implement queries based on POJO examples and configured matchers

  4. implement queries based on annotations with JSON query expressions on interface methods

  5. 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.

assignment5b autorentals mongo repo
Figure 6. Mongo Repository

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

  1. define a AutoRental MongoRepository that can support basic CRUD and complete the queries defined below.

  2. enable MongoRepository use with the @EnableMongoRepositories annotation on a @Configuration class

    Spring 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 the src/main portion of your code. The JUnit test will make the condition and successful correction obvious.
  3. 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.

  4. implement the findByAutoIdByDerivedQuery method details which must

    1. accept a autoId and a Pageable specification with pageNumber, pageSize, and sort specification

    2. return a Page of matching BOs that comply with the input criteria

    3. this query must use the Spring Data Derived Query technique **

  5. implement the findByExample method details which must

    1. accept a AutoRental BO probe instance and a Pageable specification with pageNumber, pageSize, and sort specification

    2. return a Page of matching BOs that comply with the input criteria

    3. 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.
  6. implement the findByDateRangeByAnnotatedQuery method details which must

    1. accept a startDate and endDate (inclusive) and a Pageable specification with pageNumber and pageSize

    2. return a Page of matching BOs that comply with the input criteria and ordered by id

    3. 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.
  7. Enable (and pass) the provided MyMongo5c_RepositoryTest that extends Mongo5c_RepositoryTest. This test will populate the database with content and issue query requests to your MongoAssignmentService implementation.

  8. 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:

  1. declare a MongoRepository for an existing @Document

    1. whether a MongoRepository was declared and successfully integrated into the test case

  2. implement queries based on predicates derived from repository interface methods

    1. whether a dynamic query was implemented via the expression of the repository interface method name

  3. implement queries based on POJO examples and configured matchers

    1. whether a query was successfully implemented using an example with a probe document and matching rules

  4. implement queries based on annotations with JSON query expressions on interface methods

    1. whether a query was successfully implemented using annotated repository methods containing a JSON query and sort documents

  5. add paging and sorting to query methods

    1. 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:

  1. implement a service tier that completes useful actions

  2. implement controller/service layer interactions relative to DTO and BO classes

  3. determine the correct transaction propagation property for a service tier method

  4. implement paging requests through the API

  5. 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.

assignment5a autorentals jpa4
Figure 7. API/Service/DB End-to-End

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:

  • autorenters-support-svcjpa provides an implementation of Autos using JpaRepository

  • autorenters-support-svcmongo provides an implementation of Renters using MongoRepository

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

  1. 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.
  2. Update/Replace your legacy AutoRental Service and Repository components with a service and repository based on Spring Data Repository.

    1. all CRUD calls will be handled by the Repository — no need for DataSource, EntityManager or MongoOperations/MongoTemplate

    2. 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.
    3. the service should

      1. 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.
    1. 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));
  3. 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 Approach
    public interface HomesPageableAPI extends HomesAPI {
        public static final String HOMES_PAGED_PATH = "/api/homes/paged";

    There is a convenience method within PageableDTO (from ejava-dto-util) that will convert pageNumber, pageSize, and sort to a Spring Data Pageable.

    @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();
  4. 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 (from ejava-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 (from autorenters-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);
  5. Add the capability to your API calls to provide and process the additional page information.

    There is a convenience method within PageableDTO (from ejava-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
  6. 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.
  7. 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:

  1. implement a service tier that completes useful actions

    1. whether you successfully implemented a query for AutoRentals for a specific autoId

    2. whether the service tier implemented the required query with Pageable inputs and a Page response

    3. whether this was demonstrated thru a JUnit test

  2. implement controller/service layer interactions when it comes to using DTO and BO classes

    1. whether the controller worked exclusively with DTO business classes and implemented a thin API facade

    2. whether the service layer mapped DTO and BO business classes and encapsulated the details of the service

  3. determine the correct transaction propagation property for a service tier method

    1. depending on the technology you select and the usecase you have implemented — whether the state of the database can ever reach an inconsistent state

  4. implement paging requests through the API

    1. whether your controller implemented a means to express Pageable request parameters for queries

  5. implement page responses through the API

    1. whether your controller supported returning a page-worth of results for query results

3.1.5. Additional Details

  1. 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 {