Статьи

Дополнение репозитория Spring Data с помощью делегирования

Недавно я написал несколько постов о делегации Котлина. При этом я реализовал полезный способ применить его к репозиториям Spring Data. Что позволило бы Spring Data продолжать окроплять магией, предоставляя маршрут для настройки. Код, показанный в этом посте, написан на Kotlin, но все еще имеет отношение к Java.

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

Чтение асинхронного доступа к СУБД с использованием Spring Data R2DBC и делегирование классов в Котлине было бы здесь полезно, если у вас недостаточно знаний в этих областях.

Как резюме. В чем заключается волшебство Spring Data?

Spring Data позволяет вам написать интерфейс, в котором вам нужно только определить запросы, которые вам нужны. Затем он выполнит всю работу по созданию реализации и внедрению зависимостей для вас. Это выглядит примерно так:

1
2
3
4
5
6
@Repository
interface PersonRepository : R2dbcRepository<Person, Int> {
 
  @Query("SELECT * FROM people WHERE age > $1")
  fun findAllByAgeGreaterThan(age: Int): Flux<Person>
}

Поскольку Spring Data R2DBC используется, полностью выведенные запросы еще не полностью поддерживаются. Вот почему запрос выписывается вручную.

Недостатком этого является то, что он создает реализацию на основе интерфейса. Поэтому, если вы хотите выполнить какую-либо настройку, вам нужно будет создать экземпляр интерфейса самостоятельно, внедрить его зависимости и реализовать каждый запрос. Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class PersonRepositoryImpl(
  private val entity: RelationalEntityInformation<Person, Int>,
  private val databaseClient: DatabaseClient,
  converter: R2dbcConverter,
  private val accessStrategy: ReactiveDataAccessStrategy
) : SimpleR2dbcRepository<Person, Int>(entity, databaseClient, converter, accessStrategy),
  PersonRepository {
 
  override fun findAllByAgeGreaterThan(age: Int): Flux<Person> {
 
    val mapper: StatementMapper.TypedStatementMapper<Person> =
      accessStrategy.statementMapper.forType(entity.javaType)
 
    val selectSpec: StatementMapper.SelectSpec = mapper
      .createSelect(entity.tableName)
      .withProjection(accessStrategy.getAllColumns(entity.javaType))
      .withCriteria(Criteria.where("age").greaterThan(age))
 
    val operation: PreparedOperation<*> = mapper.getMappedObject(selectSpec)
 
    return databaseClient.execute().sql(operation).`as`(entity.javaType).fetch().all()
  }
}

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

Трудность создания этого класса может быть устранена путем делегирования в репозиторий Spring, реализованный на основе вашего интерфейса. Затем вы можете добавить все необходимые настройки.

В Котлине это будет выглядеть так:

01
02
03
04
05
06
07
08
09
10
@Repository
class DelegatingPersonRepository(private val delegate: PersonRepository) :
  PersonRepository by delegate {
 
  override fun <S : Person> save(objectToSave: S): Mono<S> {
    // override `save` implementation
  }
 
  // any other overrides (kotlin provides delegated implementations)
}

В Java это немного более громоздко, но все же легко достижимо:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@Repository
public class DelegatingPersonRepository implements PersonRepository {
 
  private final PersonRepository delegate;
 
  public DelegatingPersonRepository(PersonRepository delegate) {
    this.delegate = delegate;
  }
 
  @Override
  public Flux<Person> findAllByAgeGreaterThan(int age) {
    return delegate.findAllByAgeGreaterThan(age);
  }
 
  @Override
  public <S extends Person> Mono<S> save(S entity) {
    // override `save` implementation
  }
 
  // all other implementations of `PersonRepository` functions
}

В обеих версиях DelegatingPersonRepository вызывает реализацию findAllByAgeGreaterThan определенную в PersonRepository . До сих пор не было приложено никаких усилий для написания функции для запроса к базе данных.

При использовании DelegatingPersonRepository все вызовы функций, которые не переопределяются, будут делегированы реализации PersonRepository , созданной Spring.

Для кого-то вроде меня, которому не очень нравится составлять SQL-запросы и писать весь код преобразования. Использование делегирования таким способом действительно позволяет вам использовать преимущества Spring Data, в то же время предоставляя вам возможность настроить результат. Количество кода, которое вы сохраняете, может быть не таким уж большим. Но есть значительные сокращения усилий, необходимых для его объединения. Просто пусть Spring сделает всю тяжелую работу за вас!

Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . См. Оригинальную статью здесь: Дополнение репозитория Spring Data с помощью делегирования.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.