Java JPAがプロキシからdbを呼び出さないようにする




spring boot spring security jwt mysql (5)

PersistentUnitUtilクラスを使用して、エンティティオブジェクトの属性がすでにロードされているかどうかを問い合わせることができます。 それに基づいて、以下に示すように、対応するgetterへの呼び出しをスキップすることができます。 JpaContextはユーザーエンティティBeanマッパーに提供する必要があります。

public class FooMapper {
    public Function<FooEntity, Foo> mapEntityToDomain(JpaContext context) {
        PersistenceUnitUtil putil = obtainPersistentUtilFor(context, FooEntity.class);
        return e -> {
            return new Foo(
                    e.getId(),
                    e.getName(),
                    e.getType(),
                    e.getColor(),
                    putil.isLoaded(e, "car") ? e.getCar() : null);
        };
    }

    private PersistenceUnitUtil obtainPersistentUtilFor(JpaContext context, Class<?> entity) {
        return context.getEntityManagerByManagedType(entity)
                .getEntityManagerFactory()
                .getPersistenceUnitUtil();
    }
}

私は、Java 8を使ったスプリングブート(1.5.4.RELEASE)プロジェクトを持っています。私はエンティティを持っていて、それはこのような関連ドメインクラスです:

@Entity
@Table(name = "Foo", schema = "dbo")
public class FooEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Id")
    private int id;

    @Column(name="Name")
    private String name;

    @Column(name="Type")
    private String type;

    @Column(name="Color")
    private String color;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "Car")
    private Car car;

    //getter and setter
}

public class Foo {
    private int id;
    private String name;
    private String type;
    private String color;
    private Car car;

    //Constructors and getters
}

このFooオブジェクトをDBから取得するリポジトリを作成したいのですが、不要な結合ステートメントを防ぐためにユーザーが要求した場合にのみ複雑なフィールドを取得します。 レポは次のようになります。

import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;

@Repository
public class FooRepository {
    private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);

    public FooRepository getFooByName(String name) {
        query.where(fooEntity.name.eq(name));
        return this;
    }

    public FooRepository withCar() {
        query.leftJoin(fooEntity.car, carEntity).fetchJoin();
        return this;
    }

    public Foo fetch() {
        FooEntity entity = query.fetchOne();
        return FooMapper.mapEntityToDomain().apply(entity);
    }
}

そのため、Fooオブジェクトを呼び出すための最低限の呼び出しは、carフィールドを除くすべてのフィールドの値を含むEntityを返します。 ユーザーが車の情報を希望する場合は、 withCarを明示的に呼び出すwithCarます。

これがマッパーです。

public class FooMapper {
    public static Function<FooEntity, Foo> mapEntityToDomain() {
        return entity -> { 
            return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), e.getCar());
        };
    }
}

問題は、値が存在しない場合(つまりプロキシが存在する場合e.getCar()を実行したときですe.getCar()はそれを行ってそれを取得します。 私はこれがそうであることを望まない。 値が取得され、それが存在しない場合は同等のドメインにマッピングされnull

私が聞いた(そして試した)一つの解決策はem.detach(entity);呼び出すことem.detach(entity); ただし、 getCarにアクセスしようとすると例外がスローされるため、これは私の意図したとおりには機能しません。また、これはベストプラクティスではないと聞きました。

だから私の質問は、JPAエンティティのビルダーパターンを使ってリポジトリを作成し、マッピングしようとするときにDBを呼び出さないようにする最良の方法は何なのかということです。


あなたが言ったように

私はこれがそうであることを望まない。 値が取得され、それが同等のドメインにマッピングされます(存在しない場合はnull)。

それからあなたはただそれをnullに設定します、なぜならフィールドカーは常にそこにあるわけではないからです。

そうでなければ、もしあなたがその車がdbに存在しないということがないのなら、確かに副問い合わせ(プロキシを呼び出す)を作らなければなりません。

Foo.getCar()を呼び出すときに車をつかみたい場合。

 class Car {

 }

 class FooEntity {

     private Car car;//when call getCar() it will call the proxy.

     public Car getCar() {
          return car;
     }
 }

 class Foo {
     private java.util.function.Supplier<Car> carSupplier;


     public void setCar(java.util.function.Supplier<Car> carSupplier) {
         this.carSupplier = carSupplier;
     }

     public Car getCar() {
         return carSupplier.get();
     }
 }

 class FooMapper {
     public static Function<FooEntity, Foo> mapEntityToDomain() {
         return (FooEntity e) -> {
             Foo foo = new Foo();
             foo.setCar(e::getCar);
             return foo;
         };
     }
 }

Foo.getCar()を呼び出すときは、dbセッションがあることを確認してください。


リポジトリに状態を追加してマッパーに影響を与えることができます。 このようなもの:

import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;

@Repository
public class FooRepository {
    private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
    private boolean withCar = false;

    public FooRepository getFooByName(String name) {
        query.where(fooEntity.name.eq(name));
        return this;
    }

    public FooRepository withCar() {
        query.leftJoin(fooEntity.car, carEntity).fetchJoin();
        withCar = true;
        return this;
    }

    public Foo fetch() {
        FooEntity entity = query.fetchOne();
        return FooMapper.mapEntityToDomain(withCar).apply(entity);
    }
}

あなたのマッパーでは、次に自動車検索を有効または無効にするためのスイッチを含めます。

public class FooMapper {
    public static Function<FooEntity, Foo> mapEntityToDomain(boolean withCar) {
        return e -> { 
            return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), withCar ? e.getCar() : null);
        };
    }
}

その後、 withCar()呼び出さずにnew FooRepository().getFooByName("example").fetch()を使用する場合、 e.getCar()FooMapper内で評価しないでFooMapper


与えられたオブジェクトがプロキシで初期化されていない場合にnullを返すユーティリティメソッドを作成することができます。

public static <T> T nullIfNotInitialized(T entity) {
    return Hibernate.isInitialized(entity) ? entity : null;
}

それからあなたはそれを必要とするところならどこでもメソッドを呼ぶことができます:

return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), nullIfNotInitialized(e.getCar()));

車をマッピングしないでください... IDを保持しているフィールドをマッピングし、別の方法を使用して実際の車を取得します。 他のゲッターと区別するために、独自のメソッド名を使用します。

class FooEntity {
    @Column
    private int carId;

    public int getCarId() { 
        return carId; 
    }

    public void setCarId(int id) { 
        this.carId = id; 
    }

    public Car fetchCar(CarRepository repo) {
        return repo.findById(carId);
    }         
}




spring-boot