We are building a Phonebook Application using Spring MVC.
We use a static data (List) in the Service Impl, instead of the Database
We will use a Database to manipulate the data, and we use JDBC to interact.
We will expose the services as a Rest API, and add Java Faker Library to ingest the test / fake data.
- Java 8
- Spring MVC
- Spring Core
- JUnit 5
- Bootstrap
- JSTL
- Lombok
- Log4J
- Maven
- Tomcat
- Maven Tomcat Plugin (tomcat7)
- JDBC
- Spring JDBC
- ORM / Hibernate (if time permits)
- Spring Rest
- Java Faker Library
-
Contacts
- Create
- List
- Read / Get
- Update
- Delete
-
Other Features
For any feature/module/service, we follow the steps.
* JUnit Test - preferred for *TDD* (Test Driven Development)
* Write a test method
* Make it fail
* Write the actual implementation
* Make the test cases pass
* Service Interface
* Service Implementation
* Controller
* UI Pages
* Unit Testing on the UI
* Manual
* Hitting the URL on the browser
* Validate the output in the browser
* See/Verify the Application logs in the Console if any
* _Automation_ - using *Selenium* (we will see later if time permits)
* End to end testing by following
* Start from the Login
* Test every other scenario / feature by hitting the respsective URLs
* Verify the output in the UI/Browser for the corresponding scenario
* Verify the Application logs in the Console, if any , on demand
- JUnit
- Service
- Controller
- UI
-
[DONE] →
redirect:/contacts
where we don't duplicate the code but invoke the existing method with the url pattern '/contacts' to list all the contacts. -
Validation
- [DONE] No null/empty values - kind of taken care in the UI with the
required
attribute in theinput
tag/element - Server Side Validation - at the Controller level
- [DONE] Missing or empty values - through
@Valid
annotation - [DONE] Duplicate Values
- [DONE] Missing or empty values - through
- [DONE] No null/empty values - kind of taken care in the UI with the
-
[DONE] Reduce the repeating
@RequestParam
for each of the parameters passed via the Request -
addContacts.jsp Page
- Add a red color star to indicate that a particular field is mandatory (whichever is really required)
- Add the necessary back end validation for all the applicable fields - except the firstName and lastName which we have done it so far.
- [WIP] Alignment of the columns - fields and values for each input
- JUnit
- Service
- Controller
- UI
- JUnit
- Service
- Controller
- UI
- Issue : The update was not properly happening
- Solution :
- Domain - manualy provide the
hashCode()
andequals()
method, than the one given by Lombok. - Reduced the width of the
@Size
attribute for the lastName field - from 4 to 2.
- Domain - manualy provide the
- Issue : The contact being attempted with the existing contact #. Because it is a hard coded list we manage in Phase 1 and we execute in two steps. First, we remove the item first and then adding the list later to the list. Here the removal fo an item is successful but the addition gets failed due to the validaiton for Duplicate based on the Contact #. Hence, the operation is partially completed! :( It is NOT the desired way. Either we should do it ALL or NONE - honoring the ACID style (Atomicity, Consistency, Isolation and Durability).
- Solution : Use Transaction Management - either manually using the JTA (Java Transaction API) (old style), OR let the framework like Spring manage it for us, as that was one of the primary goals of such frameworks.
- Redirecting to 'contacts.jsp' page after addContact.
- Add RedirectAttributes to add a flash message, than using the Model or the ModelMap.
- JUnit
- Service
- Controller
- UI
-
UI
- [DONE] Reusable fragments - header, footer and Menu. #Assignment | Menus - Home, Contacts
- [DONE] Add a Bootstrap button to the View
- [DONE] Add the alignment for all the colums in the
addContact.jsp
page and do the necessary validations - [DONE] Checkout the person specific branch created in the GitHub Repo
https://github.com/itsraghz/spring-mvc-demo/tree/Assignment1-UI-21Mar2023-Rama
and push your code in to the corresponding branch (Remote Feaure Branch). - [PENDNIG] The one scores high, will get merged with the PR (Pull Request) into the remote master/main branch.
-
Pending /TBD Later
- Add a favico -
<link rel="icon" type="image/x-icon" href="/images/favicon.ico">
- Add a favico -
-
Transaction - UoW (Unit of Work)
- All steps involved in the business acitivty should be completed and
commit
ted, in case of any failures in the middle, the entire operation should berolled back
.
- All steps involved in the business acitivty should be completed and
- Plain old Java with JDBC
- List of steps involved
- Most of the steps like Obaining the Connection, Creating the statement, Dealing with the SQLException, Closing the resources (ResultSet, Statement(OR PreparedStmt), Connection) - are called as Ceremonies OR also the boiler plate code
- The Reason being no matter what business logic you have, you must have all these without fail.
- And it does not add any direct value to your busines.
- Savior / Rescue - Spring JDBC
Note: Motto of the Spring Framework : - To Simplify Application Development
- It covers / wraps the boiler plate code under the hood, so that the Devleoper can just focus on the actual business logic OR the lines that really matters to him /his business.
We use Spring Framework V 4.3.30, and hence we refer this (link)[https://docs.spring.io/spring-framework/docs/4.3.30.RELEASE/spring-framework-reference/html/jdbc.html#jdbc-introduction] for Spring JDBC.
-
Find out the compatible version of Spring JDBC from the URL https://mvnrepository.com/artifact/org.springframework/spring-jdbc to be added in the
pom.xml
file of our project. For our case, we choose '4.3.30.RELEASE' version that is matching with our Spring Core (spring-context). -
We should declare a
DataSource
ofjava.sql
, as a@Bean
in the@Configuration
class -
We can have an Interface for the CRUD operations based on the Domain object.
-
This Datasource bean should be injected to the
Service
layers. -
JDBCTemplate method -
execute
orqueryXXx
-
Implementation of the
RowMapper
interface to map the data on a per-row basis, by implementing the `mapRow() method declared in the interface. -
Before executing the Test class, ensure that we have the actual Database and Table available in the Database. :)
-
Add the DB specific JDBC connector in the
pom.xml
. Ex, MySQL JDBC Connector for the MySQL Database<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency>
View/Client <--> Controller <--> Service <--> Repository <--> [JDBC <-->] Database (Actual Database)
Note: Service layer should have Datasource injected, which is declared as a
@Bean
in a@Configuration
class.
Client <--> [Service] <--> [Repository] <--> [JDBC <-->] Database (Actual Database)
- Any or both of the layers can be kept optional. But at least you need layer in the Backend.
Note: If you keep both of them optiona, then you must have one layer as a Controller (a central layer to get all requests from the Client and regulate the flow).
Typically in a Test class, we may NOT have the Controller available. In that case, we can skip that, and direclty invoke the Service/Repository (depends on what you configured in the Application) and invoke the flow.
Note: Whenever we say Service/ Repository for injecting the dependencies, we really speak about the Implementation class at the respective layer, and certainly NOT the Interface.
-
Test Class
-
Service
-
Repository
-
AppConfig
- jdbcTemplate
- DataSource
-
DI (Dependency Injection)
- TestContactDAO - we injected ContactService (Reference Type/Interface)
- ContactServiceImpl - we injected DAO (Reference Type / Interface)
- ContactDAO (Implementation Class) - we injected JdbcTemplate (which indeed has got the DataSource Reference
-
Accomplishments
- We are able to create a new entry in Contact Table successfully
-
Pending / Todo
- The validations - Boundary checks and also the Business Validations
- Boundary Validations - min/max size, not null etc.,
- Business Exceptions - Duplicate Contact
- We do have a method
isContactDuplicate()
in theContactServiceImpl
class, but it operates on the static hardcoded list of data -contactList
- Solution
- #1. We can modify this list from hardcoded to Database. [getAll() method can return the List]
- Challenge: What if the Database has got more than a Lakh records?
- #2. We can query from the Database Table with a WHERE clause to find a potential match for duplicate. If so we stop it
-
SELECT * FROM CONTACT WHERE CONTACTNO= ? -- the number supplied from the User. ```sql
-
- #3. The best bet is to add a UNIQUE constraint on the Contact Table and let the Database handle the scenario and throw a SQLException whenver an attempt is made to store a contact whose contact no is already present in the table.
- #1. We can modify this list from hardcoded to Database. [getAll() method can return the List]
- We do have a method
** Accomplishments
- Added a new contract getByContactNo(String contactNo)
in the DAO<T>
.
- Added the implementation for the contract method in the ContactDAO
class.
- Tested the same via a @Test
method in TestContactDAO
class.
- Modified the link in ContactServiceImpl
class in the isContactDuplicate()
method
- Commented the code for the iteration of the hard coded list
- Added the new logic to invoke the contactDAO.getByContactNo
and determine based on optionalContact.isPresent()
.
- [DONE] Error while checking the
getContactByContacNo(String contactNo)
for the duplicate validation check as thejdbcTemplate.query
or thequeryForObject()
method indeed calling therequireSingleResult()
which indeed throws anEmptyResultDataAccessException
if the resultset is empty (meaning when there are no matching rows for the contactNo being passed). - [DONE] The ID parameter was NOT populated in the
RowMapper
Implementation class - on thegetAll()
method for the URL/contacts
. - [DONE] The autogenerated ID was not assigned/displayed in the success message. We instead retrieved the
rowsAffected
- the direct return value of thejdbcTemplate.update(sql)
method.rowsAffected
will indicate the number of rows affected by executing the DML statement passed to theupdate
method ofjdbcTemplate
object. For an INSERT, it is NOT the same as the auto generated the sequence number or the Primary Key.
- getById() completed linked with Controller and Test Method.
- Pending Use cases using Spring JDBC
- Update
- Delete
- check into the Github Repo as always. Will create a new branch for this
Assignment2-SpringJDBC-26Mar23-<Name>
- Raghavan should
- [DONE] rectify the file names of Snagit Images which was preventing the git pull due to the very long files names and the team uses Windows.
- [DONE] create a new branch for each for this new Assignment - Assignment 2.
-
@Order does not work as expected in TestContactDAO. Not sure whether Spring Context being loaded in the class has some influence. Need to check.
-
[DONE] Properties for DataSource to be injected from the properties (
jdbc.properties
) file- Flavor #1 - Config class with
@Bean
method with@PropertySource
and@Autowired
Environment
class, and useenv.getProperty(key)
. - Flavor #2 - define a
<bean>
tag in theregistratiion-servlet.xml
along with the<context:property-placeholder location='classpath:/jdbc.properties'/>
.
pom.xml
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.30.RELEASE</version> </dependency>
registration-servlet.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="classpath:/jdbc.properties"/>
Note: If theer is a bean declared in the XML file that will take the preference than the one declared in the Java class via
@Bean
annotation. - Flavor #1 - Config class with
-
Assignment #3 - Login using Spring JDBC and Spring MVC to have the login credentials stored and verified from the Database table than the one hardcoded.
- I will create a separate github repo name for this assignment. Name: `Assignment3-SpringJDBC-Login-27Mar2023-Karthik
-
Spring MVC Rest
- Dependency on Jackson API
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.3</version> </dependency>
-
Tools
- Browser - direct URL in the address bar
- Command Line tools - HTTPie, cURL
- Dedicated Rest Clients
- Installable Apps - Postman, Insomnia REST
- Browser Addons - RESTClient for Firefox
-
JavaFaker
to have the random data added.- Search Capability to make it more meaningful
-
Spring Security
-
I18N (Internationlization) capabilities
- Resource - most important component, anything and everything we speak here is in terms of a Resource
- Example: An Image, an URL, an entity, a document etc., - anything that can be referred in the URL - is a URI
- URI - Uniform Resource Indicator.
- URL - Uniform Resource Locator.
- URL Vs URI -> Locator tells you where the entity being looked for is present.
- http://localhost:8080/spring-mvc-demo/images/logo.png - specify that the file 'logo.png' is present inside the 'images' directory
- http://localhost:8080/spring-mvc/demo/api/contacts, with HTTP method GET. It just specifies that the user is interested in getting all the contacts, not really bothered about the location where they are stored in the System.
@Controller |
@RestController |
---|---|
Used for Web (B2C) | Used for non-web (B2B) |
Returns the logical view name as a String - in Spring MVC. | Returns the complete response as a @ResponseBody |
We have two different methods with the same URI pattern but a varying HTTP Method (for exampe, /add-contact with @GetMapping and @PostMapping - where the HTTP Get method is to get the UI Page with the Form to fill the inputs and the Http Post Method is to submit the inputs and get the processing done (creation of a new entity). |
We do NOT have such two way communication, instead we have only one way, that is directly calling the Post Method. Reason : Rest does NOT bother about the User Interface. |
No prefix as a convention used on the URI | We typically use a prefix as /api/ on the URI to indicate that it is of a Rest API. NOTE: It is just a convention and NOT a Rule! |
Client <--> RestController <--> Service <--> Repository <--> [JDBC <-->] Database (Actual Database)
Note: The Client here is typically a Rest Client, like cURL, HTTPie, Postman, Insomnia REST etc., Sometimes, the Browser can also act as a Rest Client (for GET requests).
- Typically in a Java method, we use nouns for the member variables and verbs for the methods indicating the actions a method can perform on an Object. Example, doGet(), sayHello(), run(), sleep(), writeToFile() etc.,
- But in REST, we only talk in terms of Resources - meaning nouns and NOT Verbs. What should be the method names ?
/api/contacts
,/api/users
.
- We do NOT say,
api/getContact
,/api/addContact
,/api/updateContact
etc.,
Note: It is more or less like a RULE to be followed. Though the Java compiler will NOT complain you against these norms, but the Java Developer Community will accuse and abuse you if you dont', as these are the general best practices widely adopted and strictly followed without any excuses!!!!
Note: The URL patterns genrally will be of lower case, until we need a camelCase in a single word.
/api/Contacts
is NOT recommended. (Capital case C, and it is a single word!!!)
Q: How do we distinguish on the action for CRUD methods we perform on the Resources? A: Ultimately, we use two things in conjunction to determine the operations being performed on the resource.
Factors :
- HTTP Method -
GET
,POST
, etc., - Actual method name / the URL pattern -
/api/contacts
.
URL | HTTP Method | Domain Object | CRUD Operation | Remarks |
---|---|---|---|---|
/api/contacts |
GET |
Contact.java |
R - Read |
Get All Contacts (list of Contacts) |
/api/contacts |
POST |
Contact.java |
C - Create |
Create a new Contact |
/api/contacts/{id} |
GET |
Contact.java |
R - Read |
Get the Contact By Id, if any |
/api/contacts |
PUT |
Contact.java |
U - Update |
Update the Contact (By Id) - we don't say it explicitly like GET |
/api/contacts |
DELETE |
Contact.java |
D - Delete |
Delete the Contact (By Id) - we don't say it explicitly like GET |
/api/events |
GET |
Event.java |
R - Read |
Get All Events (list of Events) |
/api/events |
POST |
Event.java |
C - Create |
Create a new Event |
/api/events/{id} |
GET |
Event.java |
R - Read |
Get the Event By Id, if any |
/api/events |
PUT |
Event.java |
U - Update |
Update the Event (By Id) - we don't say it explicitly like GET |
/api/events |
DELETE |
Event.java |
D - Delete |
Delete the Event (By Id) - we don't say it explicitly like GET |
Note: The URL Pattern and the HTTP Method should be unique. The Spring MVC framework will throw an exception while getting started and it is preparing its internal Map (URL Pattern with the RequestHandlerMapping), if it is otherwise.
- Default - blows up the stack trace
- Flavor #1 - Throw a
RuntimeException
- No big difference - Flavor #2 - Return a generic
Object
- Better but status code is inappropriate. - Flavor #3 - Use a
ResponseEntity<T>
which is a wrapper object that carries- Actual Response Data
- Custom Response Headers
- Http Status Code
- We need to validate the Domain objects in the
@PostMapping
,@PutMapping
methods by using the@Valid
annotation fromjavax.validation
package for all the parameters bound to the Domain object from User Input. - Two different validations
- Domain Validation
- Boundary Check Validation
- We need to ensure we are intact with the following
- Http Status Code
- Error Message, without the Server blowing up its internals.
- Strategies
- Initial - Use
String
as a return type to show a mesage for either case (Success/ Exception). HttpStatus is always 200 (OK)! - Use
ResponseEntity
- Boundary Check was missing and the validaion was carried out before even preparing the domain objectContact
and pass it to the method argument.- We caught two different exceptions for the line invoking the Service Layer in the Cotroller Method - (1) Usual BusinessException (2) MethodArgumentNotValidException and we prepared a
ResponseEntity
accordingly. - However, the control flow did not even enter the Controller Method because the order of execution is different wherein
@Valid
was executed first and whenever there was an exception during validation of the parameters passed in the Request JSON, theMethodArgumentNotValidException
was thrown and hence neither the Domain objectContact
was prepared with the inputs passed, NOR the control came inside the method.
- We caught two different exceptions for the line invoking the Service Layer in the Cotroller Method - (1) Usual BusinessException (2) MethodArgumentNotValidException and we prepared a
- Use
@ExceptionHandler
along with the@ControllerAdvice
or more precisely@RestControllerAdvice
to handle the excpetions (MethodArgumentNotValidException
) - on boundary checks- Domain Validation + GlobalException - helps for the
@Valid
annotation validtion (Boundary Checks) but breaking the Domain Validation (Duplicate Exception by means of aBusinessException
.
- Domain Validation + GlobalException - helps for the
- Initial - Use
-
When we manually handle the exceptions via try-catch block (though for a genuine reasons),
- The Response data is handled well
- The HTTP Staus code is NOT handled! and it is mostly a HTTP 200 (OK).
-
Spring MVC offers a way to handle this situation via
@ExceptioHandler
.- Method Level
- Add a new method in the Controller (
@RestController
) and annotate it with@ExceptionHandler
by passing the .class of the actualException
that it is intended to handle. - Receive the Exception object as an Input argument in the Method
- Use
@ResponseStatus
annotation to set the HTTP Status Code, when the return type is NOT aResponseEntity
- Use any return type - preferably
ResponseEntity<T>
as that can carry all the 3 elements -Response Data
,Custom Headers
, and theHttp Status Code
, OR any other return types on demand.
- Add a new method in the Controller (
- Class Level
- Annotate the
@RestController
class with@RestControllerAdvice
to inform Spring that this class has an@ExceptionHandler
method(s) for handling the exceptions, and hence Spring need NOT worry about handling that exception.
- Annotate the
- Method Level
- The common prefix of the URI path can be applied at the
@RestController
level than at each individual API method - The URI for the
@RestController
is applied via@RequestMapping
annotation. - Benefits
- They are simple and short
- The URI is NOT too verbose
- The changes can be easily made at a single place - at the Class level.
NOTE: This API URI Optimization is not only for the
RestController
but it is applicable for@Controller
classes as well.
- Make the HTTP Post/Put/Delete requests via
cURL
, as we have demonstrated the same using Insomnia REST Client in the class. - Go through the list of HTTP Status code and get an understanding of each category (1XX to 5XX) and read about the famous status code in each category, especially 2XX, 4XX and 5XX. Wikipedia Link - https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
An Open Source Repository/library/framework that helps us fill the fake data (seemingly real but not the dummy) for our application.
Three different kinds of data we deal with when working with an application.
-
Real - The actual data
-
Fake - Seeming real but may not exist
-
Dummy - Gibberish, like
abcd
,pqr
,xyz
,grrr
etc., - does not really add any value. -
Faker Library helps us fill the fake data based on the columns what we have in our Domain (Contact, Person, Book, Movie, etc., )
- Github - https://github.com/DiUS/java-faker
- Maven - https://mvnrepository.com/artifact/com.github.javafaker/javafaker/1.0.2
- It has a collection of data - prefilled.
- It has a Randomness - added
- It has got the locale (en_US, en_IN etc., ) based on the country, language that we speak
@Test
@DisplayName("Java Faker should return the valid values")
public void javaFakerSimpleTest()
{
Faker faker = new Faker(new Locale("en-IND"));
String firstName = faker.name().firstName();
logger.info("Faker First Name : " + firstName);
assertNotNull(firstName);
String lastName = faker.name().lastName();
logger.info("Faker Last Name : " + lastName);
assertNotNull(lastName);
}
- [DONE] Exception Handling on the Rest API Methods
- [DONE] See the
@Valid
annotation in Action for the validation rules- [DONE] Domain Validation + GlobalException - helps for the
@Valid
annotation validtion (Boundary Checks) but breking the Domain Validation.
- [DONE] Domain Validation + GlobalException - helps for the
- Put Vs Patch method in Rest API
- [WIP] Java 8 Streams - on all the business logic where applicable
- [DONE] Log4J to have a rolling file appender
- Merge the assignment branches to master via the PR (Pull Request)