# MAVEN + JAVA + SPRING BOOT + Hibernate ### Начало В файлик `pom.xml` прописываем следующее: ```xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.5</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>2.3.2</version> </dependency> </dependencies> ``` Суть простая: "парент" задаёт типо обобщение для пакетов зависимостей (в частности, номер версии - чтобы его каждому пакету руками не писать). Затем подключаем `spring-boot-starter-web` - это спринг бут со встроенным Томкатом Затем `spring-boot-starter-data-jpa` - эт чтоб с хиьером работать через JPA (подробностей не знаю, ту скорее всего лежит `JDBC` и т.п.). Далее `postgresql` - чтобы к постгресу коннетиться `lombok` - чтобы `@Data` превращалось в сеттеры-геттеры `modelmapper` - чтобы DTO'хи руками не мапить __:)__ Для реализации аутентификации надо ещё подключить пакет `spring-boot-starter-security` - потом пишем отдельный класс конфигурирования этого дела и получаем секурити. Чтоб собрать без `tomcat-embedded` надо его отдельно прописать в зависимости и добавить `provided`: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> ``` После чего меняем `<packaging>jar</packaging>` на `war` и получаем варник, который подсовываем стендэлон-томкату __(:__ ### Спринг-бут приложение Главный класс нашего приложения выглядит вот так: ```java @SpringBootApplication public class LsrServer { static public void main(String args[]) { SpringApplication.run(LsrServer.class, args); } } ``` Аннотация х.з. зачем нужна, но нужна, а в теле всё что мы делаем это скармливаем наш "главный" класс спринговому стартеру ### HTTP REST Обработчики HTTP-запросов (а-ка "контроллеры") пишутся так: ```java @RestController @RequestMapping("/api") public class ExampleController { @GetMapping("/test") public Map<String, String> getHelloWorld() { HashMap<String, String> result = new HashMap<>(); result.put("Hello", "World!"); return result; } } ``` Т.е. просто пишем класс и аннотацией `@RestController` говорим спрингу, что это, собственно, __REST__-контроллер. А аннотация `@RequestMapping` позволяем задать "базовый" путь для все обработчиков этого класса (чтоб каждому не дописывать один и тот же кусок) Внутри класса пишем публичные методы, которые что-нибудь возвращают или не возвращают ничего (`void`) - спринг сам это дело будет преобразовывать в джейсон (или ещё в чо-нить, во что вы его попросите - по-дефолту он юзает джейсон, с которым работает с помощью либы `jackson`, которую он сам подтягивает) Аннотация `@GetMapping` позволяет указать тип метода (в данном случае `GET`) и дополнительный путь (его можно и не указывать). Есть аналогичные аннотации для всех остальных HTTP-методов Данные из запроса передаются в запрос с помощью аннотаций `@RequestBody`, `@PathVariable` и `@RequestParam` следующим образом: ```java @PostMapping("/test/{id}") public void postTest(@RequestBody SomeDTO body, @RequestParam("some_param") String someParam, @PathVariable("id") int id) { // todo something } ``` Спринг сделает за нас всю грязную работу и тело запроса окажется в `body`, строка `helloworld` (из строки запроса типо такой: `localhost:/test/13?some_param=helloworld`) - в переменной `someParam`, а число `13` попадёт в переменную `id` __B-)__ (Если имя Java-переменной и параметра в запросе совпадают, то тогда у аннотации можно не писать скобки и имя параметра в ковычках внутри) У аннотации `@RequestParam` есть ещё поля `required` и `defaultValue`, которые нужны сами догадываетесь зачем __;-)__ Так же, в параметрах запроса можно юзать переменные (т.е. когда сам роут несёт смысловую нагрузку), с помощью аннотации `@PathVariable`, пример: ```java @GetMapping("/{id}/test") public void getTest(@PathParam("id") int id) { // todo something } ``` Суть проста: запрос вида `localhost:/api/13/test` попадёт в этот метод-обработчик и в переменной `id` окажется число 13 (`/api` в начале запроса это потому что в примере выше мы использовали `@RequestMapping("/api")` для всего нашего класса) ### Exceptions Чтобы ответить серверу что-то кроме `200 OK` нужно выбросить исключение. Готовых нет (я не смог найти), так что пишем свои: ```java @ResponseStatus(value = HttpStatus.BAD_REQUEST) public class BadRequestException extends RuntimeException { public BadRequestException(String message) { super(message); } } ``` Спринг отлавливает всё унаследованное от `RuntimeException` что мы забудем перехватить в контроллере/репозиории (или сами нарочно выбросим) и сам формирует из него ответ клиенту. Аннотация нужна чтобы задать код ответа -- без неё спринг будет пихать `500 Internal Server Error`. ### DTO Согласно концепции "Model-View-Controller" принято для хранения данных, для их обработки и для передачи использовать разные представления (т.е. разные "классы" в нашем случае), так что для апихи создают отдельный набор, в названия которого добавляют "DTO" (ранее в примере это уже было: `... @RequestBody SomeDTO body ...`). Для "конвертации" данных из одного объекта в другой юзают библиотеку "ModelMapper". Она довольно умная и при совпадении имён меременных сама догадывается откуда куда копировать данные. Если же у нас названия отличаются, или же сама структура объектов разная, то нужно "помочь" моделмапперу - создать соответствующий бин, в котором "вручную" промапить "сложные" поля (ну или вообще все, если мы хотим реализовать какое-то хитрое отображение данных): ```java @Configuration public class ModelMapperProducer { @Bean public ModelMapper modelMapper() { ModelMapper modelMapper = new ModelMapper(); modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull()); modelMapper.typeMap(TestObject.class, TestObjectDTO.class) .setPostConverter(context -> { context.getDestination().name = context.getSource().fullName; return context.getDestination(); }); modelMapper.typeMap(TestObjectDTO.class, TestObject.class) .setPostConverter(context -> { context.getDestination().fullName = context.getSource().name; return context.getDestination(); }); return modelMapper; } } ``` В этом примере показаны два преобразования (в одну сторону и в обратную - зачастую ДТОха используется и для того чтобы "отдавать" данные, и для того чтобы их "принимать"). А так же здесь заданы две популярные настройки: "строгое" совпадение имён параметров (иначе моделМаппер начнёт пытаться "угадать" похожие имена и можно получить мяаасоо), а так же мы говорим мапперу обрабатывать только те поля (в "исходном" объекте), которые `!= null` (насчёт подробностей я пока не в курсе) Пример самой DTO: ```java @Data public class HumanDTO { String name; int age; } ``` Как бы, проще некуда - просто набор полей __(:__ ### Entity Работа с БД осуществляется по модели JPA (кароч это "накрученный" ООП). Класс, который надо "засунуть" в БД помечаем аннотациями `@Entity` и `@Data`. Имя таблицы в БД будет совпадать с именем вашего класса - если вы хотите это изменить, то нужна аннотация `@Table` (например `@Table(name="test_table")`) Пример: ```java @Entity @Data public class Boy { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(updatable = false, nullable = false) protected Long id; String name; int age; @ManyToMany @JoinTable(name = "boys_and_girls", joinColumns = @JoinColumn(name = "boy_id"), inverseJoinColumns = @JoinColumn(name = "girl_id")) Set<Girl> friends = new HashSet<>(); public int hashCode() { return (name + age).hashCode(); } } ``` `id` здесь помечено `@Id`, что задаёт "простой" primare key - уникальный идентификатор записи в БД'шной таблице. К слову, это самый праймари кей бывает и ещё и "композитым" - вещь нужная (как мне говорят). Все переменные к таком классе "используются" хибернейтом в качестве полей таблицы БД (т.е. даже вообще никак не помеченные, как, например, `name` и `age` в этом примере) - чтобы хибернейт "игнорил" поле его надо пометить аннотацией `@Transient` (и да, мноогие аннотации есть и в яве, и в хибернейте - так вот, надо юзать те что в яве!!!) Аннотация `@Column` нужна только для того, чтобы задавать дополнительные "настройки" для поля. В примере выше переопределён метод `hashCode()` - это нужно в случае когда ваша "таблица" содержит объекты типа `HashSet` - без этого переопределния выполучите `StackOverflowError`, потому что будет происходить очень странная рекурсивная чёрная магия __:-|__ (Собственно, это относится ко всем hash-объектам, например _HashMap_) Далее... в нашем классе `Boy` есть "сложный параметр" `friends` - в данном случае это ссылка на другую таюлицу БД, да ещё с "двусторонней" связью между данными. К слову, есть аннотация `@Embedded`, наличие которой заставляет хибернейт "скопипастить" содержимое этого класса, т.е. как `#include` в сях __(:__ ### Связи между таблицами Есть два способа связи: `@JoinTable` и `@JoinColumn`: в первом создаётся отдельная таблица, хранящая "связи", а во втором в "целевую" таблицу просто добавляется столбец, в котором хранится айдишник ссылающейся на него записи исходной таблицы. При отсутствии этих аннотаций хибер по-дефолту юзает `@JoinTable`. ##### @OneToOne Дефолтный `fetch` тут `EAGER` Просто ссыль на запись в другой таблице, т.е. когда одной строке в таблице А соответствует одна строка в таблице Б. Если тут прописать `@JoinColumn`, то "столбец связи" будет помещён в "эту" таблицу (это ведь логично: кому связь нужна, тот и хранит о ней информацию). Можно вручную указать таблицу, в которой должен быть этот столбец: `@JoinColumn(name = "field_name", table = "table_name")`, но таблица уже должна существовать, т.е. хибер сам её создавать/модифицировать не хочет, зараза :neutral_face: ##### @OneToMany Дефолтный `fetch` тут `LAZY` ##### @ManyToOne Дефолтный `fetch` тут `EAGER` ##### @ManyToMany Дефолтный `fetch` тут `LAZY` ### Logger Прописываем в каждом классе `private final Log logger;` (который из `org.apache.commons.logging.Log`) и в конструкторе класса пишем `this.logger = LogFactory.getLog(this.getClass());` - всё, теперь у нас есть спринговый логгер. Можем логировать всё чо душе угодно (какие методы у него и как вызывать - сами догадаетесь).