# 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());` - всё, теперь у нас есть спринговый логгер.
Можем логировать всё чо душе угодно (какие методы у него и как вызывать - сами догадаетесь).