Java JPA防止代理调用db

时间:2017-09-22 20:21:09

标签: java spring hibernate jpa spring-boot

我有一个使用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。

7 个答案:

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