Building a Basic Web Application | Part-03

This post will enable experts as well as beginners with Spring Boot web application development to understand the concepts behind a web application. It will explain these concepts by walking the reader through the process of developing a web application that enables the submitting of comments to a scrum retrospective meeting. This web application will use an embedded database for persistence, Spring Data JPA for a model, Spring Thymeleaf for a view, and Spring Web MVC for controllers.

The following topics will be covered in this example:

  • Using Spring Data JPA for persistence
  • Using Thymeleaf for view
  • Using Spring Web MVC with servlet 3.x for controller
  • Using Spring Security for authentication and authorization
  • Demonstrating Retro Board

Workflow of Spring Web MVC

Spring Web MVC is a web framework that is built using a central front controller in the form of a Java servlet known as DispatcherServlet. This servlet is responsible for the orchestration of the underlying components required in order to process a request it receives from clients (in most cases from a browser).

Spring Web MVC makes use of the following programming components of its own to make the web framework flexible and able to support different workflows:

  • HandlerMapping: This is used to map a request to a handler with a set of configurable pre-request and post-request interceptors. For example, a controller class with a @RequestMapping annotation will be mapped to a corresponding HTTP request by the RequestMappingHandlerMapping implementation of HandlerMapping.
  • Controller: This is used to implement handlers to a particular HTTP request, which will be responsible for coordinating the business logic and response generation. For example, an HTTP GET request to the URL /index can be mapped to the @GetMapping("/index") handler method in a controller class.
  • Model: This is used to send dynamic data in the form of attributes to View. Flash messages, lists of domain-specific objects, and so on can be sent using a Model.
  • ViewResolver: This is used to resolve view names usually returned by a controller handler method and get the actual View in order to return an HTTP response.
  • View: This is used to generate the final presentation to the end user. These views will contain the syntax that will use Model attributes to render responses dynamically. View technologies supported by Spring Web MVC include plain Java Server Pages (JSP) with JavaServer Pages Standard Tag Library (JSTL), Spring Thymeleaf, Apache Freemarker, and so on.

As an example, an HTTP GET request to path /index from a web browser will result in the following orchestration when the request reaches DispatcherServlet:

Spring Mvc

Let’s understand the preceding diagram:

  • The browser will initiate the HTTP GET request for the path /index using the HTTP protocol
  • The HTTP GET request in the form of the HTTP protocol will be resolved and handed over to DispatcherServlet by the Web container (this is a required but transparent step)
  • DispatcherServlet will use the available HandlerMapping implementations to find a Controller that contains a handler method matching the path /index and HTTP request method GET
  • DispatcherServlet will invoke the handler method from the Controller and return the View name and Model data if there are any
  • DispatcherServlet will use any configured View Resolver to find the view by name and retrieve it
  • DispatcherServlet will use a model, if any are available, along with the resolved View to prepare the final view that will be rendered
  • DispatcherServlet will hand over the rendered View to the web container, which will convert it to an HTTP response and send it to the browser

This complex orchestration will take place for each HTTP request in a Spring Web MVC Framework-based application.

Requirements for our web application

We will be creating a dashboard, which will allow team members to share comments during retrospective meetings. During software development, a team usually carries out scrum retrospective meetings to share their comments on a sprint (a software development period usually a week long). These comments can be positive (plus), improvement (delta), and appreciation (flower).

These comments will be used as feedback to make the team work more efficiently during the following sprints. Usually, this is done using a whiteboard with a table drawn that has columns for pluses, deltas, flowers, and multicolor sticky notes, where each comment from a team member will be placed in a sticky note and posted on the whiteboard under the corresponding column.

Finally, all the comments will be noted and action will be taken in coming sprints accordingly. But this is a tedious process, one that can use technology to simplify the task, making it easier for each member to share their comments on a Sprint easily.

 

To address this requirement, a web application can be developed that will allow multiple users to log in to the web application and post their respective comments and collaborate in real time.

The use case diagram

The following use case diagram shows the requirement for the dashboard, which is nicknamed Retro Board:

Use case

The actor is a User of the Retro Board and is a team member who is involved in a sprint. It has the following use cases:

  1. Login: This is required to authenticate users so that each comment can be uniquely identified
  2. Post Comments: This is where a logged in user can post their comment under its respective type so that it will be recorded for later, and also collaborate with other users in real time
  3. View Comments: This is where a logged in user can view comments made by all users

All actions need authentication and authorization to distinguish users and relate the comments they make. All comments need to be saved based on the date when they were made and should be retrievable based on the date also.

Understanding the Java Persistence API (JPA)

  • A query language to enable querying from relational database tables in order to retrieve Java objects
  • A JPA Criteria API, which can be used to generate queries dynamically
  • A set of metadata defined with XML Java annotations in order to successfully map relational database table columns to Java object fields

Understanding Spring Data JPA

The Spring Data JPA project is an abstraction over JPA which vastly simplifies the process of object/relation mapping, querying, and so on. The following are some of the features of Spring Data JPA:

  • Reduces/eliminates unnecessary boilerplate code
  • Ease of building repositories with Spring and JPA
  • Support for type-and value-safe JPA queries
  • Support for database-independent auditing
  • Support for database-independent pagination, custom query execution, and so on

Using Spring Data JPA for persistence

In this section, readers will learn what JPA is, as well as how Spring Data JPA helps simplify the development of applications with database persistence.

Understanding the Java Persistence API (JPA)

JPA provides object/relation mapping capabilities to enable mapping between relational database tables and Java objects in order to ease persistence in Java applications. JPA consists of the following features:

  • A query language to enable querying from relational database tables in order to retrieve Java objects
  • A JPA Criteria API, which can be used to generate queries dynamically
  • A set of metadata defined with XML Java annotations in order to successfully map relational database table columns to Java object fields

JPA is not an actual implementation of the preceding features but merely defines the specification. Third-party vendors can perform their own implementation that conforms to the specification. The most popular third-party implementations that support JPA are Hibernate and EclipseLink.

JPA 1.0 was released in 2006 as part of Java Community Process JSR 220. Its latest version is JPA 2.2, which was released in 2017. JPA has made persistence easier and standardized for developers while allowing transactions and data consistency. This has helped a lot, making JPA famous among developers.

Understanding Spring Data JPA

The Spring Data JPA project is an abstraction over JPA which vastly simplifies the process of object/relation mapping, querying, and so on. The following are some of the features of Spring Data JPA:

  • Reduces/eliminates unnecessary boilerplate code
  • Ease of building repositories with Spring and JPA
  • Support for type-and value-safe JPA queries
  • Support for database-independent auditing
  • Support for database-independent pagination, custom query execution, and so on

Spring Data JPA eases CreateRetrieveUpdateDelete (CRUD) operations by allowing the JpaRespository interface, which extends from CrudRepository. This hides the complexities of plain JPA implementations, which need to be implemented and tested by developers. Using Spring Data JPA could reduce the development time dramatically because of this.

Using Spring Data JPA for persistence

In this section, readers will learn what JPA is, as well as how Spring Data JPA helps simplify the development of applications with database persistence.

Understanding the Java Persistence API (JPA)

JPA provides object/relation mapping capabilities to enable mapping between relational database tables and Java objects in order to ease persistence in Java applications. JPA consists of the following features:

  • A query language to enable querying from relational database tables in order to retrieve Java objects
  • A JPA Criteria API, which can be used to generate queries dynamically
  • A set of metadata defined with XML Java annotations in order to successfully map relational database table columns to Java object fields

JPA is not an actual implementation of the preceding features but merely defines the specification. Third-party vendors can perform their own implementation that conforms to the specification. The most popular third-party implementations that support JPA are Hibernate and EclipseLink.

JPA 1.0 was released in 2006 as part of Java Community Process JSR 220. Its latest version is JPA 2.2, which was released in 2017. JPA has made persistence easier and standardized for developers while allowing transactions and data consistency. This has helped a lot, making JPA famous among developers.

Understanding Spring Data JPA

The Spring Data JPA project is an abstraction over JPA which vastly simplifies the process of object/relation mapping, querying, and so on. The following are some of the features of Spring Data JPA:

  • Reduces/eliminates unnecessary boilerplate code
  • Ease of building repositories with Spring and JPA
  • Support for type-and value-safe JPA queries
  • Support for database-independent auditing
  • Support for database-independent pagination, custom query execution, and so on

Spring Data JPA eases CreateRetrieveUpdateDelete (CRUD) operations by allowing the JpaRespository interface, which extends from CrudRepository. This hides the complexities of plain JPA implementations, which need to be implemented and tested by developers. Using Spring Data JPA could reduce the development time dramatically because of this.

In upcoming chapters, JpaRepository with default methods and custom methods will be used extensively to implement business logic and demonstrate how to write Spring Data JPA repositories and test them. The following sections will discuss how to use a domain model designed using a class diagram as a base to implement Spring Data JPA-based entities and repositories.

Class diagram for the domain model

The domain model is the most important part of an application; some applications have run for years on end with multiple frontend technologies but without ever changing the existing domain model. A well-built domain model can easily support multiple business logic and can run an application on limited resources efficiently.

The following is the simple class diagram for this web application:

class diagram

There are two main domain models and one enumeration, as shown in the preceding diagram. Those are as follows:

  • Comment: This is the main domain model, which will store the actual comment, comment type, comment created date, comment create a user, and so on
  • User: This is the domain model, which will store the username, password, and role of a registered user
  • CommentType: This enumeration is to differentiate comments by type

Setting up dependencies and configuration

Initially, before implementing the domain model, the dependencies and configuration class need to be specified. The following Maven starter dependency and H2 database dependency need to be included:

<dependencies>
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.196</version>
    </dependency>
</dependencies>

The following RepoConfig is used, which enables JPA Auditing (here auditing means tracking and logging events related to entities, such as createdBy for the Comment entity); this will enable auditing of the created date and created a user of an entry in the table:

@Configuration
@EnableJpaAuditing
public class RepoConfig {

}

The following configuration properties in the application.properties file need to be set to configure DataSource:

spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:~/retroboard
spring.datasource.username=sa
spring.datasource.password=

he preceding configuration does the following:

  • Uses spring.jpa.hibernate.ddl-auto to set automatically generate Data Definition Language (DDL) SQL
  • Uses spring.jpa.properties.hibernate.format_sql to format SQL generated in a visually pleasing manner
  • Uses spring.jpa.properties.hibernate.show_sql to show the SQL generated
  • Uses spring.datasource.driver-class-name to set org.h2.Driver as the database driver
  • Uses spring.datasource.url to set the JDBC URL
  • Uses spring.datasource.username to set the username for the H2 database
  • Uses spring.datasource.password to set the password for the H2 database

Implementing the domain model

In order to successfully populate the createdUser property, the AuditAware interface needs to be implemented to supply the username and needs to be registered as a Spring Component. More on this in the Using Spring Security for authentication and authorization section.

Implementing the domain model Comment class using JPA annotations will look like the following:

@Entity
@Table(name = "rb_comment")
@EntityListeners(AuditingEntityListener.class)
@Data
public class Comment {

@Id
    @GeneratedValue
private Long id;

    private String comment;

@Enumerated(EnumType.STRING)
private CommentType type;

@CreatedDate
private Timestamp createdDate;

@CreatedBy
private String createdBy;

}

In the preceding code, the @Entity annotation is used to mark the Comment class as a JPA entity so that it will be eligible to be used in JPA persistence environment. The @Table annotation is used to mention the table name to which the Comment class needs to be mapped. The @EntityListeners annotation is used with the AuditingEntityListener implementation to dynamically populate the createdDate and createdBy properties annotated with @CreatedDate and @CreatedBy in the Comment domain model when persisting the comment entry into the table. The @Data annotation is from the Lombok library and used to mark a POJO as a class that will hold data. This means getterssetters, the equals method, the hashCode method, and the toString method will be generated for that class.

The @Id annotation marks the ID property as the identity field of the entity, whereas @GeneratedValue marks it as an auto-generated value. The @Enumerated annotation with value EnumType.STRING on the type property is used to notify JPA that the value of the enum CommentType needs to be persisted as a String type in the database.

Implementing the domain model User will look like the following:

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

@Id
@GeneratedValue
private Long id;

    private String username;

    private String password;

    private String role;

}

Implementation of Spring Data JPA repositories

With the domain model implemented successfully, the JpaRepository for those can be implemented using Spring Data JPA. The specialty here is that there is no need to implement anything. Just writing an interface that extends from the JpaRepository interface will be sufficient to expose methods to find one, find all, save, delete, and so on. The following code shows CommentRepository:

public interface CommentRepository extends JpaRepository<Comment, Long> {

@Query("SELECT c FROM Comment c WHERE year(c.createdDate) = ?1 AND
     month(c.createdDate) = ?2 AND 
    day(c.createdDate) = ?3")
    List<Comment> findByCreatedYearAndMonthAndDay(int year, int month,
     int day);

}

Since a list of comments for a specific date needs to retrieved to be shown in the frontend, a custom method with a @Query annotation is added to the CommentRepository interface. This annotation is responsible for using a database-independent SQL query to filter out data from the database.

The following code shows UserRepository:

public interface UserRepository extends JpaRepository<User, Long> {

    User findByUsername(String username);
}

Testing Spring Data JPA repositories

Testing is an important part of software engineering and with Spring Boot 2.0 @DataJpaTest is introduced to ease the testing of JPA repositories. This annotation will use an embedded database for testing and will auto-configure TestEntityManager to verify the JPA Repository operations.

The following is the test for CommentRepository:

@RunWith(SpringRunner.class)
@DataJpaTest
public class CommentRepoTest {

@Autowired
private TestEntityManager testEntityManager;

@Autowired
private CommentRepository commentRepository;

@Test
public void
findByCreatedYearAndMonthAndDay_HappyPath_ShouldReturn1Comment() {
// Given
Comment comment = new Comment();
comment.setComment("Test");
comment.setType(CommentType.PLUS);
comment.setCreatedDate(new Timestamp(System.currentTimeMillis()));
testEntityManager.persist(comment);
testEntityManager.flush();

// When
LocalDate now = LocalDate.now();
List<Comment> comments = 
         commentRepository.findByCreatedYearAndMonthAndDay(now.getYear(), 
         now.getMonth().getValue(), now.getDayOfMonth());

// Then
assertThat(comments).hasSize(1);
assertThat(comments.get(0)).hasFieldOrPropertyWithValue("comment",
"Test");
}

@Test
public void save_HappyPath_ShouldSave1Comment() {
// Given
Comment comment = new Comment();
comment.setComment("Test");
comment.setType(CommentType.PLUS);
comment.setCreatedDate(new Timestamp(System.currentTimeMillis()));

// When
Comment saved = commentRepository.save(comment);

// Then
assertThat(testEntityManager.find(Comment.class,
saved.getId())).isEqualTo(saved);
}
}

The preceding test case uses a TestEntityManager private member auto-wired (auto-wiring private members should be kept to the minimum as it is not a best practice) to the JUnit test and persists Comment by flushing it to the temporary persistence using the TestEntityManager.flush method and then, in one test, tests whether it can be successfully retrieved using the CommentRepository.findByCreatedYearAndMonthAndDay method. Furthermore, in the next test, it tests whether it could successfully save a Comment.

The following is the test for UserRepository:

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepoTest {

@Autowired
private TestEntityManager testEntityManager;

@Autowired
private UserRepository userRepository;

@Test
public void findByUsername_HappyPath_ShouldReturn1User() throws 
    Exception {
// Given
User user = new User();
user.setUsername("shazin");
user.setPassword("shaz980");
user.setRole("USER");
testEntityManager.persist(user);
testEntityManager.flush();

// When
User actual = userRepository.findByUsername("shazin");

// Then
assertThat(actual).isEqualTo(user);
}

@Test
public void save_HappyPath_ShouldSave1User() throws Exception {
// Given
User user = new User();
user.setUsername("shazin");
user.setPassword("shaz980");
user.setRole("USER");

// When
User actual = userRepository.save(user);

// Then
assertThat(actual).isNotNull();
assertThat(actual.getId()).isNotNull();
}
}

Using Spring Boot Devtools for database visualization

In order to ease the testing of database development via Spring Boot Devtools, a dependency can be used that will provide a GUI to visualize the tables created with the data when an embedded database such as H2 is used. This can be seen from the following code:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

With this Spring Boot Devtools enabled after the Spring Boot application’s startup, accessing the http://<host>:<port>/h2-console (the port will be the same port as the Spring Boot application) URL will display the following H2 database console for ease of development:

H2 console

After clicking the Connect button with the correct parameters, the following screen with database tables and users will be displayed:

H2 console browser

Using Services to encapsulate business logic

It is a good practice to encapsulate business logic inside Service methods so that controllers and repositories are loosely coupled. The following Service is written to encapsulate business logic for Comment:

@Service
@Transactional(readOnly = true)
public class CommentService {

private static final Logger LOGGER = LoggerFactory.getLogger(CommentService.class);

 private final CommentRepository commentRepository;

 public CommentService(CommentRepository commentRepository) {
this.commentRepository = commentRepository;
}

@Transactional(rollbackFor = Exception.class)
public List<Comment> saveAll(List<Comment> comments) {
LOGGER.info("Saving {}", comments);
 return commentRepository.saveAll(comments);
}

public List<Comment> getAllCommentsForToday() {
 LocalDate localDate = LocalDate.now();
 return commentRepository.findByCreatedYearAndMonthAndDay(localDate.getYear(), 
 localDate.getMonth().getValue(), localDate.getDayOfMonth());
}
}

The CommentService method in the preceding code is annotated with the @Service stereotype annotation to mark it as a Spring service. Also, it has the @Transactional annotation (learn more about Spring Transaction in the reference documentation). The CommentRepository will be auto-wired using the CommentService constructor argument. Another notable thing is that the CommentService.saveAll method is annotated with the @Transactional annotation with rollbackFor set to the Exception class. This means that any code inside that method will be enclosed inside a transaction and, if an exception is thrown, JpaTransactionManager will roll back the changes it made in the database within that transaction.

Likewise, the following UserService is used for User:

@Service
@Transactional(readOnly = true)
public class UserService implements UserDetailsService {

private final UserRepository userRepository;

 public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String username) throws 
 UsernameNotFoundException {
 User user = userRepository.findByUsername(username);

 if(user == null) {
throw new UsernameNotFoundException(username);
}

return new 
 org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), 
 Arrays.asList(new SimpleGrantedAuthority(user.getRole())));
}

@Transactional(rollbackFor = Exception.class)
public User create(User user) {
return userRepository.save(user);
}
}

Testing Services

Services with business logic need to be tested for their correct functionality. In order to do this, Services like the following one can be used:

@RunWith(SpringRunner.class)
public class CommentServiceTest {

@MockBean
private CommentRepository commentRepository;

    private CommentService commentService;

@Before
public void init() {
commentService = new CommentService(commentRepository);
}

@Test
public void 
       getAllCommentsForToday_HappyPath_ShouldReturn1Comment() {
// Given
Comment comment = new Comment();
comment.setComment("Test");
comment.setType(CommentType.PLUS);
comment.setCreatedDate(new 
            Timestamp(System.currentTimeMillis()));
List<Comment> comments = Arrays.asList(comment);
LocalDate now = LocalDate.now();


        when(commentRepository.findByCreatedYearAndMonthAndDay(now.getYear(), now.getMonth().getValue(), 
         now.getDayOfMonth())).thenReturn(comments);

// When
List<Comment> actualComments = 
         commentService.getAllCommentsForToday();

// Then
verify(commentRepository, 
          times(1)).findByCreatedYearAndMonthAndDay(now.getYear(), 
          now.getMonth().getValue(), now.getDayOfMonth());
assertThat(comments).isEqualTo(actualComments);
}

@Test
public void saveAll_HappyPath_ShouldSave2Comments() {
// Given
Comment comment = new Comment();
comment.setComment("Test Plus");
comment.setType(CommentType.PLUS);
comment.setCreatedBy("Shazin");
comment.setCreatedDate(new 
          Timestamp(System.currentTimeMillis()));

Comment comment2 = new Comment();
comment2.setComment("Test Star");
comment2.setType(CommentType.STAR);
comment2.setCreatedBy("Shahim");
comment2.setCreatedDate(new  
          Timestamp(System.currentTimeMillis()));
List<Comment> comments = Arrays.asList(comment, comment2);
when(commentRepository.saveAll(comments)).thenReturn(comments);

// When
List<Comment> saved = commentService.saveAll(comments);

// Then
assertThat(saved).isNotEmpty();
verify(commentRepository, times(1)).saveAll(comments);

}
}

In the preceding test case for CommentServiceCommentRepository is annotated with @MockBean, as testing of its functionality has already been done in the JPA repository testing. During Service, test mocking is done using the Mockito library to just mock repository method invocations and verify the correct invocation.

The following is the service test case for UserService:

@RunWith(SpringRunner.class)
public class UserServiceTest {

@MockBean
private UserRepository userRepository;

    private UserService userService;

@Before
public void init() {
this.userService = new UserService(userRepository);
}

@Test
public void getAllCommentsForToday_HappyPath_ShouldReturn1Comment() {
// Given
User user = new User();
user.setUsername("shazin");
user.setPassword("sha908");
user.setRole("USER");

when(userRepository.findByUsername("shazin")).thenReturn(user);

// When
UserDetails actual = userService.loadUserByUsername("shazin");

// Then
verify(userRepository, times(1)).findByUsername("shazin");
}

}

In the preceding test case, the UserRepository.findByUsername method is mocked to return a given user and finally verify whether that method is invoked exactly once.

Leave a Reply

X