Java JPA Предотвращение прокси от вызова db

У меня есть проект spring boot (1.5.4.RELEASE) с использованием Java 8. У меня есть сущность и связанный с ней класс домена следующим образом:

@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 из БД, но только извлекает сложные поля, если пользователь просит их предотвратить ненужные заявления о соединении. Репо выглядит следующим образом:

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

Таким образом, вызов barebone для объекта Foo возвращает объект Entity со значениями для всех полей, за исключением поля автомобиля. Если пользователь хочет получить информацию о машине, то они должны явно называть 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(), если значение не существует (т.е. присутствует прокси). JPA выйдет и выберет его для вас. Я не хочу, чтобы это было так. Он просто захватит значения и сопоставляет их эквиваленту домена, если он там не будет null.

Одно из решений, которое я слышал (и пыталось), вызывает em.detach(entity);, однако это не работает так, как я думал, потому что он генерирует исключение при попытке доступа к getCar, и я также слышал, что это не лучшая практика.

Итак, мой вопрос - это лучший способ создать репо с использованием шаблона построителя на сущности JPA и не вызвать вызов DB при попытке сопоставить.

Ответ 1

Вы можете создать метод утилиты, который вернет 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()));

Ответ 2

Просто сопоставьте его с новым объектом и оставьте отношение Car, это стандартный подход. Вы можете использовать MapStruct и просто игнорировать поле автомобиля во время отображения: http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings

Ответ 3

Просто не сопоставляйте автомобиль... Составьте поле с идентификатором и используйте другой метод, чтобы получить фактический автомобиль. Я бы использовал своеобразное имя метода, чтобы отличить его от других геттеров.

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

Ответ 4

Вы можете написать запрос поверх JPA

@Query("select u from Car c")



import org.springframework.data.repository.CrudRepository;

import com.example.model.FluentEntity;

public interface DatabaseEntityRepository extends CrudRepository<FooEntity , int > {

}

Ответ 5

Как вы сказали

Я не хочу, чтобы это было так. Он просто захватит значения и сопоставляет их эквиваленту домена, если он там нет.

Тогда вы просто установите его равным 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;
         };
     }
 }

Убедитесь, что у вас есть сеанс db, когда вы вызываете Foo.getCar()

Ответ 6

Вы можете попробовать добавить состояние в свой репозиторий и повлиять на карту. Что-то вроде этого:

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

Если вы используете new FooRepository().getFooByName("example").fetch() без вызова withCar(), e.getCar() не следует оценивать внутри FooMapper

Ответ 7

Вы можете использовать класс PersistentUnitUtil для запроса, если атрибут объекта объекта уже загружен или нет. Исходя из этого, вы можете пропустить вызов соответствующего получателя, как показано ниже. JpaContext, который необходимо предоставить в пользовательский объект bean mapper.

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