【问题标题】:Java JPA Preventing Proxies from calling dbJava JPA 防止代理调用 db
【发布时间】:2018-03-04 12:02:33
【问题描述】:

我有一个使用 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 对象的准系统调用将返回具有除汽车字段之外的所有字段值的实体。如果用户想要汽车信息,那么他们必须明确调用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 实体上使用构建器模式创建存储库的最佳方法是什么,并且在尝试映射时不让它调用数据库。

【问题讨论】:

  • 你在使用jta datasource ??
  • 你正在尝试做的事情非常可疑......如果你不想要这种功能,为什么要使用 Hibernate?
  • 那里有很奇怪的存储库。只工作一次还是为每个查询创建新的存储库?

标签: java spring hibernate jpa spring-boot


【解决方案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 并在映射过程中忽略 car 字段:http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings

    【讨论】:

      【解决方案3】:

      只是不要映射汽车...映射一个包含 ID 的字段并使用另一种方法来获取实际的汽车。我会使用一个独特的方法名称,以将其与其他 getter 区分开来。

      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。

          然后您只需将其设置为 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() 时拥有 db 会话

          【讨论】:

            【解决方案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(),则不应在 FooMapper 内评估 e.getCar()

            【讨论】:

              【解决方案7】:

              您可能想使用 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();
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 2020-04-05
                • 1970-01-01
                • 2016-11-14
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-08-08
                • 1970-01-01
                相关资源
                最近更新 更多