I made a simple MVC sample system using Spring Boot

Spring Boot for the first time

I feel like I'm late, but I started studying Spring Boot. Most of the companies used their own frameworks and Struts2 systems, but the vulnerabilities that have been making noise these days and the trend toward microservices, and when I participated in JJUG CCC 2017 Spring in the spring, everywhere Spring Boot I decided to use Spring Boot because there are many stories about it, and there is a lot of information and books on the net.

As a feeling of using it, I realized that it was convenient because I could start immediately and easily add functions. If you read a book, look at the official documents, and look up information on the net, a simple system will be completed in no time. However, there were some addictive points ... It's okay to move the functions appropriately, but I decided to make a simple sample system and study it.

The created source is published on github, so I hope it will be helpful for those who are starting from now on. https://github.com/ewai/spring-boot-mvc-template

Remaining TODO

We plan to create and update it from time to time.

I made a sample system like this

System overview

System for managing book information (System that performs simple master management)

You need to log in to work with your data Only the administrator can register / update ← TODO authority is not working well

Screen transition diagram

image.png

Other than the top screen, it can be displayed only when authenticated.

Let's take a look at the demo for the time being

https://ewai.info/sbt/

user password Authority
sbt sbt Normal user authority (reference system only)
admin admin Administrator authority (data can be updated)

Directory structure

src
  ├─main
  │  ├─java
  │  │  └─info
  │  │      └─ewai
  │  │          └─sbmt
│ │ ├─config (settings around Security, etc.)
  │  │              ├─domain (entity,repository etc.
  │  │              ├─service  (service
  │  │              └─web (controller, validator
  │  │                  └─form (form
  │  └─resources
  │      ├─static
  │      │  ├─css
  │      │  └─img
│ └─ templates (thymeleaf templates
  └─test
      └─java
          └─info
              └─ewai
                  └─sbt (TODO Junit

It has a standard package configuration. It seems that you have to specify @ComponentScan ("xxx") if it does not follow the standard. I made it freely without knowing it at first.

Official documentation http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#using-boot-using-the-default-package

Library set in build.gradle

build.gradle


~
        springBootVersion = '1.5.6.RELEASE'
~
    compile("org.webjars:jquery:3.2.1")
    compile("org.webjars:bootstrap:3.3.7")

    compile('org.springframework.boot:spring-boot-starter-actuator')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.boot:spring-boot-starter-jetty')
    compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE')
    runtime('mysql:mysql-connector-java:5.1.43')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.springframework.security:spring-security-test')

github source build.gradle

By default, tomcat is used, but Jetty is used because the company uses Jetty.

environment

Development environment

Library

Application server

DB

DB environment

I made a Dockerfile that automatically registers DDL and test data based on the official MySQL of Docker Hub. If you do docker build from there and create an image, it will contain DDL and test data. Since MySQL starts up, you can start the system immediately.

The following is a fairly rough construction procedure.


First time

#Clone or download this
https://github.com/ewai/docker-spring-boot-template-mysql

Below, execute the command

#Image creation
docker build -t sbtdb .

#Container creation
docker run -d --name sbtdb -p 3306:3306 sbtdb

It should now be running.
docker ps -a

OK if the status is UP.
* DDL and test data have already been input.


After the second time

Check the status
docker ps -a
 
Start the container if the status is Exited
docker start sbtdb

When the status becomes UP, you can connect, so try connecting with MySQL Workbench. sbtdb

Connection information
jdbc:mysql://localhost/sbtdb

User: sbt
Password: sbt

Example) MySQL Workbench image.png

Try to move

image.png

Description around the source

Login authentication

Related sources

build.gradle


+ compile('org.springframework.boot:spring-boot-starter-security')

Enables Spring Security.

Just add this and Basic authentication will be applied automatically. I made that part because I want to authenticate login.

SecurityConfig.java


package info.ewai.sbmt.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import info.ewai.sbmt.service.UserService;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/img/**", "/css/**", "/js/**", "/webjars/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .usernameParameter("username")
                .passwordParameter("password").permitAll().and()
            .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true).permitAll();
    }

    @Configuration
    protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
        @Autowired
        UserService userService;

        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
        }
    }
}

SimpleController.java


    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login() {
        return "login";
    }

It is a controller that only transitions to the login page. I think I found that this kind of thing can be done only by setting, but I forgot, so I once collected those things in SimpleController.java.

UserService.java


@Component
public class UserService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StringUtils.isEmpty(username)) {
            throw new UsernameNotFoundException("Username is empty");
        }
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found for name: " + username);
        }
        return user;
    }
}

UserRepository.java


public interface UserRepository extends JpaRepository<User, Long> {
    public User findByUsername(String username);
}

Other info.ewai.sbmt.domain.User info.ewai.sbmt.domain.Authorities login.html

I implemented UserDetails and made it almost as standard, It may be possible to use it by converting and synchronizing the user information of the in-house system.

Users / privileges that can be used in the sample system

Data input by default

user password Authority Authority content
sbt sbt ROLE_USER Normal user authority (reference system only)
admin admin ROLE_ADMIN Administrator authority (data can be updated)
admin admin ACTUATOR Privilege to use Spring Boot Actuator

Search screen

BookController.java


    @RequestMapping(value = "/book", method = RequestMethod.GET)
    public String index(Model model) {
        List<Book> list = this.bookservice.findAll();
        model.addAttribute("booklist", list);
        model.addAttribute("bookForm", new BookForm());
        return "book";
    }

BookService.java


    public List<Book> findByBookNameLikeAndTagLike(String bookName, String tag) {
        if (StringUtils.isEmpty(bookName) && (StringUtils.isEmpty(tag))) {
            return this.findAll();
        }
        return this.bookRepository.findByBookNameLikeAndTagLike("%" + bookName + "%", "%" + tag + "%");
    }

BookRepository.java


public interface BookRepository extends JpaRepository<Book, Long> {

    public List<Book> findByBookNameLikeAndTagLike(String bookName, String tag);
}

If you specify Like in the method, you can do an ambiguous search, so I tried using it. I thought that% would be added automatically to the parameter, but I couldn't, so I added%. It seems that you can easily page with standard functions, but I haven't done so for the time being.

Actually, I think that it is possible to create complicated SQL, so I think that I will create a custom repository and write JPQL and SQL.

@PersistenceContext
EntityManager entityManager;
 ~
Query query = entityManager.createQuery("from Book where id = :id")

Editing screen

Check the input

I am making a custom Varidator and checking it.

BookValidator.java


@Component
public class BookValidator implements Validator {

    @Autowired
    BookService bookService;

    @Override
    public boolean supports(Class<?> clazz) {
        return BookForm.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {

        // required check
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "bookName", "field.required");

        // TODO form check
        // BookForm form = BookForm.class.cast(target);
        // errors.rejectValue("field", "errorCode");

        // global message
        if (errors.hasErrors()) {
            errors.reject("input.error");
        }
    }
}

BookForm.java


Almost Book.Same as java Entity

For simple checks such as mandatory check and size check, annotate the Form. It is possible to check, but I wanted to reuse Form and I did not want to distribute the check to Form and Validator. Checks are aggregated in Validator.

Error message is set.

The error message is in messages_ja.properties.

BookController.java


    @RequestMapping(value = "/book/save", method = RequestMethod.POST)
    public String save(@Valid @ModelAttribute BookForm bookForm, BindingResult result, Model model) {
        logger.info("save/" + bookForm.getBookId());

        if (result.hasErrors()) {
            return "book-edit";
        }

        try {
            this.bookservice.save(new Book(bookForm));
        } catch (Exception e) {
            result.reject("exception.error");
            result.reject("using defaultMessage", e.toString());
            return "book-edit";
        }

        return "book-complete";
    }

new Book(bookForm) I am switching from form to entity at. Isn't there any good way?

// Show global message bindingResult.reject("errorCode")

// Display error message for each field bindingResult.reject("field", "errorCode")

In Controller, set reject in BindingResult and In Validator, set reject in Errors.

@Valid @ModelAttribute BookForm bookForm, BindingResult result

It seems to be a rule to write the definition of the argument part in this order. If you bring the BindingResult to the front, an error will occur. I got into it a little.

If @Valid is added, this will be called in a state that has been checked in advance by Validator. So I only check result.hasErrors () for errors.

Update process

BookService.java


    @Transactional
    public Book save(Book book) {
        return this.bookRepository.save(book);
    }

Actually, I think that more complicated processing will come in, but it is just saving. If @Transactional is added, it will be rolled back when an Exception occurs. It seems that unchecked exceptions (RuntimeException, etc.) are rolled back.

I was allowed to reference) http://qiita.com/NagaokaKenichi/items/a279857cc2d22a35d0dd

I thought about adding @Transactional to the Controller method, Due to screen control, Exception is caught and processed, but then it was not rolled back, so I wonder if all the business logic should be attached here by bringing it to Service I thought.

In complicated cases, of course, it seems that Transaction will be taken out from EntityManager and controlled, I wonder if it's okay to use annotations as a basis, or not.

Thymeleaf template common

If there is a change in the source that is commonly used on each screen, all pages must be changed ... I shared the source like.

The following is a common source.

common.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.springframework.org/schema/security">
<head>
<!-- common head -->
<th:block th:fragment="head"><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
          th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}" rel="stylesheet" />
    <link href="/css/common.css"
          th:href="@{/css/common.css}" rel="stylesheet" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
            th:src="@{/webjars/jquery/3.2.1/jquery.min.js}"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
            th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script></th:block>
</head>
<body>
    <div th:fragment="header" class="container" id="header">
        <h1><a href="/">Book</a></h1>
        <p style="text-align:right;" th:if="${#httpServletRequest.remoteUser != null}">Hello 
          <span th:text="${#httpServletRequest.remoteUser}" /> | 
          <a href="/logout">Log out</a>  | 
          <a href="https://github.com/ewai/spring-boot-mvc-template" target="_blank" alt="spring-boot-mvc-template"><img src="img/mark-github.svg" /></a>
        </p>
    </div>

    <div th:fragment="footer" class="container" id="footer">
        <ul>
            <li><a href="https://github.com/ewai/spring-boot-mvc-template" target="_blank"><img src="img/mark-github.svg"  alt="spring-boot-mvc-template"/></a> <a href="https://github.com/ewai/spring-boot-mvc-template">spring-boot-mvc-template</a></li>
            <li><a href="https://github.com/ewai/docker-spring-boot-template-mysql">docker-spring-boot-template-mysql</a></li>
        </ul>
    </div>

</body>
</html>

The template is inside the ** th: fragment ** tag. I made three.

Since it is a common file, I thought about making it a different directory, but it was not read, so it is placed directly under templates.

book.html


<head>
    <th:block th:include="common::head"></th:block>
    <title>Book Search</title>
</head>
<body>
    <th:block th:replace="common::header"></th:block>
  ~~~content~~~
    <th:block th:replace="common::footer"></th:block>
</body>
</html>

I use each page like this.

The disadvantage is that you can't check the design as html. Personally, I start the application and check it while running it, It may be better not to use it when the designer creates it with pure html and checks it.

Around authority

When displaying only to a specific user

build.gradle


   + compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.3.RELEASE')

At first I added the following, but it didn't work (sec: authorize = "hasRole ('ROLE_ADMIN')" was displayed in html as it is) and it worked when I lowered the version.

compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE')

To display only authorized users

index.html


<li class="list-group-item" sec:authorize="hasRole('ROLE_ADMIN')"><a href="/book/create" th:href="@{/book/create}" class="btn btn-link" id="link">Book registration</a></li>

Added so that sec can be used

index.html


      xmlns:sec="http://www.springframework.org/schema/security">

https://github.com/thymeleaf/thymeleaf-extras-springsecurity

Make it an executable jar

build.gradle


springBoot {
    executable = true
}

If you add this and build it, it becomes an executable jar. What does that mean?

./spring-boot-mvc-template-0.0.1-SNAPSHOT.jar

It is possible to execute it like this.

It can be used when you want to start it automatically when the OS starts.

For centos7

/etc/systemd/system/sbt.service


[Unit]
Description=sbt
After=syslog.target

[Service]
User=sbtuser
ExecStart=/xxx/xxx/spring-boot-mvc-template-0.0.1-SNAPSHOT.jar
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

cmd


systemctl enable sbt.service

Now it will start automatically when the OS starts.

Spring Boot Actuator

build.gradle


   + compile('org.springframework.boot:spring-boot-starter-actuator')

I added it because you can easily check the server status just by adding this. A user who has "ACUTIATOR" authority in getAuthorities () of User (UserDetails) It seems that you can not see it unless you log in. This time, the admin user has this privilege, so you can see it by logging in with admin / admin.

http://localhost:8080/health

/health


{"status":"UP","diskSpace":{"status":"UP","total":247762329600,"free":125178765312,"threshold":10485760},"db":{"status":"UP","database":"MySQL","hello":1}}

You can see that the server is alive, the DB is alive, and the disk space is still free.

When I tried to refer to it as a user without "ACUTIATOR" authority, Since {"status": "UP"} is returned, you can see if the server is up.

http://localhost:8080/env As expected, if you do not have "ACUTIATOR" authority, you cannot refer to the environment variable system. I got an error.

Access is denied. User must have one of the these roles: ACTUATOR

http://localhost:8080/mappings It seems that a design document can be made.

/mappings


   "{[/book],methods=[GET]}":{  
      "bean":"requestMappingHandlerMapping",
      "method":"public java.lang.String info.ewai.sbmt.web.BookController.index(org.springframework.ui.Model)"
   },
   "{[/book/edit/{bookId}],methods=[GET]}":{  
      "bean":"requestMappingHandlerMapping",
      "method":"public java.lang.String info.ewai.sbmt.web.BookController.edit(info.ewai.sbmt.web.form.BookForm,org.springframework.validation.BindingResult,java.lang.Long,org.springframework.ui.Model)"
   },
   "{[/book/save],methods=[POST]}":{  
      "bean":"requestMappingHandlerMapping",
      "method":"public java.lang.String info.ewai.sbmt.web.BookController.save(info.ewai.sbmt.web.form.BookForm,org.springframework.validation.BindingResult,org.springframework.ui.Model)"
   },

There are likely to be other endpoints as well. http://qiita.com/MariMurotani/items/01dafd2978076b5db2f3

It seems that you can customize it and change the URL port, but leave it as it is.

Information that was used as a reference

Spring Framework Reference Documentation 4.3.0.RELEASE http://docs.spring.io/spring/docs/4.3.0.RELEASE/spring-framework-reference/htmlsingle/

Spring Boot Reference Guide 1.5.6.RELEASE http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/

Thorough introduction to Spring Java application development with Spring Framework It was helpful to explain and understand the functions of Spring in general. There is also a little written about Spring boot.

Introduction to Spring Boot Programming

Finally

It's a Spring boot that's very easy and quick to make, but there were times when I didn't know the rules. However, I felt that the official documents, books, and online information were abundant and relatively easy to solve. This time I made a simple sample system, so I think that there will be various trials and errors and addictions when making a practical system, but I felt that I would like to use it. Thank you to all the people who have posted various information in books and online.

Recommended Posts

I made a simple MVC sample system using Spring Boot
I made a simple search form with Spring Boot + GitHub Search API.
I made a simple recommendation function.
[Ruby] I made a simple Ping client
I made a bulletin board using Docker 1
Java beginner tried to make a simple web application using Spring Boot
Steps to create a simple camel app using Apache Camel Spring Boot starters
[LINE BOT] I made a ramen BOT with Java (Maven) + Heroku + Spring Boot (1)
Create a simple search app with Spring Boot
Create a Spring Boot application using IntelliJ IDEA
[Rails] I made a draft function using enum
I made a sample of how to write delegate in SwiftUI 2.0 using MapKit
Delegate pattern between views. I also made a sample page transition using NavigationLink.
Create a portfolio app using Java and Spring Boot
I made a simple calculation problem game in Java
[JUnit 5 compatible] Write a test using JUnit 5 with Spring boot 2.2, 2.3
Implement a simple Rest API with Spring Security with Spring Boot 2.0
I made a Restful server and client in Spring.
[Introduction to Spring Boot] Submit a form using thymeleaf
Create a simple demo site with Spring Security with Spring Boot 2.1
I wrote a test with Spring Boot + JUnit 5 now
I made a chat app.
Try using Spring Boot Security
I haven't understood after touching Spring Boot for a month
A Simple CRUD Sample Using Java Servlet / JSP and MySQL
I tried to get started with Swagger using Spring Boot
Implement a simple Rest API with Spring Security & JWT with Spring Boot 2.0
I made a Dockerfile to start Glassfish 5 using Oracle Java
Implement a simple Web REST API server with Spring Boot + MySQL
I tried printing a form with Spring MVC and JasperReports 1/3 (JasperReports settings)
I made a command line interface with WinMerge Plugin using JD-Core
[Rails] I made a simple calendar mini app with customized specifications.
I tried printing a form with Spring MVC and JasperReports 3/3 (Spring MVC control)
How to make a hinadan for a Spring Boot project using SPRING INITIALIZR
02. I made an API to connect to MySQL (MyBatis) from Spring Boot
I tried connecting to MySQL using JDBC Template with Spring MVC
Fitted in Spring Boot using a bean definition file named application.xml
Sample code to unit test a Spring Boot controller with MockMvc
I tried to build a simple application using Dockder + Rails Scaffold
Spring Boot Introductory Guide I tried [Consuming a RESTful Web Service]
Run a simple model made with Keras on iOS using CoreML
Ruby: I made a FizzBuzz program!
I tried using Spring + Mybatis + DbUnit
Spring Boot Tutorial Using Spring Security Authentication
I made a shopify app @java
I made a GUI with Swing
Spring Boot: Restful API sample project
A simple sample callback in Java
I tried Flyway with Spring Boot
I made a matching app (Android app)
I made a package.xml generation tool.
[Android] I made a pedometer app.
I tried to make a simple face recognition Android application using OpenCV
I tried printing a form with Spring MVC and JasperReports 2/3 (form template creation)
Automatically deploy a web application developed in Java using Jenkins [Spring Boot application]
[Spring Boot] I stumbled upon a method call count test (Spock framework)
What is a Spring Boot .original file?
Create a simple on-demand batch with Spring Batch
A simple sample of ArBiMap (two-way map)
Try using Spring Boot with VS Code
I made a risky die with Ruby