В чем разница между MongoTemplate Spring Data и MongoRepository?

102

Мне нужно написать приложение, с помощью которого я могу выполнять сложные запросы с использованием spring -data и mongodb. Я начал с использования MongoRepository, но боролся со сложными запросами, чтобы найти примеры или действительно понять синтаксис.

Я говорю о таких запросах:

@Repository
public interface UserRepositoryInterface extends MongoRepository<User, String> {
    List<User> findByEmailOrLastName(String email, String lastName);
}

или использование запросов на основе JSON, которые я пробовал методом проб и ошибок, потому что не понимаю синтаксиса. Даже после прочтения документации mongodb (нерабочий пример из-за неправильного синтаксиса).

@Repository
public interface UserRepositoryInterface extends MongoRepository<User, String> {
    @Query("'$or':[{'firstName':{'$regex':?0,'$options':'i'}},{'lastName':{'$regex':?0,'$options':'i'}}]")
    List<User> findByEmailOrFirstnameOrLastnameLike(String searchText);
} 

После прочтения всей документации кажется, что mongoTemplateэто гораздо лучше документировано MongoRepository. Я имею в виду следующую документацию:

http://static.springsource.org/spring-data/data-mongodb/docs/current/reference/html/

Подскажите, что удобнее и мощнее в использовании? mongoTemplateили MongoRepository? Оба они одинаковы зрелые или у одного из них не хватает функций, чем у другого?

Кристофер Армстронг
источник

Ответы:

136

«Удобный» и «мощный в использовании» в некоторой степени противоречат целям. Репозитории намного удобнее, чем шаблоны, но последние, конечно, дают вам более точный контроль над тем, что выполнять.

Поскольку модель программирования репозитория доступна для нескольких модулей Spring Data, вы найдете более подробную документацию по ней в общем разделе справочной документации Spring Data MongoDB .

TL; DR

Обычно мы рекомендуем следующий подход:

  1. Начните с аннотации репозитория и просто объявляйте простые запросы, используя механизм деривации запросов или задаваемые вручную запросы.
  2. Для более сложных запросов добавьте в репозиторий вручную реализованные методы (как описано здесь). Для реализации используйте MongoTemplate.

Детали

В вашем примере это будет выглядеть примерно так:

  1. Определите интерфейс для вашего пользовательского кода:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
    
  2. Добавьте реализацию для этого класса и следуйте соглашению об именах, чтобы убедиться, что мы можем найти класс.

    class UserRepositoryImpl implements CustomUserRepository {
    
      private final MongoOperations operations;
    
      @Autowired
      public UserRepositoryImpl(MongoOperations operations) {
    
        Assert.notNull(operations, "MongoOperations must not be null!");
        this.operations = operations;
      }
    
      public List<User> yourCustomMethod() {
        // custom implementation here
      }
    }
    
  3. Теперь позвольте вашему базовому интерфейсу репозитория расширить настраиваемый интерфейс, и инфраструктура автоматически будет использовать вашу настраиваемую реализацию:

    interface UserRepository extends CrudRepository<User, Long>, CustomUserRepository {
    
    }
    

Таким образом, вы, по сути, получаете выбор: все, что просто объявить, входит в состав UserRepository, все, что лучше реализовать вручную, входит CustomUserRepository. Параметры настройки описаны здесь .

Оливер Дротбом
источник
1
Привет Оливер, это на самом деле не работает. spring-data пытается автоматически сгенерировать запрос из настраиваемого имени. yourCustomMethod (). Он скажет, что «ваш» не является допустимым полем в классе домена. Я следил за руководством, а также дважды проверил, как вы это делаете, в spring-data-jpa-examples. Неудачно. spring-data всегда пытается автоматически сгенерировать, как только я расширяю настраиваемый интерфейс до класса репозитория. Единственная разница в том, что я использую MongoRepository, а не CrudRepository, так как пока я не хочу работать с итераторами. Если у вас есть подсказка, мы будем признательны.
Кристофер Армстронг
11
Наиболее распространенной ошибкой является неправильное имя класса реализации: если вызывается ваш базовый интерфейс репо YourRepository, класс реализации должен быть назван YourRepositoryImpl. Так ли это? Если да, то я счастлив взглянуть на образец проекта на GitHub или подобном…
Оливер Дротбом,
5
Привет, Оливер, класс Impl был назван неправильно, как вы и предполагали. Я изменил имя, и теперь похоже, что оно работает. Большое спасибо за ваш отзыв. Действительно здорово иметь возможность таким образом использовать различные параметры запроса. Хорошо продумано!
Кристофер Армстронг,
Этот ответ не так однозначен. Сделав все по этому примеру, я столкнулся с этой проблемой: stackoverflow.com/a/13947263/449553 . Таким образом, соглашение об именах более строгое, чем может показаться в этом примере.
msangel
1
Класс реализации на № 2 назван неправильно: должно быть CustomUserRepositoryи нет CustomerUserRepository.
Cotta
27

Этот ответ может быть немного запоздалым, но я бы рекомендовал избегать всего пути к репозиторию. Вы получаете очень мало реализованных методов, имеющих большую практическую ценность. Чтобы заставить его работать, вы сталкиваетесь с чушью конфигурации Java, на которую вы можете потратить дни и недели без особой помощи в документации.

Вместо этого, идите по MongoTemplateпути и создайте свой собственный уровень доступа к данным, который избавит вас от кошмаров конфигурации, с которыми сталкиваются программисты Spring. MongoTemplateэто действительно спаситель для инженеров, которым удобно создавать собственные классы и взаимодействия, поскольку существует большая гибкость. Структура может быть примерно такой:

  1. Создайте MongoClientFactoryкласс, который будет работать на уровне приложения и предоставит вам MongoClientобъект. Вы можете реализовать это как Singleton или с помощью Enum Singleton (это потокобезопасный)
  2. Создайте базовый класс доступа к данным, от которого вы можете наследовать объект доступа к данным для каждого объекта домена). Базовый класс может реализовать метод для создания объекта MongoTemplate, который методы вашего класса могут использовать для всех обращений к БД.
  3. Каждый класс доступа к данным для каждого объекта домена может реализовывать базовые методы, или вы можете реализовать их в базовом классе.
  4. Затем методы контроллера могут при необходимости вызывать методы в классах доступа к данным.
Рамешпа
источник
Привет, @rameshpa Могу ли я использовать как MongoTemplate, так и репозиторий в одном проекте? .. Возможно ли использовать
Гауранга
1
Вы могли бы, но реализуемый вами MongoTemplate будет иметь другое соединение с БД, чем соединение, используемое репозиторием. Атомарность может быть проблемой. Также я бы не рекомендовал использовать два разных соединения в одном потоке, если у вас есть потребности в секвенировании
rameshpa
27

FWIW относительно обновлений в многопоточной среде:

  • MongoTemplateобеспечивает «атомный» вне коробки операций updateFirst , updateMulti, findAndModify, upsert... , которые позволяют изменять документ в одной операции. UpdateОбъект , используемый эти методы также позволяет настроить таргетинг только соответствующие поля .
  • MongoRepositoryтолько дает вам основные операции CRUD find , insert, save, delete, которые работают с POJOs , содержащей все поля . Это вынуждает вас либо обновить документы в несколько этапов (1. findдокумент, который нужно обновить, 2. изменить соответствующие поля из возвращенного POJO, а затем 3. saveего), либо вручную определить свои собственные запросы на обновление @Query.

В многопоточной среде, такой как, например, серверная часть Java с несколькими конечными точками REST, обновление с использованием одного метода - это путь, чтобы уменьшить вероятность того, что два одновременных обновления перезапишут изменения друг друга.

Пример: учитывая такой документ: { _id: "ID1", field1: "a string", field2: 10.0 }и два разных потока, одновременно обновляющих его ...

С MongoTemplateним будет выглядеть примерно так:

THREAD_001                                                      THREAD_002
|                                                               |
|update(query("ID1"), Update().set("field1", "another string")) |update(query("ID1"), Update().inc("field2", 5))
|                                                               |
|                                                               |

и окончательное состояние для документа всегда, { _id: "ID1", field1: "another string", field2: 15.0 }поскольку каждый поток обращается к БД только один раз, и только указанное поле изменяется.

В то время как тот же сценарий MongoRepositoryбудет выглядеть так:

THREAD_001                                                      THREAD_002
|                                                               |
|pojo = findById("ID1")                                         |pojo = findById("ID1")
|pojo.setField1("another string") /* field2 still 10.0 */       |pojo.setField2(pojo.getField2()+5) /* field1 still "a string" */
|save(pojo)                                                     |save(pojo)
|                                                               |
|                                                               |

и последний документ - это либо, { _id: "ID1", field1: "another string", field2: 10.0 }либо в { _id: "ID1", field1: "a string", field2: 15.0 }зависимости от того, какая saveоперация попадает в БД последней.
(ПРИМЕЧАНИЕ: даже если бы мы использовали аннотацию Spring Data,@Version как предложено в комментариях, мало что изменилось бы: одна из saveопераций выдала бы OptimisticLockingFailureException, а окончательный документ все равно был бы одним из перечисленных выше, с обновлением только одного поля вместо обоих. )

Поэтому я бы сказал, что MongoTemplateэто лучший вариант , если у вас нет очень сложной модели POJO или вам MongoRepositoryпо какой-то причине не нужны возможности настраиваемых запросов .

Вален
источник
Хорошие моменты / примеры. Однако вашего примера состояния гонки и нежелательного результата можно избежать с помощью @Version, чтобы предотвратить этот самый сценарий.
Madbreaks 06
@Madbreaks Можете ли вы предоставить какие-либо ресурсы о том, как этого добиться? Возможно, какой-нибудь официальный документ?
Картикеян
Документы Spring data об аннотации @Version: docs.spring.io/spring-data/mongodb/docs/current/reference/html/…
Карим Тауфик
1
@Madbreaks Спасибо, что указали на это. Да, @Versionможно «избежать» перезаписи вторым потоком данных, сохраненных первым - «избежать» в том смысле, что он отбросит обновление и OptimisticLockingFailureExceptionвместо этого выбросит . Так что вам придется реализовать механизм повтора, если вы хотите, чтобы обновление прошло успешно. MongoTemplate позволяет избежать всего сценария.
walen