2025-09 spring-16-view

This commit is contained in:
stvort
2025-12-04 20:26:52 +04:00
parent f061675f1e
commit 3963e82c69
86 changed files with 2312 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
+21
View File
@@ -0,0 +1,21 @@
<?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>ru.otus</groupId>
<artifactId>spring-mvc-view</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>spring-mvc-view-exercise</module>
<module>spring-mvc-view-demo</module>
<module>spring-mvc-view-solution1</module>
<module>spring-mvc-view-solution2</module>
<module>spring-mvc-view-solution3</module>
<module>spring-mvc-view-solution4</module>
</modules>
</project>
@@ -0,0 +1,24 @@
target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
@@ -0,0 +1,86 @@
<?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>ru.otus</groupId>
<artifactId>spring-mvc-view-demo</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<h2.version>2.2.220</h2.version>
<snakeyaml.version>2.0</snakeyaml.version>
<!--<hibernate.validator.version>7.0.2.Final</hibernate.validator.version>-->
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,14 @@
package ru.otus.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
System.out.printf("Чтобы перейти на страницу сайта открывай: %n%s%n",
"http://localhost:8080");
}
}
@@ -0,0 +1,34 @@
package ru.otus.spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import java.util.Locale;
@Configuration
public class LocalizationConfig implements WebMvcConfigurer {
@Bean(name = "localeResolver")
public LocaleResolver localeResolver() {
var resolver = new CookieLocaleResolver("locale");
resolver.setDefaultLocale(new Locale("en"));
return resolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
var localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
@@ -0,0 +1,23 @@
package ru.otus.spring.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@RequiredArgsConstructor
@ControllerAdvice
public class GlobalExceptionHandler {
private final MessageSource messageSource;
@ExceptionHandler(NotFoundException.class)
public ModelAndView handeNotFoundException(NotFoundException ex) {
String errorText = messageSource.getMessage("person-not-found-error", null,
LocaleContextHolder.getLocale());
return new ModelAndView("customError", "errorText", errorText);
}
}
@@ -0,0 +1,8 @@
package ru.otus.spring.controller;
public class NotFoundException extends RuntimeException{
NotFoundException() {
super("Person not found");
}
}
@@ -0,0 +1,56 @@
package ru.otus.spring.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.otus.spring.dto.PersonDto;
import ru.otus.spring.repostory.PersonRepository;
import java.util.List;
@Slf4j
@Controller
@RequiredArgsConstructor
public class PersonController {
private final PersonRepository repository;
@GetMapping("/")
public String listPage(Model model) {
List<PersonDto> persons = repository.findAll().stream()
.map(PersonDto::fromDomainObject).toList();
model.addAttribute("persons", persons);
return "list";
}
@GetMapping("/edit")
public String editPage(@RequestParam("id") long id, Model model) {
PersonDto person = repository.findById(id)
.map(PersonDto::fromDomainObject)
.orElseThrow(NotFoundException::new);
model.addAttribute("person", person);
return "edit";
}
@PostMapping("/edit")
public String savePerson(@Valid @ModelAttribute("person") PersonDto person,
BindingResult bindingResult,
@RequestParam(value = "hobby", defaultValue = "") List<String> hobby) {
if (bindingResult.hasErrors()) {
return "edit";
}
log.debug("Hobby from plain RequestParam: {}", String.join(", ", hobby));
log.debug("Hobby from DTO: {}", person.hobbyAsString());
repository.save(person.toDomainObject());
return "redirect:/";
}
}
@@ -0,0 +1,29 @@
package ru.otus.spring.domain;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.FetchType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@ElementCollection(fetch = FetchType.EAGER)
private List<String> hobby;
}
@@ -0,0 +1,38 @@
package ru.otus.spring.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import ru.otus.spring.domain.Person;
import java.util.List;
import static org.springframework.util.CollectionUtils.isEmpty;
@Data
@AllArgsConstructor
public class PersonDto {
private long id;
@NotBlank(message = "{name-field-should-not-be-blank}")
@Size(min = 2, max = 10, message = "{name-field-should-has-expected-size}")
private String name;
private List<String> hobby;
public String hobbyAsString() {
if (isEmpty(hobby)){
return "";
}
return String.join(", ", hobby);
}
public Person toDomainObject(){
return new Person(id, name, hobby);
}
public static PersonDto fromDomainObject(Person person) {
return new PersonDto(person.getId(), person.getName(), person.getHobby());
}
}
@@ -0,0 +1,11 @@
package ru.otus.spring.repostory;
import org.springframework.data.repository.CrudRepository;
import ru.otus.spring.domain.Person;
import java.util.List;
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAll();
}
@@ -0,0 +1,23 @@
spring:
messages:
encoding: UTF-8
datasource:
url: jdbc:h2:mem:testdb
sql:
init:
mode: always
jpa:
open-in-view: false
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
ROOT: ERROR
ru.otus.spring.controller: DEBUG
@@ -0,0 +1,10 @@
insert into person (id, name) values (1, 'Pushkin');
insert into person (id, name) values (2, 'Lermontov');
insert into person_hobby (person_id, hobby)
values
(1, 'Fishing'),
(1, 'Poetry'),
(2, 'Traveling'),
(2, 'Poetry')
;
@@ -0,0 +1,16 @@
lang-switcher-header=Select language
en-lang-switch-button-caption=Language - EN
ru-lang-switch-button-caption=Language - RU
persons-table-header=Persons:
persons-table-column-action=Action
person-field-id=ID
person-field-name=Name
person-field-hobby=Hobby
edit-button-caption=Edit
person-form-header=Person Info:
save-button-caption=Save
cancel-button-caption=Cancel
name-field-should-not-be-blank=Name field should not be blank
name-field-should-has-expected-size=Name field should be between 2 and 10 characters
error-text-header=Error
person-not-found-error=Person not found
@@ -0,0 +1,16 @@
lang-switcher-header=Select language
en-lang-switch-button-caption=Language - EN
ru-lang-switch-button-caption=Language - RU
persons-table-header=Persons:
persons-table-column-action=Action
person-field-column-id=ID
person-field-name=Name
person-field-hobby=Hobby
edit-button-caption=Edit
person-form-header=Person Info:
save-button-caption=Save
cancel-button-caption=Cancel
name-field-should-not-be-blank=Name field should not be blank
name-field-should-has-expected-size=Name field should be between 2 and 10 characters
error-text-header=Error
person-not-found-error=Person not found
@@ -0,0 +1,16 @@
lang-switcher-header=\u0412\u044B\u0431\u043E\u0440 \u044F\u0437\u044B\u043A\u0430
en-lang-switch-button-caption=\u042F\u0437\u044B\u043A - EN
ru-lang-switch-button-caption=\u042F\u0437\u044B\u043A - RU
persons-table-header=\u041F\u0451\u0440\u0441\u043E\u043D\u044B:
persons-table-column-action=\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435
person-field-id=\u0410\u0439\u0414\u0438
person-field-name=\u0418\u043C\u044F
person-field-hobby=\u0425\u043E\u0431\u0431\u0438
edit-button-caption=\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C
person-form-header=\u0418\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u043F\u0451\u0440\u0441\u043E\u043D\u0435:
save-button-caption=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C
cancel-button-caption=\u041E\u0442\u043C\u0435\u043D\u0430
name-field-should-not-be-blank=\u0418\u043C\u044F \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C
name-field-should-has-expected-size=\u0414\u043B\u0438\u043D\u0430 \u0438\u043C\u0435\u043D\u0438 \u0434\u043E\u043B\u0436\u043D\u0430 \u0431\u044B\u0442\u044C \u043E\u0442 2 \u0434\u043E 10 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432
error-text-header=\u041E\u0448\u0438\u0431\u043A\u0430
person-not-found-error=\u041F\u0451\u0440\u0441\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D
@@ -0,0 +1,11 @@
create table person (
id integer generated by default as identity,
name varchar(255),
hobby varchar(500),
primary key (id)
);
create table person_hobby (
person_id integer references person(id),
hobby varchar(255)
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
h3 {
background-image: url("../static/listmark.png");
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
</style>
<style type="text/css" th:inline="text">
[[h3]] {
background-image: url([[@{/listmark.png}]]);
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
</style>
</head>
<body>
<h3 th:text="#{error-text-header}">Error text</h3>
<span th:text="${errorText}">Error text</span>
</body>
</html>
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Edit person</title>
<style type="text/css">
body {
padding: 50px;
}
label {
display: inline-block;
width: 100px;
}
input:read-only {
background: lightgray;
}
.row {
margin-top: 10px;
}
h3 {
background-image: url("../static/listmark.png");
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
.errors {
color: red;
}
</style>
<style type="text/css" th:inline="text">
[[h3]] {
background-image: url([[@{/listmark.png}]]);
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
</style>
</head>
<body>
<!-- Person edition -->
<form id="edit-form" action="edit.html" th:action="@{/edit(id=${person.id})}" th:method="post" th:object="${person}">
<h3 th:text = "#{person-form-header}">Person Info:</h3>
<div class="row">
<label for="id-input" th:text="#{person-field-id} + ':'">ID:</label>
<input id="id-input" type="text" readonly="readonly" name="id" th:value="*{id}" value="1"/>
</div>
<div class="row">
<label for="person-name-input" th:text="#{person-field-name} + ':'">Name:</label>
<input id="person-name-input" name="name" type="text" th:value="*{name}" value="John Doe"/>
<div class="errors" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Wrong person name error</div>
</div>
<div class="row">
<label for="person-hobby-select" th:text="#{person-field-hobby} + ':'">Hobby:</label>
<select id="person-hobby-select" name="hobby" multiple>
<option value="Fishing" th:selected="*{hobby.contains('Fishing')}">Fishing</option>
<option value="Poetry" th:selected="*{hobby.contains('Poetry')}">Poetry</option>
<option value="Traveling" th:selected="*{hobby.contains('Traveling')}">Traveling</option>
</select>
</div>
<div class="row">
<button type="submit" th:text="#{save-button-caption}">Save</button>
<a href="list.html" th:href="@{/}"><button type="button" th:text="#{cancel-button-caption}">Cancel</button></a>
</div>
</form>
</body>
</html>
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
.persons {
border: 1px solid steelblue;
width: 300px;
border-collapse: collapse;
}
.persons tr td, th {
padding: 5px;
border: 1px solid steelblue;
}
.persons td:last-child, td:first-child {
width: 50px;
}
h3 {
background-image: url("../static/listmark.png");
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
</style>
<style type="text/css" th:inline="text">
[[h3]] {
background-image: url([[@{/listmark.png}]]);
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
</style>
</head>
<body>
<h3 th:text="#{lang-switcher-header}">Select language</h3>
<div>
<div><a href="#" th:href="@{/(lang=en)}" th:text="#{en-lang-switch-button-caption}">Language - EN</a></div>
<div><a href="#" th:href="@{/(lang=ru)}" th:text="#{ru-lang-switch-button-caption}">Language - RU</a></div>
</div>
<h3 th:text="#{persons-table-header}">Persons:</h3>
<table class="persons">
<thead>
<tr>
<th th:text="#{person-field-id}">ID</th>
<th th:text="#{person-field-name}">Name</th>
<th th:text="#{person-field-hobby}">Hobby</th>
<th th:text="#{persons-table-column-action}">Action</th>
</tr>
</thead>
<tbody>
<tr th:each="person : ${persons}">
<td th:text="${person.id}">1</td>
<td th:text="${person.name}">John Doe</td>
<td th:text="${person.hobbyAsString()}">Hobby1, hobby2</td>
<td>
<a th:href="@{/edit(id=${person.id})}" href="edit.html" th:text="#{edit-button-caption}">Edit</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,69 @@
package ru.otus.spring.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import ru.otus.spring.domain.Person;
import ru.otus.spring.dto.PersonDto;
import ru.otus.spring.repostory.PersonRepository;
import java.util.List;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
@WebMvcTest(PersonController.class)
class PersonControllerTest {
@Autowired
private MockMvc mvc;
@MockitoBean
private PersonRepository personRepository;
private List<Person> persons = List.of(new Person(1L, "Vasya", List.of()),
new Person(2L, "Dima", List.of()));
@Test
void shouldRenderListPageWithCorrectViewAndModelAttributes() throws Exception {
when(personRepository.findAll()).thenReturn(persons);
List<PersonDto> expectedPersons = persons.stream()
.map(PersonDto::fromDomainObject).toList();
mvc.perform(get("/"))
.andExpect(view().name("list"))
.andExpect(model().attribute("persons", expectedPersons));
}
@Test
void shouldRenderEditPageWithCorrectViewAndModelAttributes() throws Exception {
when(personRepository.findById(1L)).thenReturn(Optional.of(persons.get(0)));
PersonDto expectedPerson = PersonDto.fromDomainObject(persons.get(0));
mvc.perform(get("/edit").param("id", "1"))
.andExpect(view().name("edit"))
.andExpect(model().attribute("person", expectedPerson));
}
@Test
void shouldRenderErrorPageWhenPersonNotFound() throws Exception {
when(personRepository.findById(1L)).thenThrow(new NotFoundException());
mvc.perform(get("/edit").param("id", "1"))
.andExpect(view().name("customError"));
}
@Test
void shouldSavePersonAndRedirectToContextPath() throws Exception {
when(personRepository.findById(1L)).thenReturn(Optional.of(persons.get(0)));
mvc.perform(post("/edit").param("id", "3").param("name", "Olya"))
.andExpect(view().name("redirect:/"));
verify(personRepository, times(1)).save(any(Person.class));
}
}
@@ -0,0 +1,24 @@
target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
@@ -0,0 +1,75 @@
<?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>ru.otus</groupId>
<artifactId>spring-mvc-view-exercise</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<h2.version>2.2.220</h2.version>
<snakeyaml.version>2.0</snakeyaml.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,14 @@
package ru.otus.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
System.out.printf("Чтобы проверить себя открывай: %n%s%n%s%n",
"http://localhost:8080", "http://localhost:8080/edit?id=1");
}
}
@@ -0,0 +1,7 @@
package ru.otus.spring.controller;
class NotFoundException extends RuntimeException{
NotFoundException() {
}
}
@@ -0,0 +1,30 @@
package ru.otus.spring.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.otus.spring.domain.Person;
import ru.otus.spring.repostory.PersonRepository;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class PersonController {
private final PersonRepository repository;
@GetMapping("/")
public String listPage(Model model) {
List<Person> persons = repository.findAll();
model.addAttribute("persons", persons);
return "list";
}
@GetMapping("/edit")
public String editPage(@RequestParam("id") long id, Model model) {
return null;
}
}
@@ -0,0 +1,22 @@
package ru.otus.spring.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}
@@ -0,0 +1,11 @@
package ru.otus.spring.repostory;
import org.springframework.data.repository.CrudRepository;
import ru.otus.spring.domain.Person;
import java.util.List;
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAll();
}
@@ -0,0 +1,19 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
sql:
init:
mode: always
jpa:
open-in-view: false
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
ROOT: ERROR
@@ -0,0 +1,2 @@
insert into person (id, name) values (1, 'Pushkin');
insert into person (id, name) values (2, 'Lermontov');
@@ -0,0 +1,4 @@
create table person (
id integer generated by default as identity,
name varchar(255), primary key (id)
);
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Edit person</title>
<style type="text/css">
body {
padding: 50px;
}
label {
display: inline-block;
width: 100px;
}
input:read-only {
background: lightgray;
}
.row {
margin-top: 10px;
}
</style>
</head>
<body>
<!-- Person edition -->
<form id="edit-form" action="edit.html">
<h3>Person Info:</h3>
<div class="row">
<label for="id-input">ID:</label>
<input id="id-input" type="text" readonly="readonly" value="1"/>
</div>
<div class="row">
<label for="person-name-input">Name:</label>
<input id="person-name-input" name="name" type="text" value="John Doe"/>
</div>
<div class="row">
<button type="submit">Save</button>
<a href="list.html"><button type="button">Cancel</button></a>
</div>
</form>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
.persons {
border: 1px solid steelblue;
width: 300px;
border-collapse: collapse;
}
.persons tr td, th {
padding: 5px;
border: 1px solid steelblue;
}
.persons td:last-child, td:first-child {
width: 50px;
}
</style>
</head>
<body>
<h3>Persons:</h3>
<table class="persons">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>John Doe</td>
<td>
<a href="edit.html">Edit</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,24 @@
target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
@@ -0,0 +1,75 @@
<?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>ru.otus</groupId>
<artifactId>spring-mvc-view-solution1</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<h2.version>2.2.220</h2.version>
<snakeyaml.version>2.0</snakeyaml.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,13 @@
package ru.otus.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//http://localhost:8080/edit?id=1
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
@@ -0,0 +1,7 @@
package ru.otus.spring.controller;
class NotFoundException extends RuntimeException{
NotFoundException() {
}
}
@@ -0,0 +1,33 @@
package ru.otus.spring.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.otus.spring.domain.Person;
import ru.otus.spring.repostory.PersonRepository;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class PersonController {
private final PersonRepository repository;
@GetMapping("/")
public String listPage(Model model) {
List<Person> persons = repository.findAll();
model.addAttribute("persons", persons);
return "list";
}
@GetMapping("/edit")
public String editPage(@RequestParam("id") long id, Model model) {
Person person = repository.findById(id)
.orElseThrow(NotFoundException::new);
model.addAttribute("person", person);
return "edit";
}
}
@@ -0,0 +1,22 @@
package ru.otus.spring.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}
@@ -0,0 +1,11 @@
package ru.otus.spring.repostory;
import org.springframework.data.repository.CrudRepository;
import ru.otus.spring.domain.Person;
import java.util.List;
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAll();
}
@@ -0,0 +1,19 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
sql:
init:
mode: always
jpa:
open-in-view: false
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
ROOT: ERROR
@@ -0,0 +1,2 @@
insert into person (id, name) values (1, 'Pushkin');
insert into person (id, name) values (2, 'Lermontov');
@@ -0,0 +1,4 @@
create table person (
id integer generated by default as identity,
name varchar(255), primary key (id)
);
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Edit person</title>
<style type="text/css">
body {
padding: 50px;
}
label {
display: inline-block;
width: 100px;
}
input:read-only {
background: lightgray;
}
.row {
margin-top: 10px;
}
</style>
</head>
<body>
<!-- Person edition -->
<form id="edit-form" action="edit.html">
<h3>Person Info:</h3>
<div class="row">
<label for="id-input">ID:</label>
<input id="id-input" type="text" readonly="readonly" value="1" th:value="${person.id}"/>
</div>
<div class="row">
<label for="person-name-input">Name:</label>
<input id="person-name-input" name="name" type="text" value="John Doe" th:value="${person.name}"/>
</div>
<div class="row">
<button type="submit">Save</button>
<a href="list.html"><button type="button">Cancel</button></a>
</div>
</form>
</body>
</html>
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
.persons {
border: 1px solid steelblue;
width: 300px;
border-collapse: collapse;
}
.persons tr td, th {
padding: 5px;
border: 1px solid steelblue;
}
.persons td:last-child, td:first-child {
width: 50px;
}
</style>
</head>
<body>
<h3>Persons:</h3>
<table class="persons">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>John Doe</td>
<td>
<a href="edit.html">Edit</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,24 @@
target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
@@ -0,0 +1,75 @@
<?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>ru.otus</groupId>
<artifactId>spring-mvc-view-solution2</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<h2.version>2.2.220</h2.version>
<snakeyaml.version>2.0</snakeyaml.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,13 @@
package ru.otus.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//http://localhost:8080/edit?id=1
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
@@ -0,0 +1,7 @@
package ru.otus.spring.controller;
class NotFoundException extends RuntimeException{
NotFoundException() {
}
}
@@ -0,0 +1,32 @@
package ru.otus.spring.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.otus.spring.domain.Person;
import ru.otus.spring.repostory.PersonRepository;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class PersonController {
private final PersonRepository repository;
@GetMapping("/")
public String listPage(Model model) {
List<Person> persons = repository.findAll();
model.addAttribute("persons", persons);
return "list";
}
@GetMapping("/edit")
public String editPage(@RequestParam("id") long id, Model model) {
Person person = repository.findById(id).orElseThrow(NotFoundException::new);
model.addAttribute("person", person);
return "edit";
}
}
@@ -0,0 +1,22 @@
package ru.otus.spring.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}
@@ -0,0 +1,11 @@
package ru.otus.spring.repostory;
import org.springframework.data.repository.CrudRepository;
import ru.otus.spring.domain.Person;
import java.util.List;
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAll();
}
@@ -0,0 +1,19 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
sql:
init:
mode: always
jpa:
open-in-view: false
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
ROOT: ERROR
@@ -0,0 +1,2 @@
insert into person (id, name) values (1, 'Pushkin');
insert into person (id, name) values (2, 'Lermontov');
@@ -0,0 +1,4 @@
create table person (
id integer generated by default as identity,
name varchar(255), primary key (id)
);
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Edit person</title>
<style type="text/css">
body {
padding: 50px;
}
label {
display: inline-block;
width: 100px;
}
input:read-only {
background: lightgray;
}
.row {
margin-top: 10px;
}
</style>
</head>
<body>
<!-- Person edition -->
<form id="edit-form" action="edit.html" th:object="${person}">
<h3>Person Info:</h3>
<div class="row">
<label for="id-input">ID:</label>
<input id="id-input" type="text" readonly="readonly" value="1" th:value="*{id}"/>
</div>
<div class="row">
<label for="person-name-input">Name:</label>
<input id="person-name-input" name="name" type="text" value="John Doe" th:value="*{name}"/>
</div>
<div class="row">
<button type="submit">Save</button>
<a href="list.html"><button type="button">Cancel</button></a>
</div>
</form>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
.persons {
border: 1px solid steelblue;
width: 300px;
border-collapse: collapse;
}
.persons tr td, th {
padding: 5px;
border: 1px solid steelblue;
}
.persons td:last-child, td:first-child {
width: 50px;
}
</style>
</head>
<body>
<h3>Persons:</h3>
<table class="persons">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>John Doe</td>
<td>
<a href="edit.html">Edit</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,24 @@
target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
@@ -0,0 +1,75 @@
<?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>ru.otus</groupId>
<artifactId>spring-mvc-view-solution3</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<h2.version>2.2.220</h2.version>
<snakeyaml.version>2.0</snakeyaml.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,14 @@
package ru.otus.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//http://localhost:8080
//http://localhost:8080/edit?id=1
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
@@ -0,0 +1,7 @@
package ru.otus.spring.controller;
class NotFoundException extends RuntimeException{
NotFoundException() {
}
}
@@ -0,0 +1,32 @@
package ru.otus.spring.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.otus.spring.domain.Person;
import ru.otus.spring.repostory.PersonRepository;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class PersonController {
private final PersonRepository repository;
@GetMapping("/")
public String listPage(Model model) {
List<Person> persons = repository.findAll();
model.addAttribute("persons", persons);
return "list";
}
@GetMapping("/edit")
public String editPage(@RequestParam("id") long id, Model model) {
Person person = repository.findById(id).orElseThrow(NotFoundException::new);
model.addAttribute("person", person);
return "edit";
}
}
@@ -0,0 +1,22 @@
package ru.otus.spring.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}
@@ -0,0 +1,11 @@
package ru.otus.spring.repostory;
import org.springframework.data.repository.CrudRepository;
import ru.otus.spring.domain.Person;
import java.util.List;
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAll();
}
@@ -0,0 +1,19 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
sql:
init:
mode: always
jpa:
open-in-view: false
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
ROOT: ERROR
@@ -0,0 +1,2 @@
insert into person (id, name) values (1, 'Pushkin');
insert into person (id, name) values (2, 'Lermontov');
@@ -0,0 +1,4 @@
create table person (
id integer generated by default as identity,
name varchar(255), primary key (id)
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Edit person</title>
<style type="text/css">
body {
padding: 50px;
}
label {
display: inline-block;
width: 100px;
}
input:read-only {
background: lightgray;
}
.row {
margin-top: 10px;
}
</style>
</head>
<body>
<!-- Person edition -->
<form id="edit-form" action="edit.html" th:object="${person}">
<h3>Person Info:</h3>
<div class="row">
<label for="id-input">ID:</label>
<input id="id-input" type="text" readonly="readonly" value="1" th:value="*{id}"/>
</div>
<div class="row">
<label for="person-name-input">Name:</label>
<input id="person-name-input" name="name" type="text" value="John Doe" th:value="*{name}"/>
</div>
<div class="row">
<button type="submit">Save</button>
<a href="list.html"><button type="button">Cancel</button></a>
</div>
</form>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
.persons {
border: 1px solid steelblue;
width: 300px;
border-collapse: collapse;
}
.persons tr td, th {
padding: 5px;
border: 1px solid steelblue;
}
.persons td:last-child, td:first-child {
width: 50px;
}
</style>
</head>
<body>
<h3>Persons:</h3>
<table class="persons">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="person : ${persons}" th:object = "${person}">
<td th:text="*{id}">1</td>
<td th:text="*{name}">John Doe</td>
<td>
<a href="edit.html">Edit</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,24 @@
target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
@@ -0,0 +1,75 @@
<?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>ru.otus</groupId>
<artifactId>spring-mvc-view-solution4</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<h2.version>2.2.220</h2.version>
<snakeyaml.version>2.0</snakeyaml.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,15 @@
package ru.otus.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//http://localhost:8080
//http://localhost:8080/edit?id=1
//http://localhost:8080/edit?id=111
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
@@ -0,0 +1,18 @@
package ru.otus.spring.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@RequiredArgsConstructor
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ModelAndView handeNotFoundException(NotFoundException ex) {
return new ModelAndView("customError",
"errorText", "Person not found");
}
}
@@ -0,0 +1,7 @@
package ru.otus.spring.controller;
public class NotFoundException extends RuntimeException{
NotFoundException() {
}
}
@@ -0,0 +1,39 @@
package ru.otus.spring.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.otus.spring.domain.Person;
import ru.otus.spring.repostory.PersonRepository;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class PersonController {
private final PersonRepository repository;
@GetMapping("/")
public String listPage(Model model) {
List<Person> persons = repository.findAll();
model.addAttribute("persons", persons);
return "list";
}
@GetMapping("/edit")
public String editPage(@RequestParam("id") long id, Model model) {
Person person = repository.findById(id).orElseThrow(NotFoundException::new);
model.addAttribute("person", person);
return "edit";
}
@PostMapping("/edit")
public String savePerson(Person person) {
repository.save(person);
return "redirect:/";
}
}
@@ -0,0 +1,22 @@
package ru.otus.spring.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
}
@@ -0,0 +1,11 @@
package ru.otus.spring.repostory;
import org.springframework.data.repository.CrudRepository;
import ru.otus.spring.domain.Person;
import java.util.List;
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAll();
}
@@ -0,0 +1,19 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
sql:
init:
mode: always
jpa:
open-in-view: false
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
ROOT: ERROR
@@ -0,0 +1,2 @@
insert into person (id, name) values (1, 'Pushkin');
insert into person (id, name) values (2, 'Lermontov');
@@ -0,0 +1,4 @@
create table person (
id integer generated by default as identity,
name varchar(255), primary key (id)
);
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
h3 {
background-image: url("../static/listmark.png");
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
</style>
<style type="text/css" th:inline="text">
[[h3]] {
background-image: url([[@{/listmark.png}]]);
background-repeat: no-repeat;
padding: 2px;
padding-left: 30px;
}
</style>
</head>
<body>
<h3>Error:</h3>
<span th:text="${errorText}">Error text</span>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Edit person</title>
<style type="text/css">
body {
padding: 50px;
}
label {
display: inline-block;
width: 100px;
}
input:read-only {
background: lightgray;
}
.row {
margin-top: 10px;
}
</style>
</head>
<body>
<!-- Person edition -->
<form id="edit-form" action="edit.html" th:method="post" th:action="@{/edit(id=${person.id})}"
th:object="${person}">
<h3>Person Info:</h3>
<div class="row">
<label for="id-input">ID:</label>
<input id="id-input" type="text" readonly="readonly" value="1" th:value="*{id}"/>
</div>
<div class="row">
<label for="person-name-input">Name:</label>
<input id="person-name-input" name="name" type="text" value="John Doe" th:value="*{name}"/>
</div>
<div class="row">
<button type="submit">Save</button>
<a href="list.html" th:href="@{/}"><button type="button">Cancel</button></a>
</div>
</form>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>List of all persons</title>
<style type="text/css">
body {
padding: 50px;
}
.persons {
border: 1px solid steelblue;
width: 300px;
border-collapse: collapse;
}
.persons tr td, th {
padding: 5px;
border: 1px solid steelblue;
}
.persons td:last-child, td:first-child {
width: 50px;
}
</style>
</head>
<body>
<h3>Persons:</h3>
<table class="persons">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="person : ${persons}" th:object="${person}">
<td th:text="*{id}">1</td>
<td th:text="*{name}">John Doe</td>
<td>
<a href="edit.html" th:href="@{/edit(id=*{id})}">Edit</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>