我有一个使用Java 8的spring boot(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对象的存储库,但只有在用户要求它们阻止不必要的连接语句时才会获取复杂字段。回购看起来像这样:
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对象的准系统调用将返回Entity,其中包含除car field之外的所有字段的值。如果用户想要汽车信息,那么他们必须明确地呼叫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实体上使用构建器模式创建repo的最佳方法是什么,而不是在尝试映射时调用DB。
答案 0 :(得分:6)
如果给定对象是代理并且未初始化,则可以创建一个返回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()));
答案 1 :(得分:3)
只需将其映射到新对象并省略Car关系,这是标准方法。您可以使用MapStruct并在映射期间忽略car field:http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings
答案 2 :(得分:3)
只是不要映射汽车......映射一个包含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);
}
}
答案 3 :(得分:2)
您可以在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 > {
}
答案 4 :(得分:1)
正如你所说
我不希望这种情况发生。它只会抓取值并将它们映射到等效的域,如果它不存在则为null。
然后你只需将它设置为null,因为 car 字段始终不存在。
否则,如果您的意思是不存在,那么该车在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()
时,请确保您拥有数据库会话答案 5 :(得分:1)
您可以尝试将状态添加到存储库并影响映射器。像这样:
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
答案 6 :(得分:0)
您可能希望使用PersistentUnitUtil类来查询实体对象的属性是否已加载。在此基础上,您可以跳过对相应getter的调用,如下所示。您需要提供给用户实体bean映射器的JpaContext。
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();
}
}