JavaRush /Java блог /Random /Завоевание Spring Boot
Surplus
37 уровень
Москва

Завоевание Spring Boot

Статья из группы Random
Доброго времени суток, уважаемый Читатель! И приятно познакомиться, даже если основной причиной заглянуть в скромную тему о первом знакомстве с разработкой на Spring Boot послужило помпезное название. Хотелось бы поделиться опытом выполнения вступительного задания для стажировки на портале JavaRush, излагая обозрение со стороны вполне обыкновенного студента технического университета, желающего проверить на прочность накопленные знания. Завоевание Spring Boot - 1Никоим образом не отрицаю возможное наличие грубости в приложенном коде или методике мышления, и приветствую конструктивную критику, ведь именно благодаря “шишкам и ссадинам” удается развиваться в профессиональном направлении. Более того, совершенно не претендую на звание “панацеи” в решении поставленных условий и намеренно упускаю отдельные фрагменты программы, оставляя ключевым значением вхождение в относительно сложную тему без малейших последствий для нервной системы. Верно, опрометчиво отрицать очевидное: мне было тяжело и совершенно ничего не понятно до определенного момента. И если Вы испытываете схожие ощущения от первой встречи с заданием, то “Добро пожаловать!”. Напишем web-приложение на Spring Boot по упрощенной аналогии вступительного теста на стажировку с использованием двигателя шаблонов Thymeleaf и query-запросами на локальный сервер MySQL для фильтрации входящего массива информации. Итак, начнем же!

Spring Boot. Что за зверь и как его готовить?

Если кратко и лаконично, — великолепный инструмент от компании Pivotel для сохранения драгоценного времени в процессе создания приложения, исключающий потребность прямого подключения сторонних библиотек, написания внушительного полотна мапирования и сервлетов. Достаточно воспользоваться сборщиком Spring Initializr, интегрированном в IntelliJ IDEA Ultimate Edition (File - New - Project... - Spring Initializr) или расположенном на web-сервисе start.spring.io, указывая пакеты для включения из широкого перечня предложений.
Завоевание Spring Boot - 2
Следуя выдвинутому техническому заданию, воспользуемся джентльменским набором, стандартным для создания простого web-приложения с учетом использования базы MySQL:
  • WEB — основной компонент для разработки web-приложения, включающий локальный сервер Apache Tomcat по стандартному адресу localhost:8080 и универсальный фреймворк Spring MVC.

  • DevTools — используется для быстрого перезапуска приложения в горячей JVM при обнаружении изменений в скомпилированном коде или шаблонах; более того освобождает от очистки cache у Thymeleaf, если выбранный движок включен в проект.

  • JPA — технология требуется для работы с базами данных и обеспечивает объектно-реляционное отображение Java объектов, предоставляет API (Hibernate в нашем случае) для управления, сохранения и получения сущностей.

  • Thymeleaf (Mustache, AngularJS, Vaadin и далее) — двигатель шаблонов для визуализации приложения; благодаря относительному знакомству с принципами html остановил выбор на Thymeleaf, выдвинувшему язык на краеугольный столп мира.

  • MySQL — подключает драйвера Java Database Connectivity для выполнения SQL-запросов к базе данных.
После окончательного выбора компонентов и создания получаем обыкновенную архитектуру web-приложения с готовыми для дальнейшего наполнения директориями. Фрагменты для взаимодействия с визуальной частью, будь то графические стили CSS, стандартные страницы HTML или функционал JavaScript, должны располагаться в “resources”, а back-end составляющая, соответственно, подразумевается для размещения в “java”. Отдельно стоит обратить внимание на файл pom.xml в корневом диапазоне, хранящий структуру проекта и зависимости между компонентами. Если требуется в дальнейшем расширить функционал дополнительными пакетами или удалить лишнее, то следует провести манипуляции между тегов <dependencies></dependencies> согласно схожего метода.
Завоевание Spring Boot - 3

Первые шаги в большое будущее

Далее возникает довольно интересный и вполне логично обоснованный вопрос: “А что делать теперь? Как это будет работать?”. Программа строится на принципах Model-View-Controller: организует считывание сущностей из подключенной базы данных (Model) и отображается в пользовательском интерфейсе с элементами управления (View); связь между компонентами и выполнение действий согласно переданных запросов выполняется благодаря Controller. Именно создание ключевых элементов служит опорной точкой для продолжения разработки. Дабы избежать скользкой дорожки и сохранять уважение товарищей на трудовом поприще, следует располагать компоненты в соответствующих директориях (скажем, поместить файл Controller в папку controllers в ветку “java”) и бережно хранить порядок на рабочем месте.

Сущность — маленькая деталь в большом механизме

Или иначе — наша Модель согласно поставленных в задаче условий. Отступая от тематики обсуждения и возвращаясь к вступительному проекту, можно уверенно утверждать о минимальных различиях между заданиями и придерживаться в дальнейшем обозрении усредненной концепции. Скажем, заметок в записной книжке, включающих:
  • Идентификационный номер для определения расположения в общем потоке;
  • Текстовое сообщение из определенного количества символов;
  • Дату добавления пользователем в общей перечень;
  • Логическую переменную для определения “Сделано или не сделано” (“Прочитано или не прочитано”).
Следовательно, создадим класс Note в директории с названием “entity” и добавим соответствующие поля:

@Entity
public class Note {

   @Id
   @GeneratedValue
   private int id;
   private String message;
   private Date date;
   private boolean done;

   public Note() {
   }

   public Note(String message) {
       this.message = message;
       this.date = new Date();
       this.done = false;
   }
}
Очередное отклонение от темы обсуждения для большего понимания происходящего с теоретической позиции. Связь между компонентами в Spring задается аннотациями, - специальными указателями перед объектами, каждый из которых выполняет определенную роль в механизме и начинается символом “@”. Аннотация @Entity указывает Spring Boot на принадлежность последующих данных класса к “Сущности”, а @Id и @GeneratedValue задают выбранное поле идентификатором с автоматической генерацией итератора при обработке массива информации. Специально упускаю добавление стандартных Getter and Setter для увеличения компактности визуального формата. Далее, принимая во внимание использование базы данных для хранения записей, переходим на следующую ступень в разработке приложения: создадим в директории “repository” интерфейс NoteRepository, - связующий элемент в цепочке обмена, - и унаследуем наиболее подходящий для дальнейшей работы репозиторий с указанием хранимой сущности и целочисленного итератора для обращения.

public interface NoteRepository extends JpaRepository<Note, Integer> {
}
Собственно, всё. Кратко и лаконично. Теперь Spring Boot будет использовать созданный компонент для организации взаимодействий с базой данных. Присутствует относительно много типов наследуемых репозиториев с различным потенциалом возможного действия. JpaRepository находится на вершине лестницы и обладает самым большим потенциалом, включая нижестоящие CrudRepository и PageAndSortingRepository. Более не будем углубляться и отходить от темы, ведь отдельные тонкости можно узнать на сайте Pivotel в технической документации. Теперь, после воплощения образа данных и указания способов связи на стороне приложения, требуется обратить внимание на создание базы MySQL в соответствующей внешней среде “MySQL Workbench”, заранее установленной на настольную платформу в сборке от официального разработчика с дополнительными пакетами для создания локального сервера:
Завоевание Spring Boot - 4
Далее, следуя за указаниями среды после нажатия на иконку с текущим локальным сервером в главном окне, создаем схему таблицы согласно полей нашей сущности (Note) и заполняем ее соответствующими данными. Следует отдельно уточнить тонкости диалекта MySQL, настойчиво требующие внимания для успешного достижения желаемого результата:
  • Отдельного булева типа как такового не существует. Любые действия с обработкой запросов преобразуют “true” или “false” в битовое значение “1” или “0” соответственно;
  • Сохранение даты полностью происходит в типе Timestamp. Если использовать привычный до мозга костей Date, то придется ограничиться лишь положением в календаре.
Завоевание Spring Boot - 5
После окончательного выполнения подготовительных действий, указываем “MySQL Workbench” на отправку данных в локальный сервер нажатием на значок с “молнией” на панели инструментов. Теперь, если добавление информации прошло корректно, можем уверенно возвращаться в родную IDE для дальнейшего продолжения разработки, добавляя конфигурацию текущей базы в application.properties (обычно, располагается в директории “resources”):

spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
И окончательно привязывая сущность Note к MySQL с помощью аннотаций. @Table указывает на использование таблицы с выбранным именем и схемой, а @Column - принадлежность переменных к определенному полю.

@Entity
@Table(name = "test", schema = "test", catalog = "")
public class Note {

   @Id
   @GeneratedValue
   private int id;
   @Column(name = "message")
   private String message;
   @Column(name = "date")
   private Date date;
   @Column(name = "done")
   private boolean done;

   public Note() {
   }

   public Note(String message) {
       this.message = message;
       this.date = new Date();
       this.done = false;
   }
}

Вид или пользовательский интерфейс

Увы, можно смело утверждать следующее: “Визуализация приложения станет основным камнем преткновения без наличия малейших теоретических или практических знаний”. Будучи откровенным, front-end составляющая заняла поразительный объем от общего количества работы и уверенно трепала нервы на протяжение длительного временного периода. Но благодаря удивительной простоте Thymeleaf удалось обрести подходящий компромисс после череды феерических поражений. Дальнейшее обсуждение будет вестись именно о тонкостях использования выбранного движка, хотя общая концепция придерживается схожего положения. Основная методика заключается в возможности использовании чистейшего HTML и сборке конечного отображения из отдельных фрагментов для исключения множественного повторения идентичных участков. Предположим, архитектура пользовательского интерфейса состоит из главной страницы, состоящей из навигационной панели с элементами управления (добавление новой записи, возвращение на главную страницу) и динамической таблицы для отображения сущностей с сортировкой по времени добавления заметки в направлении увеличения (ASC) или уменьшения (DESC) значения. Примем за стандартное положение отображение всех записей по возрастанию. Согласно иерархической политике выбранного движка шаблонов, составные элементы визуализации должны быть расположены на ветви “templates” в директории “resources”. Следовательно, дальнейшие манипуляции с компонентами принимают во внимание выдвинутые условия. Создадим главную страницу с именем “index” (или любым другим согласно личных предпочтений) на шаблоне html5. Например:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/head :: head"></head>
<body>
<div class="container">
   <div th:replace="fragments/header :: header"></div>
   <div th:if="${not #lists.isEmpty(notes)}">
       <div th:replace="operations/list :: notebook"></div>
   </div>
   <div th:replace="fragments/footer :: footer"></div>
</div>
</body>
</html>
И так, разложим по полочкам ключевую составляющую конечного приложения. Thymeleaf использует отдельный синтаксис для указания применения процедур и начинается с ключевого слова “th:”, ссылка на библиотеку с которым подсоединяется в обязательном порядке в открывающем тэге <html>.

<div th:if="${not #lists.isEmpty(notes)}">
Операция “if” совершенно не отличается от привычного образа действий и проверяет входящий атрибут “notes” на наличие объектов для дальнейшего отображения. Следует отдельно упомянуть о перехлестывании темы с использованием Controller, принимая во внимание применение оного для организации взаимодействия модели и визуализации. Многие туманные моменты обретают очертания в дальнейшем, просто возвратитесь назад при наличии желания.

<head th:replace="operations/list :: notebook"></head>
Операция “replace” указывает на замену “заглушки” или действующего блока на выбранный фрагмент из текущей или отдельной страницы - последний случай наглядно наблюдается в примере. Мы копируем фрагмент с названием “notebook” из “list.html” директории “operations” в <div></div> файла “index”, полностью заменяя содержимое в конечном пункте назначения. Исходящий же обладает следующим содержанием:

<!DOCTYPE html>
<!--suppress ALL -->
<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:th="http://www.thymeleaf.org">

<div th:fragment="notebook">
   <table class="table table-bordered table-hover horizontal-align">
       <thead>
       <tr>
           <th style="width: 5%">#</th>
           <th style="width: 60%">Message</th>
           <th class="dropdown" style="width: 20%">Date
               <a th:href="@{'/sort/{sortDate}' (sortDate = 'ASC')}"><i class="fa fa-chevron-circle-up"></i></a>
               <a th:href="@{'/sort/{sortDate}' (sortDate = 'DESC')}"><i class="fa fa-chevron-circle-down"></i></a>
           </th>
           <th style="width: 5%">Done</th>
           <th style="width: 5%">Edit</th>
           <th style="width: 5%">Delete</th>
       </tr>
       </thead>
       <tbody>
       <tr th:each="note : ${notes}">
           <td th:text="${note.id}" style="text-align: center">#</td>
           <td th:text="${note.message}">Message</td>
           <td th:text="${#dates.format(note.date, 'EEE, d MMM yyyy HH:mm')}" style="text-align: center">Date</td>
           <td style="text-align: center">
               <i th:if="${note.done} == true" class="fa fa-plus-square-o" style="font-size:20px;color:#337ab7"></i>
               <i th:if="${note.done} == false" class="fa fa-minus-square-o" style="font-size:20px;color:#337ab7"></i>
           </td>
           <td style="text-align: center"><a th:href="@{'/edit/{id}'(id=${note.id})}"><i class="fa fa-edit" style="font-size:20px"></i></a></td>
           <td style="text-align: center"><a th:href="@{'/delete/{id}'(id=${note.id})}"><i class="fa fa-trash" style="font-size:20px"></i></a></td>
       </tr>
       </tbody>
   </table>
</div>
</html>
Вновь обратимся к конструктивному обозрению и пройдем по использованным функциям Thymeleaf в порядке следования, опуская стандартный синтаксис HTML или использованные графические стили, и направляя внимание именно на понимание механизма движка шаблонов.

<div th:fragment="notebook">
Операция “fragment” задает название фрагмента и открывает возможность использования содержимого блока для команды “replace”. Причем! Никоим образом не исключается множественное применение в пределах одной страницы, вновь выдвигая аналогию с процедурами или функциями в языках программирования.

<a th:href="@{'/sort/{sortDate}' (sortDate = 'ASC')}">
Используется вызов аннотации @PostMapping в Controller с маппингом “/sort/{sortDate}”, где {sortDate}, - исходящий атрибут направления сортировки. Нечто схожее можно наблюдать в следующем блоке, добавляющем динамическое изменение в зависимости от положения выбранного пользователем элемента в цикле итерации:

<a th:href="@{'/edit/{id}'(id=${note.id})}">

<tr th:each="note : ${notes}">
Перечисление значений очень напоминает привычное использование блока for в синтаксисе Java: переменная “note” принимает текущий элемент из массива входящего атрибута ${notes} — массива с сущностями, — и используются в дальнейшем для изменения значений. Будучи откровенным, можно отводить отдельную статью для перечисления широкого спектра возможностей Thymeleaf с приведением примеров практического применения - двигатель шаблонов предельно прост и совершенно не требует изучения внушительного багажа дополнительного синтаксиса. Описанные выше функции изложены в технической документации на официальном сайте разработчиков и исполняют ключевое значение в организации связи с back-end. Следовательно, можно уверенно переходить к следующей и завершающей части. Конечно же, приложив остальные составляющие фрагменты визуализации в ссылке на готовое приложение в конце статьи.

Контроллер, администратор в маленькой компании

“Краеугольный камень в архитектуре web-приложения”, - пожалуй, никоим образом не удастся подобрать более точное описание значимости компонента Controller в организации работы программы: большинство операций проводится именно связующим элементом между моделью и видом. Благодаря механике действия Spring Boot, можно уверенно использовать маппирование и методы запроса GET/POST без малейших проблем, автоматически подключить репозиторий с данными. Создадим класс NoteController в отдельном файле в директории “controllers”, вновь обращаясь к применению соответствующей аннотации:

@Controller
public class NoteController {

   private NoteService service;

   @Autowired
   public void setNoteService(NoteService service) {
       this.service = service;
   }

   @GetMapping("/")
   public String list(Model model) {
       return "index";
   }
}
Внимательный взгляд может заметить важное изменение в принципах построения архитектуры приложения, связанное с добавлением сервиса для изолирования бизнес-логики от работы со службой управления базой данных. Выполненные действия требуются для увеличения универсальности готового продукта и предоставляют широкий простор для изменения функционала пользовательского интерфейса без потребности изменений в методах связи с базой данных. Стандартное представление никоим образом не выделяется из общей массы схожих: интерфейс расположен в отдельной директории и имплементируется классом с аннотацией @Service для обнаружения Spring Boot:

public interface NoteService {
   Note getNoteById(Integer id);
   void saveNote(Note note);
   void updateNote(Integer id, String message, boolean done);
   void deleteNote(Integer id);
   List<Note> findAll();
}

@Service
public class NoteServiceImpl implements NoteService{

   private NoteRepository repository;

   @Autowired
   public void setProductRepository(NoteRepository repository) {
       this.repository = repository;
   }

   @Override
   public Note getNoteById(Integer id) {
       return repository.findOne(id);
   }

   @Override
   public void saveNote(Note note) {
       repository.save(note);
   }

   @Override
   public void updateNote(Integer id, String message, boolean done) {
       Note updated = repository.findOne(id);
       updated.setDone(done);
       updated.setMessage(message);
       repository.save(updated);
   }

   @Override
   public void deleteNote(Integer id) {
       repository.delete(id);
   }

   @Override
   public List<Note> findAll() {
       return repository.findAll();
   }
}
Возвратимся к обозрению контроллера и разберем тонкости организации работы с помощью методов Spring Boot. Аннотация @Autowired сообщает о потребности автоматической привязки сервиса к указанной переменной соответствующего типа и устанавливает связь с базой данных. Следует обратить больше внимания на способ общения с видом, обозначенный аннотацией @GetMapping("/") и возвращающий страницу с именем “index” при получении вызова localhost:8080. Можно использовать различный подход, конкретизируя расширенное описание @RequestMapping(value = "/", method = RequestMethod.GET) или заменить возвращаемый тип на готовую ModelAndView. Однако, согласно текущего положения опыта практического применения, совершенно не замечаю кардинальных различий в конечном результате и использую привычный вариант. Расширим котроллер методом добавления новых элементов с помощью дополнительной вкладки. После нажатия пользователем на элемент навигационной панели происходит вызов @GetMapping("/new") и перенаправление на страницу “new” из директории “operations”, возвращающую параметр с именем “message” при подтверждении введенных данных использованием кнопки и выполняющую переадресацию на главный блок. Отдельного упоминания требует потребность полного совпадения наименования переменной в окне ввода с именем передаваемого значения.

<input type="text" class="form-control" id="message" th:name="message" placeholder="Enter your note." maxlength="100"/>

@GetMapping("/new")
public String newNote() {
   return "operations/new";
}

@PostMapping("/save")
public String updateNote(@RequestParam String message) {
   service.saveNote(new Note(message));
   return "redirect:/";
}
Схожая методика используется для обновления записи. После нажатия на элемент управления происходит вызов мапирования @GetMapping("/edit/{id}") и передача идентификатора из url-строки, добавляется атрибут “note” с записью для дальнейшего редактирования. @RequestParam(value = "done", required = false) boolean done) указание конкретного значения оказывает ключевую роль в использовании checkbox при применении движка шаблонов Thymeleaf и устанавливается по умолчанию в положение “false”.

@GetMapping("/edit/{id}")
public String edit(@PathVariable Integer id, Model model) {
   Note note = service.getNoteById(id);
   model.addAttribute("note", note);
   return "operations/edit";
}

@PostMapping("/update")
public String saveNote(@RequestParam Integer id, @RequestParam String message,
                      @RequestParam(value = "done", required = false) boolean done) {
   service.updateNote(id, message, done);
   return "redirect:/";
}
Удаление элементов из базы данных выполняется предельно просто и совершенно не требует выдающихся манипуляций, вызывая подходящую функцию сервиса с помощью переданного значения:

@GetMapping("/delete/{id}")
public String delete(@PathVariable Integer id) {
   service.deleteNote(id);
   return "redirect:/";
}
Теперь внесем маленькие коррективы в готовые фрагменты и перейдем к увлекательному общению с MySQL при помощи query-запросов в Spring Data JPA, отдельно добавляя функцию управления простой фильтрацией перед закрытием Controller.

@Controller
public class NoteController {

   private String sortDateMethod = "ASC";

   @GetMapping("/")
   public String list(Model model) {
       List<Note> notebook = filterAndSort();
       model.addAttribute("notes", notebook);
       model.addAttribute("sort", sortDateMethod);
       return "index";
   }

private List<Note> filterAndSort() {
   List<Note> notebook = null;
   switch (sortDateMethod) {
       case "ASC":
           notebook = service.findAllByOrderByDateAsc();
           break;
       case "DESC":
           notebook = service.findAllByOrderByDateDesc();
           break;
   }
   return notebook;
}

Такой маленький, но такой важный Query.

Неловко признаваться, фильтрация значений вопреки ожиданиям оказалась очередным камнем преткновения в выполнении технического задания, уверенно преодолевая установленный пагинацией - разбиением массива данных на отдельные страницы определенной размерности для дальнейшего отображения, - порог сложности. Скорее всего, сказывалась накопившаяся усталость, но… озарение снизошло после совершенно случайного столкновения с Query-запросами.

public interface NoteRepository extends JpaRepository<Note, Integer> {
   List<Note> findAllByOrderByDateAsc();
   List<Note> findAllByOrderByDateDesc();
}
Spring Data JPA предоставляет возможности для создания конкретизированных запросов в базу данных, избавляющих от потребности сортировать информацию после получения и обладающих широким потенциалом для применения. Например:

List<Note> findAllByOrderByDateAsc();
Метод будет преобразован в SQL запрос и отобразит все записи (findAll) с сортировкой (byOrder) по дате (byDate) в порядке возрастания (Asc). Более того, можно составлять сложные комбинации и проводить выборку по многим полям за единственное требование. Скажем, выбрать все (findAll) выполненные (byDoneTrue) записи в порядке (byOrder) уменьшения (Decs) по значению даты (byDate):

Page<Note> findAllByDoneTrueOrderByDateDesc(Pageable pageable);

Заключение или очередная исповедь начинающего программиста

Всё! Можно смело запускать web-приложение использованием комбинации Shift+F10 или нажатием на соответствующую иконку. Spring Boot соберет программу на Apache Maven и установит локальный сервер Apache Tomcat по адресу localhost:8080. Теперь достаточно лишь перейти по ссылке в любом браузере.
Завоевание Spring Boot - 6
И, конечно же, разработать методику для выполнения остальных бизнес-требований. Потенциал приложения ограничивается приложенными усилиями, находчивой инициативой и фантазией разработчика.
Завоевание Spring Boot - 7
Будучи откровенным и обращая внимание на пройденный путь, вновь и вновь убеждаюсь в верности выбранного направления и осознаю пользу от обучения на образовательном портале JavaRush. Благодаря множеству практических задач удалось возвратить манящий интерес к изучению программирования, окончательно забитый в устаревающей и удивительно скучной программе высшего учебного заведения схожего направления. Четыре месяца активного изучения материала в back-end стеке технологий вложили гораздо больше знаний по сравнению с целыми годами посещения лекций и лабораторных занятий. Хотите верьте, а хотите — нет. Желаю не пасовать перед трудностями вхождения в сложный материал, ведь именно благодаря преодолению препятствий мы становимся лучше и развиваемся в профессиональном и личностном плане. Надеюсь, маленький рассказ способствовал открытию свежих идеи для применения восхитительного инструмента под названием SpringBoot. P.S. Github.
Комментарии (37)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Иван Сергеевич Уровень 41
15 сентября 2021
Возможно вопрос не в тему, но тем не менее. На чем написан код для html страницы? Например для джафа fx используется scene buider, где можно иконки нужные вручную мышкой перетащить. А как работать с хтмл? не вручную же все эти теги набирают))
Николай Уровень 23
23 октября 2020
Очень мешает стиль изложения. Автор старается быть веселым и остроумным. Само по себе это неплохо, но в данном случае, когда читатель слабо знаком с материалом, мешает восприятию, т.к. приходится отделять существенное от несущественного и увеличивает объем текста.
Surplus Уровень 37
20 июля 2020
То странное чувство, когда кусок жизни печатают на катушке и запечатывают в качестве наследия. Особенно приятно заглянуть в статью и увидеть подобную активность, обсуждения интересующих моментов и желание многих стремиться к лучшему. Спасибо большое, что Вы есть. Продолжайте творить, верьте в себя и воплощайте мечты в реальность.
Денис Уровень 20
18 мая 2020
Вот бы такой пример только не веб приложение а спринг рест клиент фор андроид. С секюрити и гугл аутентификацией.. такой шаблон приложения
10 февраля 2020
Хорошая статья, большое спасибо! Вот с этого, пожалуй, начинается настоящая разработка.
4 ноября 2019
Быстро информация устаревает. На start.spring.io перечисленного набора уже не собрать. Сам сейчас стою на @Entity . Практически на каждую аннотацию из статьи Idea ругается,предлагает ее создать. Также нельзя создать красиво папки контролер и другие, все одной портянкой идет.
Sergej Kalva Уровень 2
23 октября 2019
Похоже на курс как нарисовать сову. Спасибо +5.
Рамиль Насыров Уровень 40
1 сентября 2019
Ок, как теперь сделать так же красиво, как и на скриншотах?
Saniya Уровень 17
26 февраля 2019
Если знаете хорошие источники/книги на данную тему, прошу поделиться. Спасибо!
Ola Уровень 36
4 февраля 2019
как завоевать Spring без Spring Boot?