Отвечая на вопрос «Как отобразить данные из БД» на Тостере, написал небольшое веб-приложение, использующее Spring Framework, Boot и Data и JSP/JSTL в качестве способа отображения страниц. Задача — написать простую книгу отзывов, которая может вывести список отзывов и добавить новый.
Для начала создадим новый Maven веб-проект и добавим необходимые зависимости. Итоговый pom-файл выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>name.alexkosarev.tutorials</groupId> <artifactId>sandbox-guestbook</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>sandbox-guestbook</name> <properties> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> <compilerArguments> <endorseddirs>${endorsed.dir}</endorseddirs> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <executions> <execution> <phase>validate</phase> <goals> <goal>copy</goal> </goals> <configuration> <outputDirectory>${endorsed.dir}</outputDirectory> <silent>true</silent> <artifactItems> <artifactItem> <groupId>javax</groupId> <artifactId>javaee-endorsed-api</artifactId> <version>7.0</version> <type>jar</type> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
Обратите внимание на то, что для standalone-режима нам понадобится зависимость tomcat-embed-jasper для работы в JSP/JSTL. Теперь опишем класс, который будет запускать приложение:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@SpringBootApplication public class GuestBook extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(GuestBook.class, args) .registerShutdownHook(); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(GuestBook.class); } } |
Метод main позволит запустить приложение без развёртывания в сервлет-контейнере, а метод configure из SpringBootServletInitializer позволяет запустить приложение в сервлет-контейнере.
Теперь займёмся конфигурацией:
1 2 3 4 5 6 7 8 |
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp("/WEB-INF/views/", ".jspx"); } } |
Здесь мы указываем, что представления будут браться из ресурсов приложения. Поиск представлений с расширением .jspx будет вестись относительно директории /WEB-INF/views.
Нам понадобится класс-сущность. Структура его достаточно простая, нам понадобится идентификатор, текст отзыва и дата отзыва:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Entity public class Review implements Serializable { private static final long serialVersionUID = 20160624090101L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(columnDefinition = "TEXT") private String reviewText; @Temporal(javax.persistence.TemporalType.TIMESTAMP) private Date createDate; // код опущен } |
Аннотация @GeneratedValue говорит фреймворку, что идентификатор должен быть сгенерирован. В данном случае стратегия GenerationType.IDENTITY будет преобразована в AUTO_INCREMENT в MySQL и в Serial в PostgreSQL.
По умолчанию текстовые поля преобразуются в VARCHAR(255) в SQL, но свойство, содержащее текст отзыва, может содержать больше 255 символов, так что нужно указать SQL-тип TEXT, как это показано в аннотации @Column.
Ну и в случае с датой добавления отзыва нужно указать аннотацию @Temporal, как это требует JPA. В качестве её значения нужно указать TIMESTAMP, так как нас интересует дата и время добавления отзыва.
Для работы с базой данных будем использовать репозиторий Spring Data:
1 2 3 |
@Repository public interface ReviewRepository extends CrudRepository<Review, Integer> { } |
Поскольку у нас в примере будет только два действия, то написание дополнительных методов будет лишним.
Опишем контроллер, который будет выполнять все действия:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Controller public class SiteController { @Autowired private ReviewRepository reviewRepository; @RequestMapping public ModelAndView index() { return new ModelAndView("site/index", Collections.singletonMap("reviews", reviewRepository.findAll())); } @RequestMapping(method = RequestMethod.POST) public String createReview(@RequestParam String reviewText) { reviewRepository.save(new Review(reviewText, new Date())); return "redirect:/"; } } |
POST-запрос будет добавлять новую запись в список отзывов и перенаправлять на главную страницу сайта. На главной странице мы будем видеть список отзывов.
Ну и наконец опишем нашу JSPX-страницу с отзывами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?xml version="1.0" encoding="UTF-8"?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="http://java.sun.com/jsp/jstl/core" version="2.0"> <jsp:directive.page contentType="text/html" pageEncoding="UTF-8"/> <jsp:output doctype-root-element="html" doctype-system="about:legacy-compat" omit-xml-declaration="true"/> <html> <head> <title>GuestBoot Example</title> </head> <body> <h1>Post new review</h1> <form method="POST"> <label> <textarea name="reviewText" placeholder="Your review here"><jsp:text/></textarea> </label> <br/> <button type="submit">Submit</button> </form> <hr/> <h1>Reviews</h1> <c:forEach items="${reviews}" var="review"> <div> <c:out value="${review.reviewText}"/><br/> <small><c:out value="${review.createDate}"/></small> </div> <br/> </c:forEach> </body> </html> </jsp:root> |
Теперь можно посмотреть запустить приложение, развернув в сервлет-контейнере, либо выполнив команду
1 |
java -jar target/sandbox-guestbook-1.0.0.war |
Работающее приложение: