Chapter 6 Tutorials
Required Validation
- Add the dependency to Spring Boot validation in the pom file.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- Copy the interface and bean from the chapter 5 enhanced example to a new folder
src/main/java/data/ch6/requiredValidation .
Rename the interface to RequestDataRequired . Rename the implementation
to RequestDataRequiredImpl .
- Remove the default validation from the implementation. The getters should simply
return the property without doing any testing in code.
- Add a declaration for a prototype bean to the configuration file, SimpleBean.java
@Bean("protoRequiredBean")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
com.bytesizebook.data.ch6.requiredValidation.RequestDataRequired getProtoRequiredBean() {
return new com.bytesizebook.data.ch6.requiredValidation.RequestDataRequiredImpl();
}
- Extend the interface from the interface from chapter 5.
- Add the
NotNull and Pattern annotations to the interface
getters that return a string. Use appropriate messages. The patterns for the hobby and aversion should only allow
three choices, ignoring case.
- Add an integer property for the days per week.
- Add the
Min and Max annotations to the getter for the days.
Use reasonable values and messages.
- Copy the JSPs to a new location named
ch6/requiredValidation .
- Modify each view for the days per week field.
- Modify the edit page so that it uses the Spring tag library for forms to show error messages.
- Copy the controller from the last example to a new class named
ControllerRequredValidation
in the controller/ch6/requiredValidation folder.
- Change the import for the bean to
com.bytesizebook.data.ch6.requiredValidation .
- Modify the request mapping annotation for the URL pattern
/ch6/requiredValidation/collect/ .
- Modify the instance variable of type
ObjectFactory so that it wraps the RequestDataRequired interface.
Annotate it with a qualifier for the prototype bean that was added to the configuration file for this example.
- Modify the
viewLocation method for the new location of the JSPs.
- Add the
Valid annotation to the confirm method that expects data.
- Add
BindingResult and RedirectAttributes to the same method,
after the parameter for the data.
- After testing for expired data, test if errors exist. If they do, add the errors and the data to the flash attributes and
redirect to the edit view. If no errors exist, redirect to the edit page.
Custom Editor
- This example rewrites the interface with
Integer but will still work with
the previous bean due to automatic boxing and unboxing.
- Copy the interface and bean from the above example to the folder
src/main/java/data/ch6/requiredValidation/editor .
Rename the interface to RequestDataCustom . Rename the implementation
to RequestDataCustomImpl .
- Change the type for the days per week property from
int to Integer
in the interface and implementation.
- Add a declaration for a prototype bean to the configuration file, SimpleBean.java
@Bean("protoCustomBean")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
RequestDataCustomImpl getProtoCustomBean() {
return new requiredValidation.RequestDataCustomImpl();
}
- Create a new folder for the controller named
ch6/requiredValidation/editor .
- Define an editor class that extends
CustomNumberEditor .
- Add a constructor to the editor that expects the type of the destination class and
a boolean indicating if the field is required. Call the super class with those parameters.
- Override the
setAsText method. Call the super class method of the same name. Catch
an IllegalArgumentException and set the value to 0.
- Copy the controller from the last example into the folder.
- Add a method for initializing the binder. Annotate the method with
InitBinder .
Add a parameter for a WebDataBinder from Spring. Spring will autowire
an instance for binding.
- On the binder parameter, call the
registerCustomEditor method. Pass
the Integer class, the name of the property and an instance of the custom
editor.
- Add a method for showing the expired view.
- Run the application. If a non-number is entered into the days per week field, a
zero will be entered by default.
Custom Validator
- A new interface is not needed for this example. The book declares one for the section but
does not use it.
- Create a new folder for the controller named
ch6/requiredValidation/validator .
- Define a validator class that extends
Validator .
- Override the
supports and validate methods.
- From the
supports method, return the isAssignableFrom method on the class of the bean.
- From the
validate method, call a helper method for each property.
- In the helper method for the hobby, test that it is not empty and not one of two words.
- In the helper method for the aversion, test that it is not empty and not one of two words.
- In the helper method for the days per week, test that it is in the correct range.
- Copy the controller from the last example into the folder. Give it a new name.
- Modify the request mapping annotation for the URL pattern
/ch6/requiredValidation/validator/collect/ .
- Modify the method for initializing the binder. On the binder parameter, call the
setValidator method. Pass
an instance of the validator.
Validation Groups
- Copy the interface and bean from the above example to the folder
src/main/java/data/ch6/requiredValidation/groups .
Rename the interface to RequestDataRequiredGroup . Rename the implementation
to RequestDataRequiredGroupImpl .
- Change the
NotNull annotations to NotEmpty .
- Add the group parameter to the annotations for
NotEmpty, Min and Max .
- Add a declaration for a prototype bean to the configuration file, SimpleBean.java
@Bean("protoGroupBean")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
RequestDataRequiredGroupImpl getProtoGroupBean() {
return new RequestDataRequiredGroupImpl();
}
- Create a new folder for this application named
ch6/requiredValidation/groups
- Create an interface named
Common that is empty.
- Copy the validator from the previous example to this folder and modify it.
-
Change the bean class to the one for this example, wherever the bean class
is referenced.
-
Remove the tests for empty hobby and aversion.
-
Remove the helper method for the days per week.
- Copy the controller from the custom validator example to this folder.
- Modify the URL pattern.
- Modify the
initBinder method and change setValidator to
addValidators , passing an instance of the new validator.
- Replace all occurrences of the bean interface with the one for this application.
- Modify the qualifier for the bean to the one for this application.
- Replace the
Valid annotation with the Validated annotation
in the confirm method, including the parameter for the class of the new interface.
Database Configuration
- Add the dependencies for JPA and H2 to the pom file. Run maven with clean and install.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
- Add three properties to the application properties to add the h2 console,
set the path to the h2 console, clear the associated datasource on restart and set a static URL for the
datasource at runtime.
spring.h2.console.path=/h2-console
spring.datasource.initialization-mode=always
spring.datasource.url:h2:mem:mydb
- Run the application and access the path for the h2 console.
Data Persistence
- Copy the bean, but not the interface, from the required validation
example to the folder
src/main/java/data/ch6/persistent/ .
Rename the implementation
to RequestDataPersistentImpl .
- Create an interface that extends the required validation interface and has
a getter for the id property.
- The implementation should implement the interface and
Serializable
- Add a property to the implementation for the id. Annotate the getter for the
id with the
Id and GeneratedValue annotations.
- Add a declaration for a prototype bean to the configuration file, SimpleBean.java
@Bean("protoPersistentBean")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
RequestDataPersistentImpl getProtoPersistentBean() {
return new RequestDataPersistentImpl();
}
- Create an interface for a CRUD repository for the bean and its id.
- Add a default method to the interface named
saveWrappedData
that inspects the source sent to the method and extracts the target object,
whether the bean is Spring scoped or in the session attributes.
- Create a new folder for the controller, in the location for controllers,
ch6/persistent/basic . This application introduces access to the
database, but does not implement all features, so it is named basic.
- Copy the required validation controller to this folder.
- Modify the URL pattern. Do not include 'collect' in the pattern. This is the
first application that will have paths other than 'collect'.
- Change the location of the JSPs.
- Preface all paths for the handler methods with
collect/ . The next iteration
of the application will have additional paths available.
- Replace all occurrences of the bean interface with the one for this application.
- Modify the qualifier for the bean to the one for this application.
- Autowire an instance of the CRUD repo.
- Modify the process method so that it tests for errors. If no errors, then
save the data using the CRUD repo.
- Run the application. Enter data and submit the process button.
- Go to the URL for the h2 console, h2-console.
- Connect to the datasource.
- Click on the name of the bean and run the query. You should see the record
you just entered.
- No further editing can be done on the record since the conversational storage
was cleared. Both the Start Over and Edit buttons on the process page perform
the same action. The next application will introduce distinct actions for these
buttons.
Data Persistence - Basic
- Copy the bean, but not the interface, from the required validation
example to the folder
src/main/java/data/ch6/persistent/ .
Rename the implementation
to RequestDataPersistentImpl .
- Create an interface that extends the required validation interface and has
a getter for the id property.
- The implementation should implement the interface and
Serializable
- Add a property to the implementation for the id. Annotate the getter for the
id with the
Id and GeneratedValue annotations.
- Add a declaration for a prototype bean to the configuration file, SimpleBean.java
@Bean("protoPersistentBean")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
RequestDataPersistentImpl getProtoPersistentBean() {
return new RequestDataPersistentImpl();
}
- Create an interface for a CRUD repository for the bean and its id.
- Add a default method to the interface named
saveWrappedData
that inspects the source sent to the method and extracts the target object,
whether the bean is Spring scoped or in the session attributes.
- Create a new folder for the controller, in the location for controllers,
ch6/persistent/basic . This application introduces access to the
database, but does not implement all features, so it is named basic.
- Copy the required validation controller to this folder.
- Modify the URL pattern. Do not include 'collect' in the pattern. This is the
first application that will have paths other than 'collect'.
- Preface all paths for the handler methods with
collect/ . The next iteration
of the application will have additional paths available.
- Replace all occurrences of the bean interface with the one for this application.
- Modify the qualifier for the bean to the one for this application.
- Autowire an instance of the CRUD repo.
- Modify the process method so that it tests for errors. If no errors, then
save the data using the CRUD repo.
- Run the application. Enter data and submit the process button.
- Go to the URL for the h2 console, h2-console.
- Connect to the datasource.
- Click on the name of the bean and run the query. You should see the record
you just entered.
- No further editing can be done on the record since the conversational storage
was cleared. Both the Start Over and Edit buttons on the process page perform
the same action. The next application will introduce distinct actions for these
buttons.
Data Persistence - Complete
The last example correctly added records to the database, but did not allow
the user to see them or edit them. This example will complete the application
with these features.
- In the folder that contains the CRUD repository, create a new interface that
has two, generic parameters: one for the bean type and one for
the id type. Extend the interface from the
CrudRepository class.
-
Add a default method that is similar to the
saveWrappedData method from the
last example, but uses generic types.
- Create another interface that extends the generic one, supplying arguments.
- Copy the JSPs to a new location.
- Add a view that displays all the records from the database. The records
will be available in the model.
- Include the taglib statement for the JSTL core library.
- Use the
forEach tag to loop through the records
and display each field.
- Add a link for each displayed ID. The path for the URL will
start with
view . These are new views that are
distinct from the collect views.
- Add a view that displays one record. The record will be in the model.
Add buttons to enter a new record and to view all records.
- Modify the process view so that it has a button that returns to the
edit view and one that links to the new view for viewing all records.
- Copy the required validation controller to the
ch6/persistent folder.
- Modify the URL pattern.
- Update the return value from the
viewLocation method.
- Replace the actual class for the previous repo with the new repo.
Include a qualifier annotation to identify the repo.
-
Add a handler method for viewing all the records that is mapped
to the URL
view . Do not include collect in the URL.
- Include a parameter
for the model.
- In the method, retrieve all the records and add them
to the model.
-
Return the name of the view that displays all the records.
-
Change the return value from the default handler so it redirects
to the edit view, instead of forwarding to the edit view.
-
Add a handler to view one record.
- The record id will be in the
path information, so add a parameter that has the
PathVariable attribute.
-
Retrieve the id from the repo calling the
findById method.
- If the data is present, forward to the page to view one
record. Pass the record as an attribute.
- If the data is not present, show an appropriate page
with an error message.
Testing
The only addition needed to test the database is to use the same repo
as the controller.
-
Start with the test file for the enhanced controller from Chapter 5.
-
Add the same repo to the test file as was used in the controller.
-
Modify the process method so it tests the database before and after the
request. A simple test is that the number of records has increased.
Be creative in adding other tests, but remember that after the request
is done, the values are no longer in the session.
|