Подтвердить что ты не робот

Использование дженериков в хранилищах данных Spring Data JPA

У меня есть несколько простых типов объектов, которые необходимо сохранить в базе данных. Я использую Spring JPA для управления этим постоянством. Для каждого типа объекта мне нужно построить следующее:

import org.springframework.data.jpa.repository.JpaRepository;

public interface FacilityRepository extends JpaRepository<Facility, Long> {
}


public interface FacilityService {
    public Facility create(Facility facility);
}

@Service
public class FacilityServiceImpl implements FacilityService {

    @Resource
    private FacilityRepository countryRepository;

    @Transactional
    public Facility create(Facility facility) {
        Facility created = facility;
        return facilityRepository.save(created);
    }
}

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

4b9b3361

Ответ 1

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

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

Вообще говоря, вы можете сделать что-то вроде этого:

interface ProductRepository<T extends Product> extends CrudRepository<T, Long> {

    @Query("select p from #{#entityName} p where ?1 member of p.categories")
    Iterable<T> findByCategory(String category);

    Iterable<T> findByName(String name);
}

Это позволит вам использовать хранилище на стороне клиента следующим образом:

class MyClient {

  @Autowired
  public MyClient(ProductRepository<Car> carRepository, 
                  ProductRepository<Wine> wineRepository) { … }
}

и все будет работать как положено. Однако есть несколько вещей, на которые следует обратить внимание:

Это работает, только если классы домена используют наследование одной таблицы. Единственная информация о классе домена, который мы можем получить во время начальной загрузки, состоит в том, что это будут объекты Product. Поэтому для таких методов, как findAll() и даже findByName(…), соответствующие запросы будут начинаться с select p from Product p where…. Это связано с тем, что поиск отражений никогда не сможет произвести Wine или Car, если вы не создадите специальный интерфейс хранилища для него, чтобы захватить конкретную информацию о типе.

Вообще говоря, мы рекомендуем создавать интерфейсы репозитория для совокупного корня. Это означает, что у вас нет репо для каждого класса домена как такового. Еще важнее то, что абстракция службы 1:1 над хранилищем также полностью упускает из виду. Если вы создаете сервисы, вы не создаете один для каждого репозитория (это может сделать обезьяна, а мы не обезьяны, не так ли?). Служба предоставляет API более высокого уровня, является гораздо более подходящим вариантом и обычно организует вызовы в несколько репозиториев.

Кроме того, если вы строите сервисы поверх репозиториев, вы обычно хотите заставить клиентов использовать сервис вместо репозитория (классический пример здесь заключается в том, что сервис для управления пользователями также запускает генерацию пароля и шифрование, так что ни в коем случае было бы неплохо позволить разработчикам использовать репозиторий напрямую, поскольку они будут эффективно обходить шифрование). Таким образом, вы обычно хотите быть избирательными в отношении того, кто может сохранять объекты домена, чтобы не создавать зависимости повсеместно.

Резюме

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

Ответ 2

Я работаю над проектом по созданию общего репозитория для Кассандры с данными весны.

Сначала создайте интерфейс репозитория с кодом.

StringBuilder sourceCode = new StringBuilder();
sourceCode.append("import org.springframework.boot.autoconfigure.security.SecurityProperties.User;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.AllowFiltering;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.Query;\n");
sourceCode.append("import org.springframework.data.repository.CrudRepository;\n");
sourceCode.append("\n");
sourceCode.append("public interface TestRepository extends CrudRepository<Entity, Long> {\n");
sourceCode.append("}");

Скомпилируйте код и получите класс, я использую org.mdkt.compiler.InMemoryJavaCompiler

ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
compiler = InMemoryJavaCompiler.newInstance();
compiler.useParentClassLoader(classLoader);
Class<?> testRepository = compiler.compile("TestRepository", sourceCode.toString());

И инициализировать репозиторий в весенний период выполнения данных. Это немного сложно, так как я отлаживаю код SpringData, чтобы найти способ инициализации интерфейса репозитория spring.

CassandraSessionFactoryBean bean = context.getBean(CassandraSessionFactoryBean.class);
RepositoryFragments repositoryFragmentsToUse = (RepositoryFragments) Optional.empty().orElseGet(RepositoryFragments::empty); 
CassandraRepositoryFactory factory = new CassandraRepositoryFactory(
    new CassandraAdminTemplate(bean.getObject(), bean.getConverter()));
factory.setBeanClassLoader(compiler.getClassloader());
Object repository = factory.getRepository(testRepository, repositoryFragmentsToUse);

Теперь вы можете попробовать метод сохранения репозитория и другие методы, такие как findById.

Method method = repository.getClass().getMethod("save", paramTypes);
T obj = (T) method.invoke(repository, params.toArray());

Полный пример кода и реализации я поместил в этот репозиторий https://github.com/maye-msft/generic-repository-springdata.

Вы можете расширить его до JPA с аналогичной логикой.

Ответ 3

Я просто работаю над родовым хранилищем данных, генерируя интерфейс и компилируя его во время выполнения. Пожалуйста, найдите репозиторий ниже для реализации и пример кода.

https://github.com/maye-msft/generic-repository-springdata