Учебные материалы

Доступ к MongoDB-данным через GORM

Этот урок освещает пошаговое создание Spring Boot приложения с использованием Grails Object Relational Mapper (GORM) и MongoDB. Приложение использует плагин GORM для MongoDB

Что вы создадите

Вы создадите Spring приложение, которое сохраняет геопространственные данные в БД MongoDB с использованием GORM.

В этом уроке используется только библиотека Grails GORM. Вы не обязаны использовать весь web-стек Grails.

Что вам потребуется

  • Примерно 15 минут свободного времени
  • Любимый текстовый редактор или IDE
  • JDK 7 и выше
  • Gradle 1.8+
  • Вы также можете импортировать код этого урока, а также просматривать web-страницы прямо из Spring Tool Suite (STS), собственно как и работать дальше из него.

Как проходить этот урок

Как и большинство уроков по Spring, вы можете начать с нуля и выполнять каждый шаг, либо пропустить базовые шаги, которые вам уже знакомы. В любом случае, вы в конечном итоге получите рабочий код.

Чтобы начать с нуля, перейдите в Настройка проекта.

Чтобы пропустить базовые шаги, выполните следующее:

Когда вы закончите, можете сравнить получившийся результат с образцом в gs-accessing-data-gorm-mongodb/complete.

Настройка проекта

Для начала вам необходимо настроить базовый скрипт сборки. Вы можете использовать любую систему сборки, которая вам нравится для сборки проетов Spring, но в этом уроке рассмотрим код для работы с Gradle. Если вы не знакомы с ней, ознакомьтесь с соответсвующим уроком Сборка Java-проекта с использованием Gradle.

Создание структуры каталогов

В выбранном вами каталоге проекта создайте следующую структуру каталогов; к примеру, командой mkdir -p src/main/groovy/hello для *nix систем:

└── src
    └── main
        └── groovy
            └── cities

Создание файла сборки Gradle

Ниже представлен начальный файл сборки Gradle. Если вы используете Spring Tool Suite (STS), то можете импортировать урок прямо из него.

Если вы посмотрите на pom.xml, вы найдете, что указана версия для maven-compiler-plugin. В общем, это не рекомендуется делать. В данном случае он предназначен для решения проблем с нашей CI системы, которая по умолчанию имеет старую(до Java 5) версию этого плагина.

build.gradle

buildscript {
    repositories {
    mavenLocal()
        mavenCentral()
        maven { url "https://repo.spring.io/milestone" }
        maven { url "https://repo.spring.io/libs-release" }
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.9.RELEASE")
    }
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'

jar {
    baseName = 'gs-spring-boot-gorm-mongodb'
    version =  '0.1.0'
}

repositories {
    mavenLocal()
    mavenCentral()
    maven { url "https://repo.spring.io/milestone" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.grails:gorm-mongodb-spring-boot:1.1.0.RELEASE")
    runtime("org.springframework.boot:spring-boot-starter-data-mongodb")
    testCompile("junit:junit")
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

Spring Boot gradle plugin предоставляет множество удобных возможностей:

  • Он собирает все jar'ы в classpath и собирает единое, исполняемое "über-jar", что делает более удобным выполнение и доставку вашего сервиса
  • Он ищет public static void main() метод, как признак исполняемого класса
  • Он предоставляет встроенное разрешение зависимостей, с определенными номерами версий для соответсвующих Spring Boot зависимостей. Вы можете переопределить на любые версии, какие захотите, но он будет по умолчанию для Boot выбранным набором версий

Создание GORM сущности

В этом примере мы моделируем простую сущность Person с использованием GORM:

src/main/groovy/cities/City.groovy

package cities

import grails.persistence.*
import grails.mongodb.geo.*
import org.bson.types.ObjectId

@Entity
class City {
    ObjectId id
    String name
    Point location

    static constraints = {
        name blank:false
        location nullable:false
    }

    static mapping = {
        location geoIndex:'2dsphere'
    }
}

Блок ограничения описывает любые правила валидации. В данном случае, накладывается ограничение только на использование непустого поля name и не равного значению null поля location.

Блок маппинга указывает на то, что свойство location должно быть проиндексировано с использованием геопространственного 2dsphere индекса.

Свойство location имеет тип grails.mongodb.geo.Point, который сохраняет в MongoDB как GeoJSON Point.

Создание контроллера ресурсов

Создайте контроллер для вашего Groovy Spring приложения:

src/main/groovy/cities/CityController.groovy

package cities

import com.mongodb.BasicDBObject
import javax.annotation.PostConstruct;
import org.springframework.web.bind.annotation.*
import org.springframework.http.*
import static org.springframework.web.bind.annotation.RequestMethod.*
import grails.mongodb.geo.*

@RestController
class CityController {

    @RequestMapping(value="/", method = GET)
    List index() {
        City.list().collect { [name: it.name] }
    }

    @RequestMapping(value="/near/{cityName}", method = GET)
    ResponseEntity near(@PathVariable String cityName) {
        def city = City.where { name == cityName }.find()
        if(city) {
            List<City> closest = City.findAllByLocationNear(city.location)
            return new ResponseEntity([name: closest[1].name], HttpStatus.OK)
        }
        else {
            return new ResponseEntity(HttpStatus.NOT_FOUND)
        }
    }

    @PostConstruct
    void populateCities() {
        City.withTransaction{
            City.collection.remove(new BasicDBObject())
            City.saveAll(
                [ new City( name:"London",
                            location: Point.valueOf([-0.125487, 51.508515])),
                  new City( name:"Paris",
                            location: Point.valueOf([2.352222, 48.856614])),
                  new City( name:"New York",
                            location: Point.valueOf([-74.005973, 40.714353])),
                  new City( name:"San Francisco",
                            location: Point.valueOf([-122.419416, 37.774929])) ]
            )
        }
    }
}

Пример описывает две REST точки выхода:

  • index передает список всех известных городов
  • near находит город, ближайший к указанному городу

Метод populateCities выполняется при запуске, т.к. аннотирован @PostConstruct. Метод содержит некоторые первоначальные данные, для оперирования с ними в приложении. Обратите внимание, как каждому городу передан экземпляр Point местонахождения с указанием долготы/широты:

@PostConstruct
void populateCities() {
    City.withTransaction{
        City.collection.remove(new BasicDBObject())
        City.saveAll(
            [ new City( name:"London",
                        location: Point.valueOf([-0.125487, 51.508515])),
              new City( name:"Paris",
                        location: Point.valueOf([2.352222, 48.856614])),
              new City( name:"New York",
                        location: Point.valueOf([-74.005973, 40.714353])),
              new City( name:"San Francisco",
                        location: Point.valueOf([-122.419416, 37.774929])) ]
        )
    }
}

Методу near название города в качестве параметра и затем производится поиск ближайших городов. Обратите внимание, что первая запись в результате будет городом, который запросил сам себя, потому что индекс 1 использован для поиска ближайшего города:

@RequestMapping(value="/near/{cityName}", method = GET)
    ResponseEntity near(@PathVariable String cityName) {
        def city = City.where { name == cityName }.find()
        if(city) {
            List<City> closest = City.findAllByLocationNear(city.location)
            return new ResponseEntity([name: closest[1].name], HttpStatus.OK)
        }
        else {
            return new ResponseEntity(HttpStatus.NOT_FOUND)
        }
    }

Далее вы увидите как запустить приложение и создать новую сущность Person.

Создание приложения исполняемым

Несмотря на то, что пакет этого сервиса может быть в составе web-приложения и WAR файлов, более простой подход, продемонстрированный ниже создает отдельное самостоятельное приложение. Вы упаковываете все в единый, исполняемый JAR-файл, который запускается через хорошо знакомый старый main() Java-метод. Попутно, вы используете поддержку Spring для встроенного Tomcat контейнера сервлетов как HTTP среду выполнения вместо развертывания на сторонний экземпляр.

src/main/groovy/cities/Application.groovy

package hello

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.context.annotation.ComponentScan

@EnableAutoConfiguration
@ComponentScan
class Application {
    static void main(String[] args) {
        SpringApplication.run Application, args
    }
}

main() метод передает управление вспомогательному классу SpringApplication, где Application.class - аргумент его run() метода. Это сообщает Spring о чтении метаданных аннотации из Application и управлении ею как компонента в Spring application context.

Аннотация @ComponentScan говорит Spring'у рекурсивно искать в пакете cities и его потомках классы, помеченные прямо или косвенно Spring аннотацией @Component. Эта директива гарантирует, что Spring найдет и зарегистрирует CityController, потому что он отмечен @RestController, который, в свою очередь, является своего рода @Component аннотацией.

Аннотация @EnableAutoConfiguration переключает на приемлемое по умолчанию поведение, основанное на содержании вашего classpath. К примеру, т.к. приложение зависит от встраиваемой версии Tomcat (tomcat-embed-core.jar), Tomcat сервер установлен и сконфигурирован на приемлемое по умолчанию поведение от вашего имени. И т.к. приложение также зависит от Spring MVC (spring-webmvc.jar), Spring MVC DispatcherServlet сконфигурирован и зарегестрирован для вас - web.xml не нужен! Автоконфигурация полезный и гибкий механизм. Подробную информацию смотрите в API документации.

Сборка исполняемого JAR

Вы можете собрать единый исполняемый JAR-файл, который содержит все необходимые зависимости, классы и ресурсы. Это делает его легким в загрузке, версионировании и развертывании сервиса как приложения на протяжении всего периода разработки, на различных средах и так далее.

./gradlew build

Затем вы можете запустить JAR-файл:

java -jar build/libs/gs-accessing-data-gorm-mongodb-0.1.0.jar

Если вы используете Maven, вы можете запустить приложение, используя mvn spring-boot:run, либо вы можете собрать приложение с mvn clean package и запустить JAR примерно так:

java -jar target/gs-accessing-data-gorm-mongodb-0.1.0.jar
Процедура, описанная выше, создает исполняемый JAR. Вы также можете вместо него собрать классический WAR-файл.

Запуск приложения

Если вы используете Gradle, вы можете запустить ваш сервис из командной строки:

./gradlew clean build && java -jar build/libs/gs-accessing-data-gorm-mongodb-0.1.0.jar
Если вы используете Maven, то можете запустить ваш сервис таким образом: mvn clean package && java -jar target/gs-accessing-data-gorm-mongodb-0.1.0.jar.

Как вариант, вы можете запустить ваш сервис напрямую из Gradle примерно так:

./gradlew bootRun
С mvn - mvn spring-boot:run.

Тестирование приложения

Когда приложение запустится, вы можете получить список всех городов. К примеру, используя *nix инструмент curl:

$ curl -i http://localhost:8080/

Результат ответа, показанном ниже, будет иметь список городов в JSON формате:

$ curl -i http://localhost:8080/
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 21 Mar 2014 07:52:34 GMT

[{"name":"London"},{"name":"Paris"},{"name":"New York"},{"name":"San Francisco"}]

Затем вы можете отправить GET запрос на /near для поиска ближайшего города к указанному имени городу:

$ curl -i http://localhost:8080/near/Paris
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 21 Mar 2014 07:53:47 GMT

{"name":"London"}

Поддержка Groovy Script

Предыдущий пример требовал от вас установки системы сборки Gradle или Maven, но вы можете также использовать GORM в простом Groovy скрипте.

Как пример, создайте новый файл app.groovy и поместите в него следующий код:

app.groovy

@Grab("org.grails:gorm-mongodb-spring-boot:1.1.0.RELEASE")
@Grab("org.mongodb:mongo-java-driver:2.12.2")
import grails.persistence.*
import grails.mongodb.geo.*
import org.bson.types.ObjectId
import com.mongodb.BasicDBObject
import static org.springframework.web.bind.annotation.RequestMethod.*

@RestController
class CityController {

    @RequestMapping(value="/", method = GET)
    List index() {
        City.list().collect { [name: it.name] }
    }

    @RequestMapping(value="/near/{cityName}", method = GET)
    ResponseEntity near(@PathVariable String cityName) {
        def city = City.where { name == cityName }.find()
        if(city) {
            List<City> closest = City.findAllByLocationNear(city.location)
            return new ResponseEntity([name: closest[1].name], HttpStatus.OK)
        }
        else {
            return new ResponseEntity(HttpStatus.NOT_FOUND)
        }
    }

    @PostConstruct
    void populateCities() {
        City.withTransaction{
            City.collection.remove(new BasicDBObject())
            City.saveAll(
                [ new City(name:"London",
                           location: Point.valueOf([-0.125487, 51.508515])),
                  new City(name:"Paris",
                           location: Point.valueOf([2.352222, 48.856614])),
                  new City(name:"New York",
                           location: Point.valueOf([-74.005973, 40.714353])),
                  new City(name:"San Francisco",
                           location: Point.valueOf([-122.419416, 37.774929]))
                ]
            )
        }
    }
}

@Entity
class City {
    ObjectId id
    String name
    Point location

    static constraints = {
        name blank:false
        location nullable:false
    }

    static mapping = {
        location geoIndex:'2dsphere'
    }
}
Не имеет значения, где расположен файл.

Далее, установите Spring Boot CLI.

Запустите его как показано ниже:

$ spring run app.groovy
Предполагается, что вы завершили предыдущее приложение, дабы избежать коллизии портов.

Настройка MongoDB соединения

Для настройки MongoDB соединения вы можете использовать файл приложения application.yml:

src/main/groovy/cities/Application.groovy

spring:
    mongodb:
        host: "localhost"
        databaseName: "citydb"
        options:
            connectionsPerHost: 20

Доступные параметры конфигурации такие же, как и те, что предоставляет плагин GORM для MongoDB.

Как вариант, если у вас есть свои требования к конфигурации, то вы можете настроить Spring бин типа com.mongodb.Mongo в вашем приложении.

Итог

Поздравляем! Вы только что создали Spring приложение с использованием GORM для доступа к данным MongoDB.

С оригинальным текстом урока вы можете ознакомиться на spring.io.

comments powered by Disqus