스프링 부츠 e MongoDB

73042 단어 springmongojava
O objetivo desse postécriar uma aplicaãO de geologizao,dizer quais pontos estãO próximos de vocèpor examplo issoémuito til se vocèuma empresa de entregas e precisa saber qual fornecedor estámais pr ximo para a entrega e várias aplicaèes do gènero.Para isso usarei aqui Spring Boot,pois tem toda uma facilidade Para criaão do projeto e uma comunidade e documentaões muito ativas,MongoDB além de ser uma boa opão por toda a flexibilidade que ele trás tamb mémuito til nesse cent ario onde vamos precisar fazer cálculos de geologizaão e ele nativamente possui isso.
Aqui faremosóo 백엔드 da aplica ão e futuramente desenvolveremoso 전단.

Criando a aplica ão 스프링 부츠


Para isso vamos usar oSpring Initalizr,entrando na página escolhemos como queremos iniciar o projeto,aqui eu irei usar o Spring Web Para poder fazer requisiçes Rest,também estou usando Lombok e Spring DevTools mas s s s sao mais pela facilidade que o Lombok fornece quando criarmos nossos POJOs e o DevTools Para podermos usar em desenvolvimento e termos live reload da aplicaão.
Então fica mais ou menos assim o projeto:

Após isso também precisamos adicionar ao projeto a Dependentência do google service maps,como estou usando Maven
<!-- https://mvnrepository.com/artifact/com.google.maps/google-maps-services -->
<dependency>
    <groupId>com.google.maps</groupId>
    <artifactId>google-maps-services</artifactId>
    <version>0.11.0</version>
</dependency>

Também adicione o driver do Mongo ao pom.
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
</dependency>

설명


Vamos emular uma rede entregas de alimentos,onde os estabellecimentos estão cadastrados e quando um usuário digitar o seu endereço e ele iráexibir os mais próximos dele.

모델


Vamos começar o nosso model com o que seria então estabellecimento ele possuiránome,email e a sua localizaão.então vou começar criando a classe Localizacao.
package com.challenge.geolocation.model;

import java.util.List;

import lombok.Data;

@Data
public class Localizacao {

    private String endereco;
    private List<Double> coordinates;    
    private String type = "Point";

}
Aqui temos o endere o mas também temos dois atributos que podem parecer um pouco estranhos o 좌표 e o 유형.Os dois são necesários quando estamos trabalhando com geologisazaão com o Mongo,o primeiro valor coordinateséuma lista de double contendo a lations e longitude o type diz respeito a um ponto no mapa,podemos ter outros type como Polygon.
Agora criando a nossa classe do estabellecimento propriamente dita.
package com.challenge.geolocation.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.mapping.Document;

import lombok.Data;

package com.challenge.geolocation.model;
import org.

import lombok.Data;

@Datapublic class Estabelecimento

    private ObjectId id;

    private String nome;
    private String email;
    private Localizacao localizacao;    

}

Temos aqui a classe estabellecimento composta pela classe Localizacao e com os atributos id lombok nos ajuda a reduzir um pouco a verbozidade.

코딩기


Agora temos o Model criado mas precisamos fazer de alguma forma pra que a nossa aplicaão se comunique com o Mongo.Aíentraos 인코더,ele vai ser o response á vel por fazer tanto o envio como o recebimento dos objetos do Mongo.
Então vamos criar a classe estabellecimentocodec que implementa a interface CollectibleCodec do tipo estabellecimento:
package com.challenge.geolocation.codec;

import org.bson.BsonReader;
import org.bson.BsonValue;
import org.bson.BsonWriter;
import org.bson.codecs.CollectibleCodec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;

import com.challenge.geolocation.model.Estabelecimento;

public class EstabelecimentoCodec implements CollectibleCodec<Estabelecimento>{

    @Override
    public void encode(BsonWriter writer, Estabelecimento value, EncoderContext encoderContext) {
        // TODO Auto-generated method stub

    }

    @Override
    public Class<Estabelecimento> getEncoderClass() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Estabelecimento decode(BsonReader reader, DecoderContext decoderContext) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Estabelecimento generateIdIfAbsentFromDocument(Estabelecimento document) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean documentHasId(Estabelecimento document) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public BsonValue getDocumentId(Estabelecimento document) {
        // TODO Auto-generated method stub
        return null;
    }

}
Agora precisamos começar a implementar o codec a nossa maneira para ele poder fazer o encod e o decode,para isso vamos adicionar a classe codec do pacote bson que nos ajuda,vamos tipa la como um Document e vamos adicioná-lo ao construtor para ele ficar como dependenceência do nosso codec:
import org.bson.Document;
import org.bson.codecs.Codec;

import com.challenge.geolocation.model.Estabelecimento;


public class EstabelecimentoCodec implements CollectibleCodec<Estabelecimento>{

    private Codec<Document> codec;

    public EstabelecimentoCodec(Codec<Document> codec) {
        this.codec = codec;
    }
Agora vamos 실현자 o método respons á vel por fazer o encode.Aquiéonde dizemos como serão salvo os nossos objetos em Java para um objeto do Mongo:
@Override
public void encode(BsonWriter writer, Estabelecimento estabelecimento, EncoderContext encoder) {
    Document document = new Document();

    document.put("_id", estabelecimento.getId());
    document.put("nome", estabelecimento.getNome());
    document.put("email", estabelecimento.getEmail());

    Localizacao localizacao = estabelecimento.getLocalizacao();

    List<Double> coordinates = new ArrayList<>();
    localizacao.getCoordinates().forEach(coordinates::add);

    document.put("localizacao", new Document()
            .append("endereco", localizacao.getEndereco())
            .append("coordinates", coordinates)
            .append("type", localizacao.getType()));

    codec.encode(writer, document, encoder);
}
E aquiéonde impletamos o decode,como o Java vai interpretar o objeto returnado do Mongo:
@Override
public Estabelecimento decode(BsonReader reader, DecoderContext decoderContext) {

    Document document = codec.decode(reader, decoderContext);

    Estabelecimento estabelecimento = new Estabelecimento();
estabelecimento.
    estabelecimento.setNome(document.getString("nome"));
    estabelecimento.setEmail(document.getString("email"));

    Document localizacao = (Document) document.get("localizacao");
    if(localizacao != null) {
        String endereco = localizacao.getString("endereco");
        @SuppressWarnings("unchecked")
        List<Double> coordinates = (List<Double>) localizacao.get("coordinates");

        Localizacao localizacaoEntity = new Localizacao();
        localizacaoEntity.setEndereco(endereco);
        localizacaoEntity.setCoordinates(coordinates);

        estabelecimento.setLocalizacao(localizacaoEntity);
    }

    return estabelecimento;
}
E temos os outros métodos que implementamos para que o codec consiga fazer a gerência dos objetos:
@Override
public Class<Estabelecimento> getEncoderClass() {
    return Estabelecimento.class;
}

@Override
public Estabelecimento generateIdIfAbsentFromDocument(Estabelecimento estabelecimento) {
    return documentHasId(estabelecimento) ? estabelecimento.generateId() : estabelecimento;
}

@Override
public boolean documentHasId(Estabelecimento estabelecimento) {
    return estabelecimento.getId() == null;
}

@Override
public BsonValue getDocumentId(Estabelecimento estabelecimento) {
    if (!documentHasId(estabelecimento)) {
        throw new IllegalStateException("This Document do not have a id");
    }

    return new BsonString(estabelecimento.getId().toHexString());
}
Aúnica coisa aqui A ressattle foi A criaão do método generateId no model estabellecimento que fica assim:
package com.challenge.geolocation.model;
import org.

import lombok.Data;

@Datapublic class Estabelecimento

    private ObjectId id;

    private String nome;
    private String email;
    private Localizacao localizacao;    

    public Estabelecimento generateId() {
    this
        return this;
    }

}

저장소


Agora temos o Model e o Codec Agora podemos criar o nosso Repository que iráfazer o acesso ao banco e ficaráresponsável por toda a gerência no Mongo.
package com.challenge.geolocation.repository;

import org.springframework.stereotype.Repository;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;

@Repository
public class EstabelecimentoRepository {

    private MongoClient client;
    private MongoDatabase mongoDataBase;

Aqui Crei a classe EstabelecimentoRepository e utilizei a annotation@Repository que diz ao Spring que essa classe faráa administraço com o banco,Aqui jáadicionei o MongoClient que faráo registro do Codec e faráa conexãao banco e o MongoDatabase queéquem seráresponseável por nos trazer a inst–ncia onde poderemos buscar nas nossos collections e fazer as transaçes.
Agora vamos abrir a conexão com o banco de dados:
    @Value("${host}")
    private String host;

    @Value("${port}")
    private String port;

    @Value("${database}")
    private String database;

    @Value("${collection.estabelecimento}")
    private String estabelecimento;

    private MongoCollection<Estabelecimento> openConnetion() {
        Codec<Document> codec = MongoClient.getDefaultCodecRegistry().get(Document.class);

        EstabelecimentoCodec estCodec = new EstabelecimentoCodec(codec);

        CodecRegistry registry = CodecRegistries.fromRegistries(MongoClient.getDefaultCodecRegistry(),
                CodecRegistries.fromCodecs(estCodec));

        MongoClientOptions options = MongoClientOptions.builder().codecRegistry(registry).build();

        this.client = new MongoClient(host + ":" + port, options);
        this.mongoDataBase = client.getDatabase(database);

        return this.mongoDataBase.getCollection(this.estabelecimento, Estabelecimento.class);
    }

    private void closeConnection() {
        this.client.close();
    }
Fazendo uso da annotation@Valeu do Spring conseguimos reciperar o valor que est á no 응용 프로그램.ymlcontendohost, 하나의 포트, o 데이터베이스 e o nome da 집합.
Então basicamente registramos o Codec e conectamos no Mongo e jápegamos a nossa collection e jádeixamos pronto o método para fechar a conexão.
Agora vamos ao método que vai fazer a busca e agregaço desses dados se baseando na proximidade.

    public List<Estabelecimento> searchByGeolocation(Filter filter) {
        try {
            MongoCollection<Estabelecimento> estabelecimentoCollection = openConnetion();

            estabelecimentoCollection.createIndex(Indexes.geo2dsphere("localizacao"));

            Point referencePoint = new Point(new Position(filter.getLat(), filter.getLng()));

            MongoCursor<Estabelecimento> resultados = estabelecimentoCollection
                    .find(Filters.nearSphere("localizacao", referencePoint, filter.getDistance(), 0.0)).limit(filter.getLimit()).iterator();

            List<Estabelecimento> estabelecimentos = fillEstabelecimento(resultados);

            return estabelecimentos;
        } finally {
            closeConnection();
        }
    }

    private List<Estabelecimento> fillEstabelecimento(MongoCursor<Estabelecimento> resultados) {
        List<Estabelecimento> estabelecimentos = new ArrayList<>();
        while (resultados.hasNext()) {
            estabelecimentos.add(resultados.next());
        }
        return estabelecimentos;
    }
Então aqui abrimos a conexão com o método openConnetion()que nos devove a nossa collection e como queremos fazer uma busca por proximidade adicionamos umíndice e dizemos que o campo localizacaoédo tipo 2dsphere se tivessemos usando Mongo ficaria assim:
db.estabelcimento.createIndex({
    localizacao : "2dsphere"
})
Isso o que fizemoséo que o Mongo nos obriga a fazer se quisermos fazer a busca por geologizao.
Em seguida criamos uma classe Point que recebe a lation e a longitude e que seráa baseado no nosso endereço quando passarmos.코모 페가 아나사 위도 e경도?Nãse preocupe que iremos ver mais a seguir no momento sóentenda que teremos esses valores poiséassim que poderemos trabalhar com geologizao.
Agora podemos Disperor a nossa pesquisa:
MongoCursor<Estabelecimento> resultados = estabelecimentoCollection
                    .find(Filters.nearSphere("localizacao", referencePoint, filter.getDistance(), 0.0)).limit(filter.getLimit()).iterator();
Aqui estáa nossa busca,temos a nossa collection e usamos o método find sóque passamos dentro dele os nossos filtros para a agregaço com a classe Filter e seu método estático nearSphere que ele recebe o campo onde ele vai fazer a busca que no casoélocalizacao ponto de referencecia que o nosso referencePoint que nós criamos com a lation e longitude,a máxima dist–ncia,em metros,da nossa pesquisa e a mínima dist–ncia;também passamos o limit de resultados e chamamos o iterator para podermos percorres o que nos voltar do Mongo.
Com um Iterator de resultados em mão podemos então percorre o resultado,aqui separai no método fillEstabelecimento que devolve uma lista de estabellecimento:
    private List<Estabelecimento> fillEstabelecimento(MongoCursor<Estabelecimento> resultados) {
        List<Estabelecimento> estabelecimentos = new ArrayList<>();
        while (resultados.hasNext()) {
            estabelecimentos.add(resultados.next());
        }
        return estabelecimentos;
    }

서비스


Agora faremos a classe de serviço,que iráexecutar a nossa consulta ao banco através do Repository e que iráser chamada pela nossa Controller que nos passaráo endereço e nós iremos fazer a transformaço dele em lation e longitude,para isso usaremos a depencia Google Maps mas antes disso teremos que nos registrator no GCP-Google Cloud Platform,para isso ser á neces á rio criar um conta lá, n ãse preocupe com isso pois o Google dar á um valor em créditos caso seja seu primeiro registro e após isso n ão cobrar á nada sem seu approvisimento prévio, após criar a conta acesseo Console habilitar a API 지리 인코딩 API:

Após issoénecessário criar uma Apikey para que a sua aplicaão possa se communicar com o serviço que foi habilitado:

Agora a nossa apikey em mão jápodmos começar a criar a CLASS E EstabelecimentoService:
package com.challenge.geolocation.service;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.challenge.geolocation.dto.EstabelecimentoDTO;
import com.challenge.geolocation.filter.Filter;
import com.challenge.geolocation.model.Estabelecimento;
import com.challenge.geolocation.repository.EstabelecimentoRepository;
import com.google.maps.GeoApiContext;
import com.google.maps.GeocodingApi;
import com.google.maps.GeocodingApiRequest;
import com.google.maps.errors.ApiException;
import com.google.maps.model.GeocodingResult;
import com.google.maps.model.Geometry;
import com.google.maps.model.LatLng;

@Service
public class EstabelecimentoService {

    @Autowired
    private EstabelecimentoRepository repository;

    @Value("${apikey}")
    private String apikey;

    public List<EstabelecimentoDTO> procuraEstabelecimentosProximoAMim(String endereco, String distance, String limit) {

        GeoApiContext context = new GeoApiContext.Builder().apiKey(apikey).build();

        GeocodingApiRequest request = GeocodingApi.newRequest(context).address(endereco);

        try {
            GeocodingResult[] results = request.await();
            GeocodingResult resultado = results[0];

            Geometry geometry = resultado.geometry;

            LatLng location = geometry.location;

            List<Estabelecimento> estabelecimentoList = repository.searchByGeolocation(
                    Filter.toFilter(location.lat, location.lng, Double.valueOf(distance), Integer.valueOf(limit)));

            List<EstabelecimentoDTO> dtoList = estabelecimentoList.stream().map(estabelecimento -> {
                return EstabelecimentoDTO.toDTO(estabelecimento);
            }).collect(Collectors.toList());

            return dtoList;
        } catch (ApiException | InterruptedException | IOException e) {
            e.printStackTrace();
        }

        return List.of();
    }
}

Criamos o método procuraEstabelecimentosProximoAMim que recebe o endereco,a distance e o limit,em seguida Criamos GeoApiContext usando a nossa apikey fazemos a nossa requisiço para a o serviço do Google passando nosso endereço como estamos fazendo tudo isso de forma síncrona ficamos esperando returno do serviço externo,isso por si sópode ocasionar muitos problem a então todo o método wait lança Exceptions que aquinão vamos nos aprofundar tratando as.
Após o returno pegamos o resultado e navegamos no objeto de returno atéchegarmos onde queremos queéna location queéonde ele guarda a lation e a longitude e agora podemos chamar o nosso Repository que iráexecutar a nossa busca.

필터


Um ponto de observaão,vocêdeveter notado a classe Filter e o método toFilter na nossa Service e na Repository o Filter com getLat,getLng,getDistance e getLimit.Ele nos ajuda a não passar um valor muito grande variáveis na chamada de um método,segue:
@Getter
public class Filter {

    private Filter() {}

    private double lat;
    private double lng;
    private double distance;
    private int limit;

    public static Filter toFilter(double latitude, double longitude, double distance, int limit) {
        Filter filter = new Filter();
        filter.lat = latitude;
        filter.lng = longitude;
        filter.distance = distance == 0 ? 1000.0 : distance;
        filter.limit = limit == 0 ? 10 : limit;     

        return filter;
    }

}

컨트롤러


Agora iremos criar nossa 컨트롤러 onde receberemos a requisião e devolveremos o resultado da pesquisa.
package com.challenge.geolocation.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.challenge.geolocation.dto.EstabelecimentoDTO;
import com.challenge.geolocation.service.EstabelecimentoService;

@RestController
@RequestMapping("api")
public class EstabalecimentoController {

    @Autowired
    private EstabelecimentoService service;

    @GetMapping("estabelecimento")
    public List<EstabelecimentoDTO> pegaEstabelecimentosProximosPeloEndereco(
            @RequestParam(name = "limit", defaultValue = "10") String limit,
            @RequestParam(name = "distancia", defaultValue = "1000.00") String distancia,
            @RequestParam("endereco") String endereco) {
        return service.procuraEstabelecimentosProximoAMim(endereco, distancia, limit);
    }

}
Temos aqui a nossa EstabalecimentoController com unm método pegaEstabelecimentosProximosPeloEndereco e os par–metros limit com um valor default de 10 caso não seja specíficado na url,distancea com o valor de 1000.00,valor em metros e endereco.
Uma coisa interestsanteéque a nossa Controller não devolve o nosso Model pois nãoéconsiderado Uma boa prática e atémesmo um falha de segurança dependedo da situaço então que devolvemos?Devolvemos um DTO(데이터 전송 대상) queéum objeto POJO que só trafega dados como no examplo:
package com.challenge.geolocation.dto;

import com.challenge.geolocation.model.Estabelecimento;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;

import lombok.Getter;

@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
@Getter
public class EstabelecimentoDTO {

    private EstabelecimentoDTO() {}

    private String nome;
    private String email;
    private String endereco;

    public static EstabelecimentoDTO toDTO(Estabelecimento estabelecimento) {
        EstabelecimentoDTO dto = new EstabelecimentoDTO();

        dto.nome = estabelecimento.getNome();
        dto.email = estabelecimento.getEmail();
        dto.endereco = estabelecimento.getLocalizacao().getEndereco();      

        return dto;
    }
}
Aúnica responsibilidade desse DTOétransformar um estabellecimento em um DTO para ser returnado para A Controller e issoéfeito dentro da nossa classe de Service no returno da nossa Repository.

최종 결과


테스트 전 준비 작업에는 벽면 보호기, 벽면 보호기 없음:
{
    "nome" : "Mercado I",
    "email" : "[email protected]",
    "localizacao" : {
        "endereco" : "Rua Brigadeiro Tobias n 780",
        "coordinates" : [ 
            -23.53624, 
            -46.63395
        ],
        "type" : "Point"
    }
}

{    
    "nome" : "Estabelecimento II",
    "email" : "[email protected]",
    "localizacao" : {
        "endereco" : "R. Brg. Tobias, 206 - Santa Ifigênia, São Paulo - SP, 01032-000",
        "coordinates" : [ 
            -23.54165, 
            -46.63583
        ],
        "type" : "Point"
    }
}

{    
    "nome" : "Estabelecimento III",
    "email" : "[email protected]",
    "localizacao" : {
        "endereco" : "Av. Cásper Líbero, 42 - Centro Histórico De São Paulo, São Paulo - SP, 01033-000",
        "coordinates" : [ 
            -23.54132, 
            -46.63643
        ],
        "type" : "Point"
    }
}

{    
    "nome" : "Estabelecimento IV",
    "email" : "[email protected]",
    "localizacao" : {
        "endereco" : "Av. Rio Branco, 630 - República, São Paulo - SP, 01205-000",
        "coordinates" : [ 
            -23.53984, 
            -46.64008
        ],
        "type" : "Point"
    }
}

{    
    "nome" : "Estabelecimento V",
    "email" : "[email protected]",
    "localizacao" : {
        "endereco" : "Alameda Barão de Limeira, 425 - Campos Elíseos, São Paulo - SP, 01202-900",
        "coordinates" : [ 
            -23.53386, 
            -46.6482
        ],
        "type" : "Point"
    }
}

{    
    "nome" : "Estabelecimento VI",
    "email" : "[email protected]",
    "localizacao" : {
        "endereco" : "R. Canuto do Val, 41 - Santa Cecilia, São Paulo - SP, 01224-040",
        "coordinates" : [ 
            -23.54062, 
            -46.65114
        ],
        "type" : "Point"
    }
}

Agora vamos fazer um teste manual usando 불면증 um client para requisi es HTTP,mas vocè usar qualquer um de sua prefència,PostMan,postwomer,cUrle 등.

엔토 파잔도 아르쿠시온http://localhost:8080/api/estabelecimento?endereco=R.베어링토바이어스, 247
Temos a reposta:
[
  {
    "nome": "Estabelecimento II",
    "email": "[email protected]",
    "endereco": "R. Brg. Tobias, 206 - Santa Ifigênia, São Paulo - SP, 01032-000"
  },
  {
    "nome": "Estabelecimento III",
    "email": "[email protected]",
    "endereco": "Av. Cásper Líbero, 42 - Centro Histórico De São Paulo, São Paulo - SP, 01033-000"
  },
  {
    "nome": "Mercado I",
    "email": "[email protected]",
    "endereco": "Rua Brigadeiro Tobias n 780"
  },
  {
    "nome": "Estabelecimento IV",
    "email": "[email protected]",
    "endereco": "Av. Rio Branco, 630 - República, São Paulo - SP, 01205-000"
  }
]

종목 결승전


O 프로젝트 최종vocêpode encontrar aquiaqui

좋은 웹페이지 즐겨찾기