web-dev-qa-db-ja.com

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);
    }
}

私は、各オブジェクトタイプの複数のクラスを3つのジェネリックベースのクラスに置き換えて、多くの定型コードを節約できる可能性があることに気付きました。私はそれについてどうやって行くのか、実際にはそれが良いアイデアであるかどうか正確にはわかりませんか?

26
skyman

まず第一に、私たちはここでかなり水準を上げていることを知っていますが、これは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) { … }
}
_

期待どおりに機能します。ただし、注意すべき点がいくつかあります。

これは、ドメインクラスが単一のテーブル継承を使用している場合にのみ機能します。bootstrapで取得できるドメインクラスに関する唯一の情報=時間はProductオブジェクトになるため、findAll()findByName(…)などのメソッドでも、関連するクエリは_select p from Product p where…_で始まります。リフレクションルックアップがWineまたはCarを生成することは決してないという事実に、nless具体的な型情報をキャプチャするための専用のリポジトリインターフェイスを作成します。

一般的に言えば、 集約ルート ごとにリポジトリインターフェースを作成することをお勧めします。これは、すべてのドメインクラス自体のリポジトリがないことを意味します。さらに重要なことは、リポジトリ上のサービスを1対1で抽象化しても、その点が完全に欠落していることです。サービスを構築する場合、リポジトリごとに構築する必要はありません(猿がそれを行うことができます。私たちは猿ではありませんよね?;)。サービスはより高いレベルのAPIを公開しており、はるかにユースケースのドライブであり、通常は複数のリポジトリへの呼び出しを調整します。

また、リポジトリの上にサービスを構築する場合、通常、クライアントがリポジトリの代わりにサービスを使用するように強制します(ここでの古典的な例は、ユーザー管理のサービスもパスワードの生成と暗号化をトリガーするため、決して暗号化を効果的に回避するため、開発者がリポジトリを直接使用できるようにすることをお勧めします)。そのため、通常は、誰がどのドメインオブジェクトを永続化して、どこでも依存関係を作成しないように選択する必要があります。

概要

はい、汎用リポジトリを構築し、複数のドメインタイプで使用できますが、技術的に非常に厳しい制限があります。それでも、アーキテクチャの観点からは、上記のシナリオはポップアップするべきではありません。これは、とにかくデザインの匂いに直面していることを意味します。

65
Oliver Drotbohm

cassandra= springデータを使用した汎用リポジトリーを作成するプロジェクトに取り組んでいます。

まず、コードを使用してリポジトリインターフェイスを作成します。

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());

Spring Data Runtimeでリポジトリを初期化します。 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);

これで、リポジトリのsaveメソッドを試すことができ、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に拡張できます。

1
Ye Ma