Java 3 - 2026

Week 4

Week 1 - Setup Pet Clinic App. Deployment using Docker.
Week 2 - Domains, AWS, ER Diagrams and Databases, Understand the Pet Clinic Setup.

Week 3 - MVC, Repository interfaces, Thymeleaf, Internationalization, Controller Unit Tests, 

Week 4 - Use Case Narrative, Sequence Diagram, State Chart Diagram, Create new objects.
Lesson 5 - Users, roles, and permissions setup from Java 2. User Registration.

Lesson 6 - Homepage with content from the database. Custom not found and error pages.

Lesson 7 - Login and logout. Cookies. User permission access.

Lesson 8 - Edit and delete user profile. Password reset.
Lesson 9 - Pagination. Filtering and limiting records by category.
Lesson 10 - Web Sockets. Date and currency formatting.
Lesson 11 - Email and SMS messaging. Failed login attempts.
Lesson 12 - Shopping Cart. Payment processing.

Course Plan

  • Modify the Pet Clinic application based on the user stories and personas you created in Java 2.
  • Derive an entity-relationship diagram and SQL queries for the data you will collect.
  • Create user interface drawings and wireframes for all web layouts.
  • Write use case narratives and build diagrams for each feature you add.
  • Write unit tests as necessary.

Expectations

  • Open Docker Desktop. Click the Images tab to see your previous images.

  • If you are running out of space, delete any old containers/images you no longer need. When deleting an image, you may get "Image is in use. Delete the container that's using it and try again."

  • Build a new image.
    docker build -t <your-docker-username>/<your-projectname>:<your-version> .

  • For example:
    docker build -t mlhaus/petclinic:v2 .

  • Refresh your list of images in Docker Desktop to see the newly created one.

  • In the world of containers, tags like v1 are treated as "immutable". The best practice is to increment the version number (v2, v3) or use the Git commit hash (e.g., :git-a1b2c3d).

Build an Image

  • Use Docker to run your image locally.
    docker run -p 8099:8080 `
      -e MYSQL_URL="jdbc:mysql://<your-db-host>:3306/<your-db-name>?useSSL=true" `
      -e MYSQL_USER="<your-db-user>" `
      -e MYSQL_PASS="<your-db-password>" `
    <your-docker-username>/<your-projectname>:<your-version>

  • Remember to use backticks ` with Windows Powershell or backslashes \ with Git Bash.

  • Change MYSQL_PASS to MYSQL_PASSWORD if that is what you used.

  • Visit http://localhost:8099 to confirm it works.

  • Press Ctrl + C in the terminal to stop the server.

Run the Image Locally

  • Use Docker to push your image to Docker Hub.
    docker push <your-docker-username>/<your-projectname>:<your-version>

  • Go to your Docker Hub Repositories list to see your image.
    https://hub.docker.com/repositories/

  • Click the repository name to see your tag list.

Push the Image to Docker Hub

  • To deploy a new Docker container image to an existing Azure Container App, you need to update the container app's image reference using the Azure CLI.

  • Update the container app with the new image tag.
    az containerapp update `
        --name <your-container-app-name> `
        --resource-group <your-resource-group-name> `
        --image <your-docker-username>/<your-imagename>:<your-version>

  • During the "create" phase you included environment variables. You do not need to include them again.

  • View your live Azure Container App.
    https://portal.azure.com/#browse/Microsoft.App%2FcontainerApps​

  • "Find Owners" should be changed to "Find Schools".

  • For extra credit, set up a custom domain.

Deploy the Image to Azure

  • In ISO/IEC 12207:2008, Process 7.1.4 refers to the Software Detailed Design Process

  • The purpose of the Software Detailed Design process is to provide a detailed design for the software components, including data structures, databases, and internal interfaces, that enables coding to take place. 

  • In other words, you need to spend time planning and designing before coding.

  • In a Spring Boot application, behavior (Sequence) and data state (State Chart) diagrams are often valuable.

SDLC ISO/IEC 12207

  • Description: Create New School

  • Actor: Administrator

  • Precondition: User is logged in and viewing the School List.

  • Normal Flow:

    1. User clicks "Add School".

    2. System displays the School Creation Form.

    3. User enters required values "Name" and "Domain".

    4. System validates that the domain does not already exist.

    5. System saves the new School with status ACTIVE.

    6. System redirects user to the School Details page (or School List).

  • Alternative Flows:

    • Validation Fail: Name or Domain is empty -> System re-displays form with error "Name is required" or "Domain is required".

    • Duplicate Fail: Domain exists -> System re-displays form with error "Domain already registered".

Use Case Narrative

Sequence Diagram

sequenceDiagram
    autonumber
    actor User as School Admin
    participant Browser
    participant Controller as SchoolController
    participant Repo as SchoolRepository
    participant View as Thymeleaf<br>Template Engine

    Note over User, View: Phase 1: Requesting the Form

    User->>Browser: Click "Add School" button
    Browser->>Controller: GET /schools/new
    activate Controller
    Controller-->>Controller: new School()
    Controller->>View: return "schools/createOrUpdateSchoolForm"
    deactivate Controller
    View-->>Browser: Render HTML Form

    Note over User, View: Phase 2: Submitting the Data

    User->>Browser: Fill out and submit form
    Browser->>Controller: POST /schools/new
    activate Controller
    Controller->>Controller: Validate form
    alt Validation Failed
        Controller->>View: return "schools/createOrUpdateSchoolForm"
        View-->>Browser: Render HTML Form with Error Messages
    else Validation Passed
        Controller->>Repo: save(school)
        activate Repo
        Repo-->>Controller: School Entity Saved
        deactivate Repo
        Controller-->>Browser: Redirect to "/schools"
    end
    deactivate Controller

    Browser->>Controller: GET /schools (Redirect)
    activate Controller
    Controller->>Repo: findAll(Pageable)
    activate Repo
    Repo-->>Controller: Page<School>
    deactivate Repo
    Controller->>View: return "schools/schoolList"
    deactivate Controller
    View-->>Browser: Render HTML Updated School List
  • A State Chart Diagram models the lifecycle of a single object.

  • It shows every possible "state" the object can be in and exactly what events cause it to move from one state to another.

  • New School: The user typed data in the form, no ID is set yet.

  • Active School: The normal state. The school is visible.

  • Inactive/Suspended School: The school exists in the system but is flagged (e.g., stopped paying for a subscription or closed).

  • Deleted School: The school was "removed" from the database with a deleted_at timestamp. It is hidden from queries.

State Chart Diagram

stateDiagram
    [*] --> New : User clicks "Add School"
    
    New --> Active : save() [Default]
    
    state "Persisted (In Database)" as Persisted {
        
        state "Visible / Live" as Live {
            Active --> Inactive : Set Status = INACTIVE
            Inactive --> Active : Set Status = ACTIVE
            
            Active --> Suspended : Set Status = SUSPENDED
            Suspended --> Active : Set Status = ACTIVE
        }
        
        Live --> Deleted : delete() (Set deleted_at = NOW)
        
        state "Soft Deleted" as Deleted {
             [*] --> Hidden
             note right of Hidden : Excluded from default queries \n(@SQLRestriction)
        }
        
        Deleted --> Live : Restore (Set deleted_at = NULL)
    }
  • When using Microsoft OneDrive or a cloud storage, if you new unit tests, you will likely get an error: "Caused by: java.nio.file.AccessDeniedException".

  • Build tools like Gradle create, modify, and delete thousands of temporary files (inside build/) in milliseconds.

    • When Gradle creates a file, OneDrive notices the new file and immediately tries to lock it to upload it to the cloud, Gradle tries to write to or rename that file a millisecond later, and Gradle is denied access because OneDrive is holding the lock.

  • Option 1: Move the project to a non-synced folder to avoid the syncing process fighting with the compiler.

  • Option 2: Pause Sync. Click the OneDrive icon in your taskbar. Select Pause Syncing > 2 hours.

    • In IntelliJ, run Clean (Gradle tab > Tasks > build > clean).

  • Run your tests again.

Cloud Storage Problem

  • Following a Test-Driven Development (TDD) approach, we can write the tests before the implementation of any code.

  • Add these import statements.
    import org.junit.jupiter.api.DisplayName;​
    import static org.mockito.Mockito.verify;

    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

  • Add following new test methods inside the class.

SchoolControllerTest

@Test
@DisplayName("User clicks \"Add School\" -> GET /schools/new")
void testInitCreationForm() throws Exception {
	mockMvc.perform(get("/schools/new"))
		.andExpect(status().isOk())
		.andExpect(view().name("schools/createOrUpdateSchoolForm"))
		.andExpect(model().attributeExists("school"));
}
  • Add these new test methods inside the class.

SchoolControllerTest

@Test
@DisplayName("Validation Passed -> verify that the controller tells the repository to save() the school and then redirects us.")
void testProcessCreationFormSuccess() throws Exception {
	mockMvc.perform(post("/schools/new")
			.param("name", "University of Iowa")
			.param("domain", "uiowa.edu"))
		.andExpect(status().is3xxRedirection())
		.andExpect(redirectedUrl("/schools"));

	// Verify that the repository.save() method was actually called
	verify(schools).save(any(School.class));
}

@Test
@DisplayName("Validation Failed -> send an empty domain and ensure the controller returns us to the form instead of saving.")
void testProcessCreationFormHasErrors() throws Exception {
	mockMvc.perform(post("/schools/new")
			.param("name", "Bad School")
			.param("domain", "")) // Empty domain should trigger @NotEmpty
		.andExpect(status().isOk()) // 200 OK because we are re-rendering the form, not redirecting
		.andExpect(model().attributeHasErrors("school"))
		.andExpect(model().attributeHasFieldErrors("school", "domain"))
		.andExpect(view().name("schools/createOrUpdateSchoolForm"));
}

If you run SchoolControllerTest, it should fail: 404 Not Found (because /schools/new is not mapped in the controller yet).

  • We need to add a link on the schoolList.html page so users can actually find this new form.

  • Open src/main/resources/templates/schools/schoolList.html and add this button code right below your <h2> and above your <table>:

The "Add School" Button

<a th:href="@{/schools/new}" class="btn btn-primary" style="margin-bottom: 15px;">
  <span class="fa fa-plus"></span>
  <span th:text="#{addSchool}">Add School</span>
</a>
  • Now you can run the tests and app.

  • Click "Find Schools," click "Add School," and try creating a new entry. It should save and redirect you back to the list.

  • Validation occurs, but no error or success messages display. We'll add that next.

  • In ISO 12207, Process 7.1.5 refers to translating the logic defined in your Sequence Diagram directly into Java syntax.

  • Open the SchoolController.java and add the following code.

  • Add these imports for the validation logic and the binding result (which holds validation errors).
    import jakarta.validation.Valid;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;

SchoolController Get and Post

@GetMapping("/schools/new")
public String initCreationForm(Map<String, Object> model) {
	// Phase 1 of Sequence Diagram
    // 1. Create the blank object (State: New)
	School school = new School();
	// 2. Add it to the model so the Thymeleaf form can bind data to it
	model.put("school", school);
	// 3. Return the view
	return "schools/createOrUpdateSchoolForm";
}

@PostMapping("/schools/new")
public String processCreationForm(@Valid School school, BindingResult result) {
	// Phase 2 of Sequence Diagram
	// 1. Check Validation
	if (result.hasErrors()) {
		// Validation Failed: Return to the form to show errors
		return "schools/createOrUpdateSchoolForm";
	}
	// 2. Save Data (Validation Passed)
	// Note: The status defaults to ACTIVE because of your School.java definition
	schoolRepository.save(school);
	// 3. Redirect to the list
	return "redirect:/schools";
}
  • If you try to run the tests or the program, it will crash because we haven't created the HTML Template yet.

  • Create a file called "createOrUpdateSchoolForm.html" in the "src/main/resources/templates/schools/" folder.

  • In the Spring PetClinic architecture, forms rely on a reusable fragment called inputField to handle the labels, inputs, and error messages automatically to keep your form code very clean.

HTML Template

<html xmlns:th="https://www.thymeleaf.org"
      th:replace="~{fragments/layout :: layout (~{::body},'schools')}">

<body>

<h2 th:text="#{addSchool}">Add School</h2>

<form th:object="${school}" class="form-horizontal" id="add-school-form" method="post">

  <input th:replace="~{fragments/inputField :: input (#{label.school.name}, 'name', 'text')}" />

  <input th:replace="~{fragments/inputField :: input (#{label.school.domain}, 'domain', 'text')}" />

  <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
      <button class="btn btn-primary" type="submit" th:text="#{addSchool}">Add School</button>
    </div>
  </div>

</form>

</body>
</html>
  • th:object="${school}": This binds the entire form to the School object we created in the Controller's initCreationForm method.

  • th:replace="~{fragments/inputField ...}": Instead of writing 10 lines of HTML for every input (label, input box, validation error span), we call this helper file.

    • It automatically checks bindingResult for errors.

    • It highlights the box red if validation fails.

    • It preserves the user's input if the page reloads due to an error.

Thymeleaf Features

  • By default, you will see a generic message like "must not be empty".

  • If you want to customize this (e.g., "School Name is required"), you can define them in your messages.properties file.

  • Spring looks for keys in the format: Annotation.objectName.fieldName

  • The NamedEntity class uses @NotBlank instead of @NotEmpty.

  • Going forward, I will only add English translations. Adding Spanish/Korean translations is not required to pass unit tests.

Custom Error Messages

label.school.name=Name
label.school.domain=Domain
NotBlank.school.name=School Name is required
NotEmpty.school.domain=Domain Name is required
label.school.name=Nombre
label.school.domain=Dominio
NotBlank.school.name=El nombre de la escuela es obligatorio
NotEmpty.school.domain=El nombre del dominio es obligatorio
label.school.name=이름
label.school.domain=도메인
NotBlank.school.name=학교 이름은 필수입니다
NotEmpty.school.domain=도메인 이름은 필수입니다
  • Update the contents of fragments/inputField.html to display red error messages.

  • Restart the application. Changes to fragments often need a full restart to clear the cache.

inputField.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<body>
<th:block th:fragment="input (label, name, type)">

  <div class="mb-3"> 
    <label th:for="${name}" class="form-label" th:text="${label}"></label>

    <div class="position-relative"> 
      <div th:switch="${type}">
      
        <input th:case="'text'"
               th:class="${#fields.hasErrors(name)} ? 'form-control is-invalid' : 'form-control'"
               type="text" th:field="*{__${name}__}" />

        <input th:case="'date'"
               th:class="${#fields.hasErrors(name)} ? 'form-control is-invalid' : 'form-control'"
               type="date" th:field="*{__${name}__}" />

        <input th:case="'password'"
               th:class="${#fields.hasErrors(name)} ? 'form-control is-invalid' : 'form-control'"
               type="password" th:field="*{__${name}__}" />
      </div>

      <span th:if="${#fields.hasErrors(name)}"
            class="fa fa-remove position-absolute text-danger"
            style="top: 10px; right: 10px;"
            aria-hidden="true"></span>

      <div th:if="${#fields.hasErrors(name)}" class="invalid-feedback" style="display:block">
        <span class="help-inline" th:errors="*{__${name}__}"></span>
      </div>

    </div>
  </div>
</th:block>
</body>
</html>
  • Your Controller checks for errors (if (result.hasErrors())),

  • Your School entity must define what an "error" actually is.

  • You need to explicitly tell Java that the name and domain fields are Required with @NotEmpty annotations.

  • Browser: You submit an empty form.

  • Controller: Spring sees @Valid School school. It looks at the School class.

  • Validator: It sees @NotEmpty on the name field. It checks the data. It sees it is empty.

  • Result: It flags a field error on "name" or "domain".

  • Controller Logic: result.hasErrors() now returns true.

  • Thymeleaf: The inputField fragment sees the error and automatically renders the red styling and the error message.

Data Validation

  • The extra set of single quotation marks around the types text, date, and password are required by Thymeleaf to distinguish between a literal string and a variable name.

  • th:case="'text'": Checks if the variable ${type} equals the word "text". (This is what you want).

  • th:case="text": Checks if the variable ${type} equals the value of a variable named text. Since you don't have a variable named text in your Java model, this would evaluate to null and the case would never match.

Data Validation

  • Validate .edu domain. Is there an API?

2027

  • If you try to create a new school with a domain name that already exists, you'll get a 'Duplicate entry' error.

  • There is no built-in @Unique annotation in the standard Jakarta Validation (Bean Validation) specification because standard validators only check the data in the object itself, not the database requirements.

  • However, you can create your own custom annotation to do this. This keeps your Controller clean and puts the logic where it belongs: in the Model.

  • Create the Interface UniqueDomain.java in a new package,
     org.springframework.samples.petclinic.validation.

Unique Validation

package org.springframework.samples.petclinic.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// We target the TYPE (Class) so we can access both the 'domain' and the 'id'
@Target({ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueDomainValidator.class)
public @interface UniqueDomain {
    String message() default "This domain is already registered";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  • Create a class UniqueDomainValidator.java in the same package.

Validator Logic

package org.springframework.samples.petclinic.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.samples.petclinic.school.School;
import org.springframework.samples.petclinic.school.SchoolRepository;

import java.util.Optional;

@Component
public class UniqueDomainValidator implements ConstraintValidator<UniqueDomain, School> {

	@Autowired
	private SchoolRepository schoolRepository;

	private static ApplicationContext staticContext;

	@Override
	public boolean isValid(School school, ConstraintValidatorContext context) {
		if (schoolRepository == null && staticContext != null) {
			schoolRepository = staticContext.getBean(SchoolRepository.class);
		}

		if (schoolRepository == null) {
			return true;
		}

		if (school == null || school.getDomain() == null) {
			return true; // Lets @NotEmpty handle null checks
		}

		String domainToCheck = school.getDomain();
		Optional<School> existingSchool = schoolRepository.findByDomain(domainToCheck);

		if (existingSchool.isPresent()) {
			// Duplicate Found: If the ID of the found school is DIFFERENT from the school being validated, it's a conflict.
			// (This allows you to "Update" School #1 without changing its domain)
			if (!existingSchool.get().getId().equals(school.getId())) {

				// This block moves the error from the "Class" level to the specific "domain" field
				// so it appears next to the input box in your form.
				context.disableDefaultConstraintViolation();
				context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
					.addPropertyNode("domain")
					.addConstraintViolation();

				return false;
			}
		}

		return true;
	}
}
  • Now you can use the annotation in the School model.

  • Note that because we wrote a Class-Level validator (to handle the ID check safely), the annotation goes above the public class School line, not on the field itself.

Apply Annotation to Model

package org.springframework.samples.petclinic.school;

import org.springframework.samples.petclinic.validation.UniqueDomain; 
// ... other imports

@Entity
@Table(name = "schools")
@UniqueDomain // <--- validate unique domain
// ... other annotations
public class School extends NamedEntity {
  • When you submit the form, Spring runs @Valid. It sees @UniqueDomain on the class and runs UniqueDomainValidator.isValid(school). It then calls findByDomain("kirkwood.edu").

  • If New School: ID is null. Found existing school (ID 1).

    • null != 1 is True. Invalid.

  • If Editing School 1: ID is 1. Found existing school (ID 1).

    • 1 != 1 is False. Valid (It found itself).

  • If Editing School 1 (Change to School 2's domain): ID is 1. Found existing school (ID 2).

    • 1 != 2 is True. Invalid.

  • UI Feedback: The context.addPropertyNode("domain") line ensures the error message appears right under the Domain input field in your HTML, exactly like @NotEmpty errors do.

Domain Validation

  • Owner.java:

    • Relationships: Has a One-to-Many relationship with Pet. It manages this list with methods like addPet() and getPet().

    • Validation: Includes constraints like @Pattern(regexp = "\\d{10}") to ensure telephone numbers are exactly 10 digits.

  • Pet.java:

    • Relationships:

      • One-to-Many with Visit (a pet can have multiple doctor visits).
      • Many-to-One with PetType (a pet belongs to one species).

    • Logic: It automatically sorts visits by date using @OrderBy("date ASC").

Relationships, Validation, Logic

  • OwnerRepository.java: The primary way to access owner data.

    • Key Feature: It extends JpaRepository and adds a custom search method: findByLastNameStartingWith, which supports the "Find Owner" search bar feature.

  • PetTypeRepository.java: Provides a list of all valid animal types (findPetTypes), ordered by name, which is used to populate dropdown menus in the UI.

Repository Layer (The Database Access)

  • OwnerController.java: Manages the high-level Owner operations.

    • Routes: Handles listing owners (/owners), searching (/owners/find), and creating/editing owner profiles (/owners/new, /owners/{ownerId}/edit).

  • Note the use of @GetMapping for GET requests and @PostMapping for POST requests.

  • One bug I discovered is

Controller Layer (The HTTP Request Handling)

  • PetController.java: Manages the "sub-resource" of adding or editing a pet for a specific owner.

    • Routes: It operates under the /owners/{ownerId} path. For example, creating a new pet is handled at /owners/{ownerId}/pets/new.

  • VisitController.java: Manages the creation of new visits.

    • Routes: This is deeply nested: /owners/{ownerId}/pets/{petId}/visits/new. It ensures that before a visit is created, the system validates that the Pet and Owner actually exist.

Controller Layer (The HTTP Request Handling)

  • PetValidator.java: A custom validator that ensures a Pet has a name, a birthdate, and a type. It is manually applied in the PetController.

  • PetTypeFormatter.java: Helps Spring MVC convert the string "Cat" from a web form into the actual PetType Java object, and vice versa.

Support Classes (Validation & Formatting)

  • the src/test folder contains the automated tests that ensure the application works correctly. They are organized to test different "layers" of the application, from simple logic to full database integrations.

  • These files test start up the full Spring Boot application context to verify that all components work together.

  • PetClinicIntegrationTests.java: The main integration test suite that checks standard application flows.

  • MySqlIntegrationTests.java and PostgresIntegrationTests.java: These are specialized tests that verify the application works correctly when connected to real MySQL or PostgreSQL databases (often using Testcontainers) instead of the default in-memory H2 database.

Integration Tests

  • Located in subpackages like owner and vet, these tests use @WebMvcTest. They focus only on the URL routing and HTML generation. They "mock" (fake) the service layer so they can test the UI without needing a real database.

  • OwnerControllerTests.java: Verifies that URLs like /owners/new return the correct view and that form submissions work.

Controller Tests (The Web Layer)

  • These test individual classes in isolation.

  • PetValidatorTests.java: Checks specifically that the PetValidator correctly identifies invalid inputs (like a missing name).

  • VetTests.java: Tests the internal logic of the Vet entity, such as serialization.

    • Serialization is the process of converting an object's state into a sequence of bytes, which can then be saved to a file, stored in a database, or transmitted over a network.

    • The reverse process, converting the byte stream back into a live object, is called deserialization.

  • PetTypeFormatterTests.java: Ensures the string-to-object conversion for Pet Types functions as expected.

Unit Tests (Specific Logic)

  • CrashControllerTests.java: specifically tests that the "Error" page triggers correctly.

  • I18nPropertiesSyncTest.java: A quality assurance test that ensures your translation files (like Spanish and English) stay in sync and don't have missing keys.

  • EntityUtils.java: Not a test itself, but a helper class used by other tests to look up objects in collections.

System & Utility Tests

  • This is the Maven configuration file. It tells Java exactly which third-party libraries (dependencies) your project needs to run.

  • Parent POM: You will see a <parent> tag for spring-boot-starter-parent. This is the "magic" that manages versions for you. You don't need to specify the version for every single library because the parent knows which versions work together.

  • Starters: Notice dependencies like spring-boot-starter-web, spring-boot-starter-data-jpa, and spring-boot-starter-thymeleaf. These are pre-packaged bundles. For example, starter-web automatically gives you Tomcat (server), Spring MVC (framework), and Jackson (JSON parser) in one go.

  • Java Version: It explicitly sets the Java version to 17 (<java.version>17</java.version>).

pom.xml

  • Located in src/main/resources/, this is where you configure the application's behavior without changing Java code.

  • Database Settings: The line database=h2 tells the app to use the H2 in-memory database by default. This is why you don't need to install MySQL to run the demo; the database is created in RAM when you start the app and destroyed when you stop it.

  • Database Initialization: It points to schema.sql (to create tables) and data.sql (to load dummy data).

  • Logging: You can control how much info appears in the console. For example, logging.level.org.springframework=INFO.

application.properties

  • This is the file with the public static void main method that launches the app.

  • @SpringBootApplication: This single annotation actually triggers three powerful features:

    1. Configuration: It marks the class as a source of bean definitions.

    2. EnableAutoConfiguration: It tells Spring Boot to configure your app based on the libraries in your pom.xml 

    3. ComponentScan: It tells Spring to scan the current package (and all sub-packages) for your Controllers, Services, and Repositories.

PetClinicApplication.java

  • While application.properties is good for simple key-value pairs, complex configuration is done in Java classes marked with @Configuration.

    1. @EnableCaching: This annotation turns on Spring's caching ability.

    2. @Bean: The method petclinicCacheConfigurationCustomizer() is marked with @Bean. This tells Spring: "Execute this method and manage the object it returns.". It is used here to set up a specific cache for "vets" so the database doesn't have to be queried every time the user refreshes the veterinarian list.

CacheConfiguration.java

  • The term "Bean" is used in two distinct ways in this project. It can be confusing because they share the same name, but they serve different purposes.

What is a Java Bean?

  • In the comments of files like Vet.java and Owner.java, you will see them described as a "Simple JavaBean domain object".

  • A standard JavaBean is simply a Java class that follows a specific set of naming conventions so other tools can easily use it.

  • Private Properties: Data fields are hidden (e.g., private String address; in Owner.java).

  • Public Getters/Setters: The data is accessed via public methods named get... and set... (e.g., getAddress() and setAddress()).

  • In your project: Classes like Vet, Owner, Pet, and Visit are standard JavaBeans. They are just containers for your data.

  • No-Argument Constructor: It usually has a default constructor so frameworks can create an empty instance of it easily.

  • JavaBean is a pattern for writing data classes (like Vet or Owner).

 The "JavaBean" (Data Standard)

  • In CacheConfiguration.java and WebConfiguration.java, you saw the @Bean annotation.

  • A Spring Bean is an object that is created, configured, and managed by the Spring Framework itself (the "Spring Container").

  • Managed Lifecycle: You do not create these objects with new (e.g., new VetController()). Instead, Spring creates them for you at startup.

  • Dependency Injection: Spring "injects" these beans into other beans that need them. For example, VetRepository is a Spring Bean that is automatically injected into the VetController.

  • Spring Bean is a specific object living in the Spring system (like VetController or the CacheManager).

The "Spring Bean" (Managed Component)

Java 3 - Week 4

By Marc Hauschildt

Java 3 - Week 4

  • 142