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