Spring Boot进阶系列三

Thymeleaf是官方推荐的显示引擎,这篇文章主要介绍怎么让spring boot整合Thymeleaf.  它是一个适用于Web和独立环境的现代服务器端Java模板引擎。

Thymeleaf的主要目标是给开发工作流程带来优雅的自然模板 - 可以在浏览器中正确显示的HTML,也可以用作静态原型,从而在开发团队中实现更强大的协作。通过Spring Framework模块,与喜欢的工具的集成,Thymeleaf是HTML5 JVM Web开发的理想选择。

 

1.自然模板官方示例

用Thymeleaf编写的HTML模板看起来和HTML一样工作

<table>
  <thead>
    <tr>
      <th th:text="#{msgs.headers.name}">Name</th>
      <th th:text="#{msgs.headers.price}">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr th:each="prod: ${allProducts}">
      <td th:text="${prod.name}">Oranges</td>
      <td th:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td>
    </tr>
  </tbody>
</table>

 

2.项目结构

2.1 主要功能还是添加一本书,查看一本书的明细,以及返回所有的书籍。这次项目中用到两个数据表。

CREATE TABLE `book` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `Category` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `Price` decimal(18,2) NOT NULL,
  `Publish_Date` date NOT NULL,
  `Poster` varchar(128) NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=87 DEFAULT CHARSET=utf8

CREATE TABLE `author` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `phone` varchar(16) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(45) COLLATE utf8_unicode_ci NOT NULL,
  `book_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

 

2.2 和系列二中的示例几乎一样,多了一个templates文件夹用来存放thymeleaf文件。

需要在application.properties文件中添加以下内容:

 

spring.thymeleaf.cache=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html

 

2.3 项目用到的技术如下,

  •   Spring-Data-JPA
  •   H5
  •   Bootstrap
  •   Thymeleaf
  •   数据库依旧是MySQL

2.4 项目逻辑架构图

 

2.5 完整的pom.xml如下,

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- springboot,jpa 整合包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- mysql 驱动包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--根据MySQL server 版本实际情况变动 -->
            <version>8.0.17</version><!--$NO-MVN-MAN-VER$ -->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

3.后端服务层

3.1 com.example.demo.model包中新建Author,Book,BookViewModel类,

@Entity
public class Author implements Serializable {
    private static final long serialVersionUID = -3523362375538074534L;
    @Id
    private Integer id;
    private String name;
    private String phone;
    private String email;
    private Integer bookId;
} //省略getter & setter方法

@Entity
public class Book implements Serializable {
    private static final long serialVersionUID = -3123479062966697145L;
    @Id
    private Integer id;
    private String name;
    private String category;
    private Double price;
    private Date publish_date;
    private String poster;
} //省略getter & setter方法

public class BookViewModel {
    private Book book;
    private Author author;
} //省略getter & setter方法

3.2com.example.demo.store包中,新建AuthorRepository,BookRepository接口

public interface BookRepository extends JpaRepository<Book,Integer> {
    @Query(value = "SELECT * FROM book order by Id desc", nativeQuery = true)
    List<Book> findBooks();
}

public interface AuthorRepository extends JpaRepository<Author, Integer> {
    Author findByBookId(Integer bookId);
}

3.3com.example.demo.controller包中创建BookController类,

@Controller
@RequestMapping(path = "/book")
public class BookController {

    @Autowired
    private BookRepository bookRepository;
    
    @Autowired
    private AuthorRepository authorRepository;
    
    @Value("${upload.path}")
    private String filePath;
    
    @GetMapping(path="/add")
    public ModelAndView add() {
        ModelAndView modelAndView = new ModelAndView("add");
        return modelAndView;
    }

    @PostMapping(path = "/save")
    public String save(HttpServletRequest request, @RequestParam("inputPoster") MultipartFile inputPoster) {

        String tempPath = filePath;
        String name = request.getParameter("inputName");
        String category = request.getParameter("categorySelect");
        Double price = Double.valueOf(request.getParameter("inputPrice"));
        // Give today as a default date
        Date publishDate = new Date();
        String temp = request.getParameter("inputDate");
        DateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd");
        // Test only
        try {
            publishDate = formatDate.parse(temp);
        } catch (ParseException e2) {
            // TODO Auto-generated catch block
            e2.printStackTrace();
        }
        // Handle uploading picture
        String fileName = inputPoster.getOriginalFilename();
        String suffixName = fileName.substring(fileName.lastIndexOf('.'));
        fileName = UUID.randomUUID() + suffixName;
        String posterPath = "image/" + fileName;
        Book book = new Book();
        book.setId(0);
        book.setName(name);
        book.setCategory(category);
        book.setPrice(price);
        book.setPublish_date(publishDate);
        book.setPoster(posterPath);
        
        tempPath += fileName;
        try {
            inputPoster.transferTo(new File(tempPath));
            bookRepository.save(book);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "redirect:/book/all";
    }

    @GetMapping(path = "/all")
    public ModelAndView findAll() {
        ModelAndView modelAndView = new ModelAndView("home");
        Iterable<Book> books = bookRepository.findBooks();
        modelAndView.addObject("bookList", books);
        return modelAndView;
    }
    
    
    @GetMapping(path="view/{id}")
    public ModelAndView view(@PathVariable Integer id) {
        ModelAndView mv = new ModelAndView("view");
        Optional<Book> optional = bookRepository.findById(id);
        Book book = optional.orElseGet(Book::new);
        Author author = authorRepository.findByBookId(id);
        if(author == null) {
            author = new Author();
        }
        BookViewModel bookViewModel = new BookViewModel();
        bookViewModel.setAuthor(author);
        bookViewModel.setBook(book);
        mv.addObject("bookView",bookViewModel);
        return mv;
        
    }
}

3.4com.example.demo.config 包中的WebConfig类和示例二中完全一样,此处省略不提。

4.前端显示层

在templates文件夹里面添加三个文件,分别是home.html, add.html, view.html.

4.1 home.html

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8"></meta>
<meta name="viewport"
    content="width=device-width, initial-scale=1, shrink-to-fit=no"></meta>

<!-- Bootstrap CSS -->
<link rel="stylesheet"
    href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
    crossorigin="anonymous"></link>

<title>Index Page</title>
</head>
<body>
    <div class="container">
        <h1>Springboot进阶系列三</h1>

        <!-- Content here -->
        <div class="table-responsive">
            <table class="table table-striped table-sm" id="books">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>Category</th>
                        <th>Price</th>
                        <th>Operation</th>
                    </tr>
                </thead>
                <tbody>
                    <tr th:each="book : ${bookList}">
                        <td th:text="${book.id}"></td>
                        <td th:text="${book.name}"></td>
                        <td th:text="${book.category}"></td>
                        <td th:text="${book.price}"></td>
                        <td><a class="btn btn-outline-primary"
                            th:href="@{/book/view/{id}(id=${book.id})}" role="button">View</a>
                            <a class="btn btn-outline-primary"
                            th:href="@{/book/edit/{id}(id=${book.id})}" role="button">Edit</a>
                            <a class="btn btn-outline-primary"
                            th:href="@{/book/delete/{id}(id=${book.id})}" role="button">Delete</a></td>
                    </tr>
                </tbody>
            </table>
        </div>

        <a href="/book/add" class="btn btn-outline-primary" role="button"
            aria-pressed="true">Add Book</a>
    </div>
</body>
</html>

4.2 add.html

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8"></meta>
<meta name="viewport"
    content="width=device-width, initial-scale=1, shrink-to-fit=no"></meta>

<!-- Bootstrap CSS -->
<link rel="stylesheet"
    href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
    crossorigin="anonymous"></link>

<title>Add Book</title>
</head>
<body>
    <div class="container">
        <h1>Springboot进阶系列三</h1>
        <!-- Content here -->
        <form action="/book/save" method="POST" enctype="multipart/form-data">
            <div class="form-group row">
                <label for="inputName" class="col-sm-2 col-form-label">Name</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" name="inputName"
                        placeholder="Name">
                </div>
            </div>
            <div class="form-group row">
                <label for="categorySelect" class="col-sm-2 col-form-label">Category</label>
                <div class="col-sm-10">
                    <select class="form-control" name="categorySelect">
                        <option>武侠</option>
                        <option>历史</option>
                        <option>军事</option>
                        <option>国学</option>
                        <option>投资</option>
                        <option>管理</option>
                        <option>传记</option>
                    </select>
                </div>
            </div>
            <div class="form-group row">
                <label for="inputPrice" class="col-sm-2 col-form-label">Price</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" name="inputPrice"
                        placeholder="Price">
                </div>
            </div>
            <div class="form-group row">
                <label for="inputDate" class="col-sm-2 col-form-label">Publish
                    Date</label>
                <div class="col-sm-10">
                    <input type="date" class="form-control" name="inputDate" />
                </div>
            </div>
            <div class="form-group row">
                <label for="inputPoster" class="col-sm-2 col-form-label">Poster</label>
                <div class="col-sm-10">
                    <input type="file" class="form-control" name="inputPoster" />
                </div>
            </div>
            <div class="form-group row">
                <div class="col-sm-10">
                    <button type="submit" class="btn btn-outline-primary">Save</button>
                </div>
            </div>
        </form>
    </div>
</body>
</html>

4.3 view.html

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8"></meta>
<meta name="viewport"
    content="width=device-width, initial-scale=1, shrink-to-fit=no"></meta>

<!-- Bootstrap CSS -->
<link rel="stylesheet"
    href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
    crossorigin="anonymous"></link>

<title>View Book</title>
</head>
<body>
    <div class="container">
        <h1>Springboot进阶系列三</h1>
        <!-- Content here -->
        
            <div class="form-group row">
                <label class="col-sm-2 col-form-label">Name</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" th:value="${bookView.Book.name}">
                </div>
            </div>
            <div class="form-group row">
                <label class="col-sm-2 col-form-label">Category</label>
                <div class="col-sm-10">
                    <select class="form-control" th:value="${bookView.Book.category}">
                        <option>武侠</option>
                        <option>历史</option>
                        <option>军事</option>
                        <option>国学</option>
                        <option>投资</option>
                        <option>管理</option>
                        <option>传记</option>
                    </select>
                </div>
            </div>
            <div class="form-group row">
                <label class="col-sm-2 col-form-label">Price</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" th:value="${bookView.Book.price}">
                </div>
            </div>
            <div class="form-group row">
                <label class="col-sm-2 col-form-label">Publish
                    Date</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" th:value="${#dates.format(bookView.Book.publish_date,'yyyy-MM-dd')}" />
                </div>
            </div>
            <div class="form-group row">
                <label for="inputPoster" class="col-sm-2 col-form-label">Poster</label>
                <div class="col-sm-10">
                    <img th:src="${'../../' + bookView.Book.poster}" alt="Poster"></img>
                </div>
            </div>
            <div class="form-group row">
                <label class="col-sm-2 col-form-label">Author</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" th:value="${bookView.Author.name}">
                </div>
            </div>
            <div class="form-group row">
                <label class="col-sm-2 col-form-label">Phone</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" th:value="${bookView.Author.phone}">
                </div>
            </div>
            <div class="form-group row">
                <label class="col-sm-2 col-form-label">Email</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" th:value="${bookView.Author.email}">
                </div>
            </div>
            <div class="form-group row">
                <div class="col-sm-10">
                    <a href="/book/all" class="btn btn-outline-dark" role="button" aria-pressed="true">Back</a>
                </div>
            </div>

    </div>
</body>
</html>

5.运行程序

浏览器中输入http://localhost:8080/book/all,显示如下,

 

点击 Add Book按钮,跳转到add.html页面,

 

posted @ 2019-09-15 23:32  沐璟  阅读(253)  评论(0编辑  收藏  举报