├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── ru │ └── alishev │ └── springcourse │ ├── config │ ├── MySpringMvcDispatcherSerlvetIntitializer.java │ └── SpringConfig.java │ ├── controllers │ ├── BooksController.java │ └── PeopleController.java │ ├── dao │ ├── BookDAO.java │ └── PersonDAO.java │ ├── models │ ├── Book.java │ └── Person.java │ ├── repositories │ ├── BooksRepository.java │ └── PeopleRepository.java │ ├── services │ ├── BooksService.java │ └── PeopleService.java │ └── util │ └── PersonValidator.java ├── resources └── hibernate.properties ├── sql └── project1_db.sql └── webapp └── WEB-INF └── views ├── books ├── edit.html ├── index.html ├── new.html ├── search.html └── show.html └── people ├── edit.html ├── index.html ├── new.html └── show.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Моя реализация Project 2 2 | 3 | SQL код, который нужен для того, чтобы вручную назначить Timestamp: 4 | ``` 5 | UPDATE Book SET taken_at='2021-05-07 08:00:00' WHERE id=1; 6 | ``` 7 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | ru.alishev.springcourse 8 | spring-project2 9 | 1.0-SNAPSHOT 10 | war 11 | 12 | spring-project2 Maven Webapp 13 | 14 | http://www.example.com 15 | 16 | 17 | UTF-8 18 | 1.7 19 | 1.7 20 | 21 | 5.3.5 22 | 5.4.28.Final 23 | 24 | 25 | 26 | 27 | junit 28 | junit 29 | 4.11 30 | test 31 | 32 | 33 | 34 | org.springframework 35 | spring-core 36 | ${spring.version} 37 | 38 | 39 | 40 | org.springframework 41 | spring-context 42 | ${spring.version} 43 | 44 | 45 | 46 | org.springframework 47 | spring-web 48 | ${spring.version} 49 | 50 | 51 | 52 | org.springframework 53 | spring-webmvc 54 | ${spring.version} 55 | 56 | 57 | 58 | org.springframework 59 | spring-jdbc 60 | ${spring.version} 61 | 62 | 63 | 64 | org.thymeleaf 65 | thymeleaf-spring5 66 | 3.0.11.RELEASE 67 | 68 | 69 | 70 | javax.servlet 71 | javax.servlet-api 72 | 4.0.1 73 | provided 74 | 75 | 76 | 77 | org.hibernate.validator 78 | hibernate-validator 79 | 6.1.6.Final 80 | 81 | 82 | 83 | 84 | org.postgresql 85 | postgresql 86 | 42.2.18 87 | 88 | 89 | 90 | 91 | 92 | 93 | org.hibernate 94 | hibernate-core 95 | ${hibernate.version} 96 | 97 | 98 | 99 | 100 | org.springframework 101 | spring-orm 102 | ${spring.version} 103 | 104 | 105 | 106 | 107 | org.springframework.data 108 | spring-data-jpa 109 | 2.4.7 110 | 111 | 112 | 113 | 114 | spring-project2 115 | 116 | 117 | 118 | maven-clean-plugin 119 | 3.1.0 120 | 121 | 122 | 123 | maven-resources-plugin 124 | 3.0.2 125 | 126 | 127 | maven-compiler-plugin 128 | 3.8.0 129 | 130 | 131 | maven-surefire-plugin 132 | 2.22.1 133 | 134 | 135 | maven-war-plugin 136 | 3.2.2 137 | 138 | 139 | maven-install-plugin 140 | 2.5.2 141 | 142 | 143 | maven-deploy-plugin 144 | 2.8.2 145 | 146 | 147 | 148 | 149 | 150 | org.apache.maven.plugins 151 | maven-compiler-plugin 152 | 153 | 8 154 | 8 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/config/MySpringMvcDispatcherSerlvetIntitializer.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.config; 2 | 3 | import org.springframework.web.filter.CharacterEncodingFilter; 4 | import org.springframework.web.filter.HiddenHttpMethodFilter; 5 | import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; 6 | 7 | import javax.servlet.DispatcherType; 8 | import javax.servlet.FilterRegistration; 9 | import javax.servlet.ServletContext; 10 | import javax.servlet.ServletException; 11 | import java.util.EnumSet; 12 | 13 | /** 14 | * @author Neil Alishev 15 | */ 16 | public class MySpringMvcDispatcherSerlvetIntitializer extends AbstractAnnotationConfigDispatcherServletInitializer { 17 | @Override 18 | protected Class[] getRootConfigClasses() { 19 | return null; 20 | } 21 | 22 | @Override 23 | protected Class[] getServletConfigClasses() { 24 | return new Class[]{SpringConfig.class}; 25 | } 26 | 27 | @Override 28 | protected String[] getServletMappings() { 29 | return new String[]{"/"}; 30 | } 31 | 32 | @Override 33 | public void onStartup(ServletContext aServletContext) throws ServletException { 34 | super.onStartup(aServletContext); 35 | registerCharacterEncodingFilter(aServletContext); 36 | registerHiddenFieldFilter(aServletContext); 37 | } 38 | 39 | private void registerHiddenFieldFilter(ServletContext aContext) { 40 | aContext.addFilter("hiddenHttpMethodFilter", 41 | new HiddenHttpMethodFilter()).addMappingForUrlPatterns(null, true, "/*"); 42 | } 43 | 44 | private void registerCharacterEncodingFilter(ServletContext aContext) { 45 | EnumSet dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD); 46 | 47 | CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); 48 | characterEncodingFilter.setEncoding("UTF-8"); 49 | characterEncodingFilter.setForceEncoding(true); 50 | 51 | FilterRegistration.Dynamic characterEncoding = aContext.addFilter("characterEncoding", characterEncodingFilter); 52 | characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, "/*"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/config/SpringConfig.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.PropertySource; 9 | import org.springframework.core.env.Environment; 10 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 11 | import org.springframework.jdbc.datasource.DriverManagerDataSource; 12 | import org.springframework.orm.jpa.JpaTransactionManager; 13 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 14 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 15 | import org.springframework.transaction.PlatformTransactionManager; 16 | import org.springframework.transaction.annotation.EnableTransactionManagement; 17 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 18 | import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; 19 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 20 | import org.thymeleaf.spring5.SpringTemplateEngine; 21 | import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; 22 | import org.thymeleaf.spring5.view.ThymeleafViewResolver; 23 | 24 | import javax.sql.DataSource; 25 | import java.util.Properties; 26 | 27 | /** 28 | * @author Neil Alishev 29 | */ 30 | @Configuration 31 | @ComponentScan("ru.alishev.springcourse") 32 | @PropertySource("classpath:hibernate.properties") 33 | @EnableTransactionManagement 34 | @EnableJpaRepositories("ru.alishev.springcourse.repositories") 35 | @EnableWebMvc 36 | public class SpringConfig implements WebMvcConfigurer { 37 | 38 | private final ApplicationContext applicationContext; 39 | 40 | private final Environment env; 41 | 42 | @Autowired 43 | public SpringConfig(ApplicationContext applicationContext, Environment env) { 44 | this.applicationContext = applicationContext; 45 | this.env = env; 46 | } 47 | 48 | @Bean 49 | public SpringResourceTemplateResolver templateResolver() { 50 | SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); 51 | templateResolver.setApplicationContext(applicationContext); 52 | templateResolver.setPrefix("/WEB-INF/views/"); 53 | templateResolver.setSuffix(".html"); 54 | templateResolver.setCharacterEncoding("UTF-8"); 55 | return templateResolver; 56 | } 57 | 58 | @Bean 59 | public SpringTemplateEngine templateEngine() { 60 | SpringTemplateEngine templateEngine = new SpringTemplateEngine(); 61 | templateEngine.setTemplateResolver(templateResolver()); 62 | templateEngine.setEnableSpringELCompiler(true); 63 | return templateEngine; 64 | } 65 | 66 | @Override 67 | public void configureViewResolvers(ViewResolverRegistry registry) { 68 | ThymeleafViewResolver resolver = new ThymeleafViewResolver(); 69 | resolver.setTemplateEngine(templateEngine()); 70 | resolver.setCharacterEncoding("UTF-8"); 71 | 72 | registry.viewResolver(resolver); 73 | } 74 | 75 | @Bean 76 | public DataSource dataSource() { 77 | DriverManagerDataSource dataSource = new DriverManagerDataSource(); 78 | 79 | dataSource.setDriverClassName(env.getRequiredProperty("hibernate.driver_class")); 80 | dataSource.setUrl(env.getRequiredProperty("hibernate.connection.url")); 81 | dataSource.setUsername(env.getRequiredProperty("hibernate.connection.username")); 82 | dataSource.setPassword(env.getRequiredProperty("hibernate.connection.password")); 83 | 84 | return dataSource; 85 | } 86 | 87 | private Properties hibernateProperties() { 88 | Properties properties = new Properties(); 89 | properties.put("hibernate.dialect", env.getRequiredProperty("hibernate.dialect")); 90 | properties.put("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql")); 91 | 92 | return properties; 93 | } 94 | 95 | @Bean 96 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 97 | final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); 98 | em.setDataSource(dataSource()); 99 | em.setPackagesToScan("ru.alishev.springcourse.models"); 100 | 101 | final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); 102 | em.setJpaVendorAdapter(vendorAdapter); 103 | em.setJpaProperties(hibernateProperties()); 104 | 105 | return em; 106 | } 107 | 108 | @Bean 109 | public PlatformTransactionManager transactionManager() { 110 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 111 | transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); 112 | 113 | return transactionManager; 114 | } 115 | } -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/controllers/BooksController.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.validation.BindingResult; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.alishev.springcourse.models.Book; 9 | import ru.alishev.springcourse.models.Person; 10 | import ru.alishev.springcourse.services.BooksService; 11 | import ru.alishev.springcourse.services.PeopleService; 12 | 13 | import javax.validation.Valid; 14 | 15 | /** 16 | * @author Neil Alishev 17 | */ 18 | @Controller 19 | @RequestMapping("/books") 20 | public class BooksController { 21 | 22 | private final BooksService booksService; 23 | private final PeopleService peopleService; 24 | 25 | @Autowired 26 | public BooksController(BooksService booksService, PeopleService peopleService) { 27 | this.booksService = booksService; 28 | this.peopleService = peopleService; 29 | } 30 | 31 | @GetMapping() 32 | public String index(Model model, @RequestParam(value = "page", required = false) Integer page, 33 | @RequestParam(value = "books_per_page", required = false) Integer booksPerPage, 34 | @RequestParam(value = "sort_by_year", required = false) boolean sortByYear) { 35 | 36 | if (page == null || booksPerPage == null) 37 | model.addAttribute("books", booksService.findAll(sortByYear)); // выдача всех книг 38 | else 39 | model.addAttribute("books", booksService.findWithPagination(page, booksPerPage, sortByYear)); 40 | 41 | return "books/index"; 42 | } 43 | 44 | @GetMapping("/{id}") 45 | public String show(@PathVariable("id") int id, Model model, @ModelAttribute("person") Person person) { 46 | model.addAttribute("book", booksService.findOne(id)); 47 | 48 | Person bookOwner = booksService.getBookOwner(id); 49 | 50 | if (bookOwner != null) 51 | model.addAttribute("owner", bookOwner); 52 | else 53 | model.addAttribute("people", peopleService.findAll()); 54 | 55 | return "books/show"; 56 | } 57 | 58 | @GetMapping("/new") 59 | public String newBook(@ModelAttribute("book") Book Book) { 60 | return "books/new"; 61 | } 62 | 63 | @PostMapping() 64 | public String create(@ModelAttribute("book") @Valid Book Book, 65 | BindingResult bindingResult) { 66 | if (bindingResult.hasErrors()) 67 | return "books/new"; 68 | 69 | booksService.save(Book); 70 | return "redirect:/books"; 71 | } 72 | 73 | @GetMapping("/{id}/edit") 74 | public String edit(Model model, @PathVariable("id") int id) { 75 | model.addAttribute("book", booksService.findOne(id)); 76 | return "books/edit"; 77 | } 78 | 79 | @PatchMapping("/{id}") 80 | public String update(@ModelAttribute("book") @Valid Book book, BindingResult bindingResult, 81 | @PathVariable("id") int id) { 82 | if (bindingResult.hasErrors()) 83 | return "books/edit"; 84 | 85 | booksService.update(id, book); 86 | return "redirect:/books"; 87 | } 88 | 89 | @DeleteMapping("/{id}") 90 | public String delete(@PathVariable("id") int id) { 91 | booksService.delete(id); 92 | return "redirect:/books"; 93 | } 94 | 95 | @PatchMapping("/{id}/release") 96 | public String release(@PathVariable("id") int id) { 97 | booksService.release(id); 98 | return "redirect:/books/" + id; 99 | } 100 | 101 | @PatchMapping("/{id}/assign") 102 | public String assign(@PathVariable("id") int id, @ModelAttribute("person") Person selectedPerson) { 103 | // У selectedPerson назначено только поле id, остальные поля - null 104 | booksService.assign(id, selectedPerson); 105 | return "redirect:/books/" + id; 106 | } 107 | 108 | @GetMapping("/search") 109 | public String searchPage() { 110 | return "books/search"; 111 | } 112 | 113 | @PostMapping("/search") 114 | public String makeSearch(Model model, @RequestParam("query") String query) { 115 | model.addAttribute("books", booksService.searchByTitle(query)); 116 | return "books/search"; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/controllers/PeopleController.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.validation.BindingResult; 7 | import org.springframework.web.bind.annotation.*; 8 | import ru.alishev.springcourse.models.Person; 9 | import ru.alishev.springcourse.services.PeopleService; 10 | import ru.alishev.springcourse.util.PersonValidator; 11 | 12 | import javax.validation.Valid; 13 | 14 | /** 15 | * @author Neil Alishev 16 | */ 17 | @Controller 18 | @RequestMapping("/people") 19 | public class PeopleController { 20 | 21 | private final PeopleService peopleService; 22 | private final PersonValidator personValidator; 23 | 24 | @Autowired 25 | public PeopleController(PeopleService peopleService, PersonValidator personValidator) { 26 | this.peopleService = peopleService; 27 | this.personValidator = personValidator; 28 | } 29 | 30 | @GetMapping() 31 | public String index(Model model) { 32 | model.addAttribute("people", peopleService.findAll()); 33 | return "people/index"; 34 | } 35 | 36 | @GetMapping("/{id}") 37 | public String show(@PathVariable("id") int id, Model model) { 38 | model.addAttribute("person", peopleService.findOne(id)); 39 | model.addAttribute("books", peopleService.getBooksByPersonId(id)); 40 | 41 | return "people/show"; 42 | } 43 | 44 | @GetMapping("/new") 45 | public String newPerson(@ModelAttribute("person") Person person) { 46 | return "people/new"; 47 | } 48 | 49 | @PostMapping() 50 | public String create(@ModelAttribute("person") @Valid Person person, 51 | BindingResult bindingResult) { 52 | personValidator.validate(person, bindingResult); 53 | 54 | if (bindingResult.hasErrors()) 55 | return "people/new"; 56 | 57 | peopleService.save(person); 58 | return "redirect:/people"; 59 | } 60 | 61 | @GetMapping("/{id}/edit") 62 | public String edit(Model model, @PathVariable("id") int id) { 63 | model.addAttribute("person", peopleService.findOne(id)); 64 | return "people/edit"; 65 | } 66 | 67 | @PatchMapping("/{id}") 68 | public String update(@ModelAttribute("person") @Valid Person person, BindingResult bindingResult, 69 | @PathVariable("id") int id) { 70 | if (bindingResult.hasErrors()) 71 | return "people/edit"; 72 | 73 | peopleService.update(id, person); 74 | return "redirect:/people"; 75 | } 76 | 77 | @DeleteMapping("/{id}") 78 | public String delete(@PathVariable("id") int id) { 79 | peopleService.delete(id); 80 | return "redirect:/people"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/dao/BookDAO.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.dao; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * @author Neil Alishev 7 | */ 8 | @Component 9 | public class BookDAO { 10 | // Здесь будут лежать специфические запросы к БД (с помощью SQL) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/dao/PersonDAO.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.dao; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * @author Neil Alishev 7 | */ 8 | @Component 9 | public class PersonDAO { 10 | // Здесь будут лежать специфические запросы к БД (с помощью SQL) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/models/Book.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.models; 2 | 3 | import javax.persistence.*; 4 | import javax.validation.constraints.Min; 5 | import javax.validation.constraints.NotEmpty; 6 | import javax.validation.constraints.Size; 7 | import java.util.Date; 8 | 9 | /** 10 | * @author Neil Alishev 11 | */ 12 | @Entity 13 | @Table(name = "Book") 14 | public class Book { 15 | @Id 16 | @Column(name = "id") 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private int id; 19 | 20 | @NotEmpty(message = "Название книги не должно быть пустым") 21 | @Size(min = 2, max = 100, message = "Название книги должно быть от 2 до 100 символов длиной") 22 | @Column(name = "title") 23 | private String title; 24 | 25 | @NotEmpty(message = "Автор не должен быть пустым") 26 | @Size(min = 2, max = 100, message = "Имя автора должно быть от 2 до 100 символов длиной") 27 | @Column(name = "author") 28 | private String author; 29 | 30 | @Min(value = 1500, message = "Год должен быть больше, чем 1500") 31 | @Column(name = "year") 32 | private int year; 33 | 34 | @ManyToOne 35 | @JoinColumn(name = "person_id", referencedColumnName = "id") 36 | private Person owner; 37 | 38 | @Column(name = "taken_at") 39 | @Temporal(TemporalType.TIMESTAMP) 40 | private Date takenAt; 41 | 42 | @Transient 43 | private boolean expired; // Hibernate не будет замечать этого поля, что нам и нужно. По-умолчанию false. 44 | 45 | public Book() { 46 | 47 | } 48 | 49 | public Book(String title, String author, int year) { 50 | this.title = title; 51 | this.author = author; 52 | this.year = year; 53 | } 54 | 55 | public int getId() { 56 | return id; 57 | } 58 | 59 | public void setId(int id) { 60 | this.id = id; 61 | } 62 | 63 | public String getTitle() { 64 | return title; 65 | } 66 | 67 | public void setTitle(String title) { 68 | this.title = title; 69 | } 70 | 71 | public String getAuthor() { 72 | return author; 73 | } 74 | 75 | public void setAuthor(String author) { 76 | this.author = author; 77 | } 78 | 79 | public int getYear() { 80 | return year; 81 | } 82 | 83 | public void setYear(int year) { 84 | this.year = year; 85 | } 86 | 87 | public Person getOwner() { 88 | return owner; 89 | } 90 | 91 | public void setOwner(Person owner) { 92 | this.owner = owner; 93 | } 94 | 95 | public Date getTakenAt() { 96 | return takenAt; 97 | } 98 | 99 | public void setTakenAt(Date takenAt) { 100 | this.takenAt = takenAt; 101 | } 102 | 103 | public boolean isExpired() { 104 | return expired; 105 | } 106 | 107 | public void setExpired(boolean expired) { 108 | this.expired = expired; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/models/Person.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.models; 2 | 3 | import javax.persistence.*; 4 | import javax.validation.constraints.Min; 5 | import javax.validation.constraints.NotEmpty; 6 | import javax.validation.constraints.Size; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Neil Alishev 11 | */ 12 | @Entity 13 | @Table(name = "Person") 14 | public class Person { 15 | @Id 16 | @Column(name = "id") 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private int id; 19 | 20 | @NotEmpty(message = "Имя не должно быть пустым") 21 | @Size(min = 2, max = 100, message = "Имя должно быть от 2 до 100 символов длиной") 22 | @Column(name = "full_name") 23 | private String fullName; 24 | 25 | @Min(value = 1900, message = "Год рождения должен быть больше, чем 1900") 26 | @Column(name = "year_of_birth") 27 | private int yearOfBirth; 28 | 29 | @OneToMany(mappedBy = "owner") 30 | private List books; 31 | 32 | // Конструктор по умолчанию нужен для Spring 33 | public Person() { 34 | 35 | } 36 | 37 | public Person(String fullName, int yearOfBirth) { 38 | this.fullName = fullName; 39 | this.yearOfBirth = yearOfBirth; 40 | } 41 | 42 | public int getId() { 43 | return id; 44 | } 45 | 46 | public void setId(int id) { 47 | this.id = id; 48 | } 49 | 50 | public String getFullName() { 51 | return fullName; 52 | } 53 | 54 | public void setFullName(String fullName) { 55 | this.fullName = fullName; 56 | } 57 | 58 | public int getYearOfBirth() { 59 | return yearOfBirth; 60 | } 61 | 62 | public void setYearOfBirth(int yearOfBirth) { 63 | this.yearOfBirth = yearOfBirth; 64 | } 65 | 66 | public List getBooks() { 67 | return books; 68 | } 69 | 70 | public void setBooks(List books) { 71 | this.books = books; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/repositories/BooksRepository.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | import ru.alishev.springcourse.models.Book; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Neil Alishev 11 | */ 12 | @Repository 13 | public interface BooksRepository extends JpaRepository { 14 | List findByTitleStartingWith(String title); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/repositories/PeopleRepository.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | import ru.alishev.springcourse.models.Person; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * @author Neil Alishev 11 | */ 12 | @Repository 13 | public interface PeopleRepository extends JpaRepository { 14 | Optional findByFullName(String fullName); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/services/BooksService.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.services; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Sort; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | import ru.alishev.springcourse.models.Book; 9 | import ru.alishev.springcourse.models.Person; 10 | import ru.alishev.springcourse.repositories.BooksRepository; 11 | 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | /** 17 | * @author Neil Alishev 18 | */ 19 | @Service 20 | @Transactional(readOnly = true) 21 | public class BooksService { 22 | 23 | private final BooksRepository booksRepository; 24 | 25 | @Autowired 26 | public BooksService(BooksRepository booksRepository) { 27 | this.booksRepository = booksRepository; 28 | } 29 | 30 | public List findAll(boolean sortByYear) { 31 | if (sortByYear) 32 | return booksRepository.findAll(Sort.by("year")); 33 | else 34 | return booksRepository.findAll(); 35 | } 36 | 37 | public List findWithPagination(Integer page, Integer booksPerPage, boolean sortByYear) { 38 | if (sortByYear) 39 | return booksRepository.findAll(PageRequest.of(page, booksPerPage, Sort.by("year"))).getContent(); 40 | else 41 | return booksRepository.findAll(PageRequest.of(page, booksPerPage)).getContent(); 42 | } 43 | 44 | public Book findOne(int id) { 45 | Optional foundBook = booksRepository.findById(id); 46 | return foundBook.orElse(null); 47 | } 48 | 49 | public List searchByTitle(String query) { 50 | return booksRepository.findByTitleStartingWith(query); 51 | } 52 | 53 | @Transactional 54 | public void save(Book book) { 55 | booksRepository.save(book); 56 | } 57 | 58 | @Transactional 59 | public void update(int id, Book updatedBook) { 60 | Book bookToBeUpdated = booksRepository.findById(id).get(); 61 | 62 | // добавляем по сути новую книгу (которая не находится в Persistence context), поэтому нужен save() 63 | updatedBook.setId(id); 64 | updatedBook.setOwner(bookToBeUpdated.getOwner()); // чтобы не терялась связь при обновлении 65 | 66 | booksRepository.save(updatedBook); 67 | } 68 | 69 | @Transactional 70 | public void delete(int id) { 71 | booksRepository.deleteById(id); 72 | } 73 | 74 | // Returns null if book has no owner 75 | public Person getBookOwner(int id) { 76 | // Здесь Hibernate.initialize() не нужен, так как владелец (сторона One) загружается не лениво 77 | return booksRepository.findById(id).map(Book::getOwner).orElse(null); 78 | } 79 | 80 | // Освбождает книгу (этот метод вызывается, когда человек возвращает книгу в библиотеку) 81 | @Transactional 82 | public void release(int id) { 83 | booksRepository.findById(id).ifPresent( 84 | book -> { 85 | book.setOwner(null); 86 | book.setTakenAt(null); 87 | }); 88 | } 89 | 90 | // Назначает книгу человеку (этот метод вызывается, когда человек забирает книгу из библиотеки) 91 | @Transactional 92 | public void assign(int id, Person selectedPerson) { 93 | booksRepository.findById(id).ifPresent( 94 | book -> { 95 | book.setOwner(selectedPerson); 96 | book.setTakenAt(new Date()); // текущее время 97 | } 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/services/PeopleService.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.services; 2 | 3 | import org.hibernate.Hibernate; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import ru.alishev.springcourse.models.Book; 8 | import ru.alishev.springcourse.models.Person; 9 | import ru.alishev.springcourse.repositories.PeopleRepository; 10 | 11 | import java.util.Collections; 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | /** 17 | * @author Neil Alishev 18 | */ 19 | @Service 20 | @Transactional(readOnly = true) 21 | public class PeopleService { 22 | 23 | private final PeopleRepository peopleRepository; 24 | 25 | @Autowired 26 | public PeopleService(PeopleRepository peopleRepository) { 27 | this.peopleRepository = peopleRepository; 28 | } 29 | 30 | public List findAll() { 31 | return peopleRepository.findAll(); 32 | } 33 | 34 | public Person findOne(int id) { 35 | Optional foundPerson = peopleRepository.findById(id); 36 | return foundPerson.orElse(null); 37 | } 38 | 39 | @Transactional 40 | public void save(Person person) { 41 | peopleRepository.save(person); 42 | } 43 | 44 | @Transactional 45 | public void update(int id, Person updatedPerson) { 46 | updatedPerson.setId(id); 47 | peopleRepository.save(updatedPerson); 48 | } 49 | 50 | @Transactional 51 | public void delete(int id) { 52 | peopleRepository.deleteById(id); 53 | } 54 | 55 | public Optional getPersonByFullName(String fullName) { 56 | return peopleRepository.findByFullName(fullName); 57 | } 58 | 59 | public List getBooksByPersonId(int id) { 60 | Optional person = peopleRepository.findById(id); 61 | 62 | if (person.isPresent()) { 63 | Hibernate.initialize(person.get().getBooks()); 64 | // Мы внизу итерируемся по книгам, поэтому они точно будут загружены, но на всякий случай 65 | // не мешает всегда вызывать Hibernate.initialize() 66 | // (на случай, например, если код в дальнейшем поменяется и итерация по книгам удалится) 67 | 68 | // Проверка просроченности книг 69 | person.get().getBooks().forEach(book -> { 70 | long diffInMillies = Math.abs(book.getTakenAt().getTime() - new Date().getTime()); 71 | // 864000000 милисекунд = 10 суток 72 | if (diffInMillies > 864000000) 73 | book.setExpired(true); // книга просрочена 74 | }); 75 | 76 | return person.get().getBooks(); 77 | } 78 | else { 79 | return Collections.emptyList(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/ru/alishev/springcourse/util/PersonValidator.java: -------------------------------------------------------------------------------- 1 | package ru.alishev.springcourse.util; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.validation.Errors; 6 | import org.springframework.validation.Validator; 7 | import ru.alishev.springcourse.models.Person; 8 | import ru.alishev.springcourse.services.PeopleService; 9 | 10 | /** 11 | * @author Neil Alishev 12 | */ 13 | @Component 14 | public class PersonValidator implements Validator { 15 | 16 | private final PeopleService peopleService; 17 | 18 | @Autowired 19 | public PersonValidator(PeopleService peopleService) { 20 | this.peopleService = peopleService; 21 | } 22 | 23 | @Override 24 | public boolean supports(Class aClass) { 25 | return Person.class.equals(aClass); 26 | } 27 | 28 | @Override 29 | public void validate(Object o, Errors errors) { 30 | Person person = (Person) o; 31 | 32 | if (peopleService.getPersonByFullName(person.getFullName()).isPresent()) 33 | errors.rejectValue("fullName", "", "Человек с таким ФИО уже существует"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/hibernate.properties: -------------------------------------------------------------------------------- 1 | # Конфигурация источника данных (Data Source) 2 | hibernate.driver_class=org.postgresql.Driver 3 | hibernate.connection.url=jdbc:postgresql://localhost:5432/project1 4 | hibernate.connection.username=postgres 5 | hibernate.connection.password=postgres 6 | 7 | # Конфигурация самого Hibernate 8 | hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 9 | hibernate.show_sql=true -------------------------------------------------------------------------------- /src/main/sql/project1_db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Person ( 2 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 3 | full_name varchar(100) NOT NULL UNIQUE, 4 | year_of_birth int NOT NULL 5 | ); 6 | 7 | CREATE TABLE Book ( 8 | id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 9 | title varchar(100) NOT NULL, 10 | author varchar(100) NOT NULL, 11 | year int NOT NULL, 12 | person_id int REFERENCES Person(id) ON DELETE SET NULL 13 | ); -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/books/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Update book 6 | 7 | 8 | 9 |
10 | 11 | 12 |
Title Error
13 |
14 | 15 | 16 |
Author Error
17 |
18 | 19 | 20 |
Year Error
21 |
22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/books/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index 6 | 7 | 8 | 9 |
10 | book 12 |
13 | 14 |
15 |
16 | 17 | Добавить книгу 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/books/new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | New book 6 | 7 | 8 | 9 |
10 | 11 | 12 |
Title Error
13 |
14 | 15 | 16 |
Author Error
17 |
18 | 19 | 20 |
Year Error
21 |
22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/books/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index 6 | 7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | Книг не найдено 19 |
20 | 21 |
22 |

book

23 | 24 |
25 | Книга сейчас у: Person Name 26 |
27 | 28 |
29 | Книга свободна 30 |
31 | 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/books/show.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Show 7 | 8 | 9 |

VALUE

10 | 11 |
12 | Книга сейчас у: Person Name 13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | Эта книга свободна. Кому назначить ее? 21 |
22 | 23 | 27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/people/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Редактировать человека 6 | 7 | 8 | 9 |
10 | 11 | 12 |
Full Name Error
13 |
14 | 15 | 16 |
Year of birth Error
17 |
18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/people/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index 6 | 7 | 8 | 9 |
10 | user 12 |
13 | 14 |
15 |
16 | 17 | Добавить человека 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/people/new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | New person 7 | 8 | 9 | 10 |
11 | 12 | 13 |
Full Name Error
14 |
15 | 16 | 17 |
Year of birth Error
18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/people/show.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Show 7 | 8 | 9 |

VALUE

10 | 11 |
12 |

Человек пока не взял ни одной книги

13 |
14 |
15 | 16 |
17 |
18 | Книги: 19 |
20 | 21 | 22 | 28 | 29 |
23 | 25 | book 26 | 27 |
30 |
31 |
32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 | 42 | --------------------------------------------------------------------------------