JavaRush /Blogue Java /Random-PT /Bota Primavera Conquista
Surplus
Nível 37
Москва

Bota Primavera Conquista

Publicado no grupo Random-PT
Bom dia, caro leitor! E é um prazer conhecê-lo, mesmo que o nome pomposo tenha sido o principal motivo para abordar o modesto tópico sobre o primeiro contato com o desenvolvimento do Spring Boot. Gostaria de compartilhar minha experiência na realização do trabalho introdutório para um estágio no portal JavaRush, apresentando uma visão geral de um estudante universitário técnico completamente comum que deseja testar a força de seu conhecimento acumulado. Bota Conquest Spring - 1De forma alguma nego a possível presença de grosseria no código ou método de pensamento anexo, e acolho críticas construtivas, pois é graças aos “solavancos e hematomas” que é possível desenvolver-se no sentido profissional. Além disso, não pretendo de forma alguma ser uma “panaceia” na resolução das condições dadas e omito deliberadamente fragmentos individuais do programa, deixando a importância fundamental de entrar num tema relativamente complexo sem as menores consequências para o sistema nervoso. É verdade, é temerário negar o óbvio: foi difícil para mim e absolutamente nada ficou claro até certo momento. E se você tiver sentimentos semelhantes desde o primeiro encontro com a tarefa, então “Bem-vindo!” Vamos escrever uma aplicação web em Spring Boot usando uma analogia simplificada de um teste de admissão de estágio usando um mecanismo de modelo Thymeleafe queryconsultas a um servidor MySQL local para filtrar a matriz de informações recebidas. Então vamos começar!

Bota Primavera. Que tipo de animal é e como cozinhá-lo?

Resumindo e concisamente, é uma excelente ferramenta da Pivotel para economizar um tempo valioso no processo de criação de um aplicativo, eliminando a necessidade de conectar diretamente bibliotecas de terceiros, escrever uma impressionante tela de mapeamento e servlets. Basta usar o construtor Spring Initializr , integrado ao IntelliJ IDEA Ultimate Edition (Arquivo - Novo - Projeto... - Spring Initializr) ou localizado no serviço web start.spring.io , especificando pacotes a serem incluídos de uma ampla gama de ofertas.
Bota Conquest Spring - 2
Seguindo as especificações técnicas apresentadas, utilizaremos o conjunto de cavalheiros, padrão para criação de uma aplicação web simples utilizando o banco de dados MySQL :
  • WEB é o principal componente para o desenvolvimento de uma aplicação web, incluindo um servidor Apache Tomcat local no endereço padrão localhost:8080 e o framework universal Spring MVC.

  • DevTools - usado para reiniciar rapidamente um aplicativo em uma JVM ativa quando alterações são detectadas no código compilado ou nos modelos; Além disso, libera o Thymeleaf de limpar o cache se o mecanismo selecionado estiver incluído no projeto.

  • JPA é uma tecnologia necessária para trabalhar com bancos de dados e fornece mapeamento objeto-relacional de objetos Java, fornece uma API ( Hibernate no nosso caso) para gerenciar, salvar e recuperar entidades.

  • Thymeleaf (Mustache, AngularJS, Vaadin e além) - mecanismo de template para visualização de aplicativos; Graças à minha relativa familiaridade com os princípios do html, escolhi o Thymeleaf, que levou a linguagem à pedra angular do mundo.

  • MySQL - conecta drivers Java Database Connectivity para executar consultas SQL no banco de dados.
Após a seleção final dos componentes e criação, obtemos uma arquitetura de aplicação web comum com diretórios prontos para preenchimento posterior. Fragmentos para interagir com a parte visual, sejam estilos gráficos CSS, páginas HTML padrão ou funcionalidade JavaScript, devem estar localizados em “recursos”, e o componente back-end, respectivamente, deve ser colocado em “java”. Devemos também prestar atenção ao arquivo pom.xml no intervalo raiz, que armazena a estrutura do projeto e as dependências entre os componentes. Se você deseja expandir ainda mais a funcionalidade com pacotes adicionais ou remover itens desnecessários, você deve realizar manipulações entre tags <dependencies></dependencies>usando um método semelhante.
Bota Primavera Conquista - 3

Primeiros passos para um grande futuro

A seguir surge uma questão bastante interessante e bastante lógica: “O que fazer agora? Como isso funcionará? O programa é construído sobre os princípios do Model-View-Controller: organiza a leitura das entidades do banco de dados conectado (Model) e é exibido na interface do usuário com controles (View); a comunicação entre os componentes e a execução das ações de acordo com as solicitações transmitidas é realizada graças ao Controlador. É a criação de elementos-chave que serve de ponto de referência para o desenvolvimento contínuo. Para evitar uma ladeira escorregadia e manter o respeito de seus companheiros no campo de trabalho, você deve colocar os componentes nos diretórios apropriados (por exemplo, colocar o arquivo Controller na pasta controllers no branch “java”) e manter cuidadosamente ordem no local de trabalho.

A essência é uma pequena parte de um grande mecanismo

Ou seja, nosso Modelo de acordo com as condições estabelecidas no problema. Partindo do tema da discussão e voltando ao projeto introdutório, podemos afirmar com segurança que existem diferenças mínimas entre as tarefas e aderir ao conceito médio em uma revisão mais aprofundada. Digamos, anotações em um caderno, incluindo:
  • Número de identificação para determinar localização no fluxo geral;
  • Uma mensagem de texto com um determinado número de caracteres;
  • A data em que o usuário o adicionou à lista geral;
  • Uma variável booleana para determinar “Concluído ou não concluído” (“Ler ou não ler”).
Portanto, vamos criar uma classe Note em um diretório chamado “entidade” e adicionar os campos apropriados:
@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;
   }
}
Mais um desvio do tema da discussão para uma maior compreensão do que está acontecendo a partir de uma posição teórica. A conexão entre os componentes no Spring é especificada por anotações - ponteiros especiais na frente dos objetos, cada um dos quais desempenha uma função específica no mecanismo e começa com o símbolo “@”. A anotação @Entity indica ao Spring Boot que os dados da classe subsequente pertencem à “Entidade”, e @Id e @GeneratedValue especificam o campo selecionado como um identificador com geração automática de um iterador ao processar uma matriz de informações. Omiti deliberadamente a adição de Getter e Setter padrão para aumentar a compactação do formato visual. A seguir, levando em consideração o uso de um banco de dados para armazenamento de registros, passamos para a próxima etapa do desenvolvimento da aplicação: criaremos a interface NoteRepository no diretório “repositório”, um elemento de conexão na cadeia de troca, e herdaremos o mais repositório adequado para trabalhos futuros, indicando a entidade armazenada e o iterador inteiro para acessar.
public interface NoteRepository extends JpaRepository<Note, Integer> {
}
Na verdade, isso é tudo. Breve e conciso. Agora o Spring Boot usará o componente criado para organizar as interações com o banco de dados. Existem relativamente muitos tipos de repositórios legados com potencial de ação variado. JpaRepository está no topo da escada e tem o maior potencial, incluindo CrudRepository e PageAndSortingRepository abaixo dele. Não iremos mais longe e nos desviaremos do assunto, pois algumas das sutilezas podem ser encontradas no site do Pivotel na documentação técnica. Agora, após implementar a imagem de dados e especificar os métodos de comunicação do lado da aplicação, é necessário atentar para a criação de um banco de dados MySQL no ambiente externo apropriado “MySQL Workbench”, pré-instalado na plataforma desktop em um assembly do desenvolvedor oficial com pacotes adicionais para criar um servidor local:
Bota Conquest Spring - 4
A seguir, seguindo as instruções do ambiente após clicar no ícone com o servidor local atual na janela principal, criamos um diagrama de tabela de acordo com os campos da nossa entidade (Nota) e preenchemos com os dados apropriados. É necessário esclarecer separadamente as sutilezas do dialeto MySQL, que requerem atenção urgente para alcançar com sucesso o resultado desejado:
  • Não existe um tipo booleano separado como tal. Quaisquer ações de processamento de solicitação converterão “verdadeiro” ou “falso” no valor do bit “1” ou “0”, respectivamente;
  • A data é armazenada inteiramente no tipo Timestamp. Se você usar a Data, que é familiar, terá que se limitar apenas à posição no calendário.
Bota Primavera Conquista - 5
Após a conclusão final das etapas preparatórias, indicamos “MySQL Workbench” para enviar dados ao servidor local clicando no ícone “relâmpago” da barra de ferramentas. Agora, se a adição de informações foi concluída corretamente, podemos retornar com segurança ao nosso IDE nativo para continuar o desenvolvimento, adicionando a configuração atual do banco de dados a application.properties (geralmente localizado no diretório “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
E finalmente vinculando a entidade Note ao MySQL usando anotações. @Table indica o uso de uma tabela com o nome e esquema selecionados e @Column indica que as variáveis ​​pertencem a um campo específico.
@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;
   }
}

Visualização ou interface do usuário

Infelizmente, podemos afirmar com segurança o seguinte: “A visualização da aplicação se tornará o principal obstáculo sem o menor conhecimento teórico ou prático”. Para ser franco, o componente front-end ocupou uma quantidade incrível de todo o trabalho e me deixou com os nervos à flor da pele por um longo período de tempo. Mas graças à incrível simplicidade do Thymeleaf, foi possível encontrar um compromisso adequado após uma série de derrotas encantadoras. Uma discussão mais aprofundada será sobre os meandros do uso do mecanismo selecionado, embora o conceito geral siga uma posição semelhante. A técnica principal é a capacidade de usar o HTML mais puro e montar a exibição final a partir de fragmentos individuais para evitar repetições múltiplas de seções idênticas. Vamos supor que a arquitetura da UI consista em uma página principal que consiste em uma barra de navegação com controles (adicionar nova entrada, retornar à página principal) e uma tabela dinâmica para exibir entidades classificadas por horário em que a nota foi adicionada em ordem crescente (ASC) ou decrescente ( DESC) direção. significados. Tomemos como posição padrão a exibição de todos os registros em ordem crescente. De acordo com a política hierárquica do mecanismo de template selecionado, os elementos de visualização do componente devem estar localizados na ramificação “templates” do diretório “resources”. Consequentemente, outras manipulações com componentes levam em consideração as condições apresentadas. Vamos criar uma página principal com o nome “index” (ou qualquer outro nome de acordo com a preferência pessoal) em um template html5. Por exemplo:
<!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>
E então, vamos analisar os principais componentes da aplicação final. Thymeleaf usa uma sintaxe separada para indicar o uso de procedimentos e começa com a palavra-chave “th:”, cujo link para a biblioteca está necessariamente incluído na tag de abertura <html>.
<div th:if="${not #lists.isEmpty(notes)}">
A operação “if” não é completamente diferente da maneira usual de fazer as coisas e verifica o atributo “notas” recebido quanto à presença de objetos para exibição posterior. Vale ressaltar separadamente a sobreposição do tema com a utilização do Controller, levando em consideração sua utilização para organização da interação do modelo e visualização. Muitos momentos vagos tomam forma no futuro, basta voltar se quiser.
<head th:replace="operations/list :: notebook"></head>
A operação “replace” indica a substituição de um “stub” ou bloco ativo por um fragmento selecionado da página atual ou separada - o último caso é claramente observado no exemplo. Copiamos o fragmento denominado “notebook” de “list.html” do diretório “operações” para o <div></div> do arquivo “index”, substituindo completamente o conteúdo no destino final. O de saída tem o seguinte conteúdo:
<!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>
Vamos retornar à visão geral construtiva e examinar as funções do Thymeleaf usadas em ordem, omitindo a sintaxe HTML padrão ou os estilos gráficos usados, e focando especificamente na compreensão do mecanismo do mecanismo de modelo.
<div th:fragment="notebook">
A operação “fragmento” especifica o nome do fragmento e permite utilizar o conteúdo do bloco para o comando “substituir”. Além disso! Múltiplos usos dentro de uma única página não são de forma alguma excluídos, novamente trazendo a analogia com procedimentos ou funções em linguagens de programação.
<a th:href="@{'/sort/{sortDate}' (sortDate = 'ASC')}">
Uma chamada para a anotação @PostMapping é usada no Controlador com o mapeamento “/sort/{sortDate}”, onde {sortDate} é o atributo de direção de classificação de saída. Algo semelhante pode ser visto no bloco a seguir, que adiciona uma mudança dinâmica dependendo da posição do elemento selecionado pelo usuário no loop de iteração:
<a th:href="@{'/edit/{id}'(id=${note.id})}">
<tr th:each="note : ${notes}">
Enumerar valores é muito semelhante ao uso familiar de um bloco for na sintaxe Java: a variável “note” pega o elemento atual do array de atributos de entrada ${notes} – um array de entidades – e é usado para alterar os valores ​​mais tarde. Para ser franco, poderíamos dedicar um artigo separado para listar a ampla gama de recursos do Thymeleaf com exemplos de aplicação prática - o mecanismo de modelo é extremamente simples e não requer o aprendizado de uma bagagem impressionante de sintaxe adicional. As funções descritas acima estão descritas na documentação técnica do site oficial dos desenvolvedores e desempenham um papel fundamental na organização da comunicação com o back-end. Portanto, você pode passar com segurança para a próxima e última parte. Claro, anexando os componentes restantes da visualização em um link para o aplicativo finalizado no final do artigo.

Controlador, administrador em uma pequena empresa

“A pedra angular da arquitetura de uma aplicação web” - talvez não haja como encontrar uma descrição mais precisa da importância do componente Controller na organização do trabalho do programa: a maioria das operações é realizada justamente pelo elemento de conexão entre o modelo e a vista. Graças à mecânica de ação do Spring Boot, você pode usar métodos de mapeamento e solicitação GET/POST com segurança, sem o menor problema, e conectar automaticamente o repositório de dados. Vamos criar a classe NoteController em um arquivo separado no diretório “controllers”, novamente nos referindo ao uso da anotação apropriada:
@Controller
public class NoteController {

   private NoteService service;

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

   @GetMapping("/")
   public String list(Model model) {
       return "index";
   }
}
Um olhar atento pode notar uma mudança importante no design da arquitetura do aplicativo associada à adição de um serviço para isolar a lógica de negócios do trabalho com o serviço de gerenciamento de banco de dados. As ações concluídas são necessárias para aumentar a versatilidade do produto acabado e fornecer ampla margem para alteração da funcionalidade da interface do usuário sem a necessidade de alterações nos métodos de comunicação com o banco de dados. A representação padrão não se destaca de forma alguma da multidão de outras semelhantes: a interface está localizada em um diretório separado e é implementada por uma classe com a anotação @Service para detecção do 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();
   }
}
Vamos voltar à revisão do controlador e examinar as complexidades da organização do trabalho usando métodos Spring Boot. A anotação @Autowired indica a necessidade de vincular automaticamente um serviço a uma variável especificada do tipo apropriado e estabelecer uma conexão com o banco de dados. Mais atenção deve ser dada à forma como a view se comunica, indicada pela anotação @GetMapping("/"), que retorna uma página chamada “index” ao receber uma chamada para localhost:8080. Você pode usar uma abordagem diferente, especificando a descrição estendida @RequestMapping(value = "/", method = RequestMethod.GET) ou substituindo o tipo de retorno por um ModelAndView pronto. Porém, de acordo com o estado atual da experiência na aplicação prática, não noto diferenças fundamentais no resultado final e utilizo a opção usual. Vamos expandir o controlador adicionando novos elementos usando uma guia adicional. Após o usuário clicar em um elemento da barra de navegação, @GetMapping("/new") é chamado e redirecionado para a página “nova” do diretório “operações”, retornando um parâmetro chamado “mensagem” ao confirmar os dados inseridos através do botão e redirecionando para o bloco principal. A necessidade de uma correspondência completa do nome da variável na janela de entrada com o nome do valor transferido requer menção especial.
<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:/";
}
Uma técnica semelhante é usada para atualizar um registro. Após clicar no controle, o mapeamento @GetMapping("/edit/{id}") é chamado e o identificador da string da URL é transferido, o atributo “note” é adicionado com uma entrada para posterior edição. @RequestParam(value = "done", require = false) boolean done) especificar um valor específico desempenha um papel fundamental no uso da caixa de seleção ao usar o mecanismo de modelo Thymeleaf e é definido como “false” por padrão.
@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:/";
}
A remoção de itens do banco de dados é extremamente simples e não requer nenhuma manipulação significativa, chamando a função de serviço apropriada usando o valor passado:
@GetMapping("/delete/{id}")
public String delete(@PathVariable Integer id) {
   service.deleteNote(id);
   return "redirect:/";
}
Agora vamos fazer pequenos ajustes nos fragmentos finalizados e prosseguir para uma comunicação interessante com o MySQL usando consultas de consulta no Spring Data JPA, adicionando separadamente uma função para gerenciar a filtragem simples antes de fechar o 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;
}

Consulta tão pequena, mas tão importante.

É constrangedor admitir que a filtragem de valores, ao contrário das expectativas, acabou sendo outro obstáculo no cumprimento da tarefa técnica, superando com segurança o limite de complexidade estabelecido pela paginação - quebrando a matriz de dados em páginas separadas de um determinado tamanho para exibição posterior. Muito provavelmente, o cansaço acumulado estava cobrando seu preço, mas... a inspiração veio depois de um encontro completamente acidental com as consultas do Query.
public interface NoteRepository extends JpaRepository<Note, Integer> {
   List<Note> findAllByOrderByDateAsc();
   List<Note> findAllByOrderByDateDesc();
}
Spring Data JPA oferece a capacidade de criar consultas de banco de dados altamente granulares que eliminam a necessidade de classificar as informações assim que são recebidas e têm uma ampla gama de potenciais de aplicação. Por exemplo:
List<Note> findAllByOrderByDateAsc();
O método será convertido em uma consulta SQL e exibirá todos os registros (findAll) ordenados (byOrder) por data (byDate) em ordem crescente (Asc). Além disso, você pode criar combinações complexas e amostras em vários campos com um único requisito. Digamos, selecione todos os registros (findAll) concluídos (byDoneTrue) em ordem (byOrder) decrescente (Decs) por valor de data (byDate):
Page<Note> findAllByDoneTrueOrderByDateDesc(Pageable pageable);

Conclusão ou outra confissão de um programador novato

Todos! Você pode iniciar o aplicativo da web com segurança usando a combinação Shift+F10 ou clicando no ícone correspondente. Spring Boot construirá o programa no Apache Maven e instalará um servidor Apache Tomcat local em localhost:8080. Agora você só precisa seguir o link em qualquer navegador.
Bota Primavera Conquest - 6
E, claro, desenvolver uma metodologia para atender a outros requisitos de negócio. O potencial do aplicativo é limitado pelo esforço, desenvoltura e imaginação do desenvolvedor.
Bota Conquest Spring - 7
Sendo franco e atento ao caminho percorrido, estou sempre convencido do acerto do rumo escolhido e percebo os benefícios de estudar no portal educacional JavaRush. Graças a uma variedade de tarefas práticas, foi possível devolver o interesse sedutor em aprender programação, que havia sido completamente suprimido no programa desatualizado e surpreendentemente enfadonho de uma instituição de ensino superior de direção semelhante. Quatro meses de estudo ativo de material na pilha de tecnologia de back-end investiram muito mais conhecimento em comparação com anos inteiros de palestras e aulas de laboratório. Acredite ou não. Desejo que você não ceda às dificuldades de ingressar em materiais complexos, pois é através da superação de obstáculos que nos tornamos melhores e nos desenvolvemos profissionalmente e pessoalmente. Espero que esta pequena história tenha me ajudado a descobrir novas ideias para usar a incrível ferramenta chamada SpringBoot. PS Github .
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION