Consider this:
-bash-4.2$ pwd /u/dgerman/apache/htdocs/spring-boot/fall2019/step09 -bash-4.2$ tree . +-- pom.xml +-- src +-- main +-- java | +-- com | +-- example | +-- demo | +-- HomeController.java | +-- Job.java | +-- JobRepository.java | +-- SpringBoot09Application.java +-- resources +-- application.properties +-- templates +-- jobform.html +-- list.html 8 directories, 8 files -bash-4.2$
You are using an in-memory database to store information about jobs. The information is captured from a user (or users) via an HTML form. The process is the same as for any other database (MySQL, Postgres, SQL Server, etc.), and you benefit from the Java Persistence (What is JPA?, Learn JPA) API's ability to talk to many different databases using the same Spring Boot code.Model
The Job Class: You are creating a class that will become a table in the H2 database. The annotations being used will determine how your application interacts with the database (e.g., automatically generating theControllerId
which is used as the primary key for that table) and what kind of information the database stores in each field.The Job Repository: This acts as a pipeline to the database. The Job Repository has built-in methods that you can use to save, locate and delete data. The Job Repository can return single or multiple instances of the jobs that are in the database, depending on the criteria used to locate fields.
This is where the action happens. Routes are mapped out for each action: Creating, Reading, Updating and Deleting data (CRUD).ViewWhat does
@Autowired
do?
When you create an instance of an object, you use the well known instantiation formatObjectClass someObject = new ObjectClass();
This creates an instance of the object, and you can use it within the scope of the object reference. ForJobRepository
, that would mean that you had to instantiate the object within each method that used toJobRepository
class, but that would be a pain.@Autowired
tells the compiler to instantiate the repository object when the application runs, so you don't have to type out that line so many times.@RequestMapping("/")
When a user visits the default route the user will see a list of records that have been saved in the database. This is because the JobRepository is being used to retrieve all available records, and the result of this search is being passed to the view (in an Iterable object called jobs). The view will then display the individual elements of the jobs object using the Thymeleaf loop.@GetMapping("/add")
When a user visits the default route a new and empty instance of theJob
class will be created. This will be passed to the view (where it is referred to as a job) so that the user's input can be stored in fields within that model, and validated according to the rules set in theJob
class.@PostMapping("/process")
When a user presses the submit button the view returns to the controller to execute the method under this route. This method checks the object that was passed to the view. That object is now populated with the user's input, which can be validated against the rules in the Job class. The@Valid
annotation is used with the BindingResult object to check the object for validity according to the validation constraints. If the user has entered invalid data each invalid field input is highlighted. Once the user enters valid information for all required inputs, the controller will return to the default route. "redirect" is being used to call a route. Note that if you use Model to create a model attribute, the job object that that has been created and made available to the controller will disappear. In this case, ONLY use redirects for routes that support GET mapping.
Remember to include the thymeleaf XML namespace so you can use the thymeleaf tags in the HTML template. Thejobform.html
form has two purposes. It accepts information from the user and also informs the user about invalid data that was entered after the form has been submitted. When a new (and empty) object is passed to it, the form ignores the error messages, because theth:if
statements evaluate tofalse
. When an error occurs, the errors captured in the BindingResult object are passed back to the form, and checked for using${#fields.hasErrors('fieldName')}
where fieldname is the name of the field into which data was entered. If theth:if
statement evaluates totrue
then the span will display the default (or modified) error for that field:th:errors="*{title}"
Remember: the form action uses a thymeleaf statement,
th:action
. This maps to a named route in the controller. Thed method used is POST, since information will be submitted to the application. Sinceth:action
is being used, the form action is not required. If you still wish to use it, assign#
to the form action.This is where the items that were entered show up in the application. The object that was passed to the view jobs is looped through. This object is like a list, or array list and contains a number of objects. The
th:each
Thymeleaf statement is used to loop through the data. This can be confusing at first, but it is easier to understand if you think about it as an enhanced for loop. If you were looping through a similar object in the controller, you would do it this way:In Thymeleaf you are changing this slightly. The controller has already passed jobs to Thymeleaf. So now, the view has to iterate through it usingIterable <Job> jobs = jobRepository.findAll(); for (Job job : jobs) { System.out.println( job.getTitle() + " @ " + job.getEmployer()); System.out.println( job.getDescription() ); }th:each
. Look at the code below again and think about how similar it is to the enhanced for-loop above:Note that the getters in Thymeleaf look like the variables declared in the Job class. They actually call the getters<div th:each="job : ${jobs}"> <h4 th:inline="text">[[${job.title}]] @ [[${job.employer}]]</h4> <p th:text="${job.description}"></p> </div>.getTitle()
,.getEmployer()
, and.getDescription()
in the background to retrieve their values and display them in Thymeleaf. This is one of the most important reasons to add your getters and setters! If you don't, your application will throw an error while trying to render the Thymeleaf view.