【问题标题】:How to fetch a field lazily with Hibernate Criteria如何使用 Hibernate Criteria 懒惰地获取字段
【发布时间】:2015-10-25 11:03:08
【问题描述】:

应读取表格Person(具有namefirstnameage)的每一行。

EntityManager em = emf.createEntityManager();
Session s = (Session) em.getDelegate();
Criteria criteria = s.createCriteria(Person.class);
criteria.setFetchMode("age", FetchMode.SELECT);

但是 SQL 显示

Hibernate:
    select
        person0_.name,
        person0_.firstname,
        person0_.age
    from 
        SCOPE.PERSON person0_

如何让年龄变得懒惰ONLY为Criteria??

【问题讨论】:

  • 我相信那是不可能的,但相反是的,让它在需要时延迟触发它的初始化。
  • 在您的Person 实体中添加到字段age 注释@Basic(fetch=FetchType.LAZY)。至少试一试。
  • @PawełGłowacz 我知道这种风格,但我不喜欢它。它应该是标准的一部分,而不是实体实施的一部分。
  • 不检索年龄是什么意思?线路中的字节更少?
  • @gabrielgiussi 是因为性能。年龄是按时区、夏令时、位置来计算的……比方说,计算年龄需要几秒钟。

标签: java hibernate orm lazy-loading fetch


【解决方案1】:

我想补充(或澄清)以下内容。给定具有属性类(客户)的主类(结算):

@Entity
@Table(name = "settlement") 
public class Settlement extends IdBasedObject {

...

    @OneToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "customer_id_fk")
    private Customer customer;
}

@Entity
@Table(name = "customer", schema = SchemaUtil.SCHEMA_COMMON)
public class Customer extends IdBasedObject {
    @Column(name = "organization_type")
    @Enumerated(EnumType.ORDINAL)
    private CompanyType companyType;

    @Column(name = "organization_legal_name")
    private String companyLegalName;

    ...
}

如果您想从 Settlement 中获取所有不同的客户,您可以在 'customer' 属性上使用 Projections distinct,然后从 Settlement 类创建一个别名:

    public List<Customer> findUniqueCustomer() throws Exception {
        Session session = super.getSessionFactory().openSession();

        ProjectionList projectionList = Projections.projectionList();
        projectionList.add(Projections.distinct(Projections.property("customer")));

        Criteria criteria = session.createCriteria(Settlement.class);
        criteria.setProjection(projectionList);
        criteria.createAlias("customer", "customer");

return criteria.list();
}

现在,如果您这样做,您将返回每个客户对象的非代理错误列表“无法初始化代理 - 无会话”。

幸运的是,criteria 提供了 setResultTransformer 函数,可以“重塑”返回值。

        criteria.setResultTransformer(new ResultTransformer() {
            
            @Override
            public Object transformTuple(Object[] tuple, String[] aliases) {
                Customer customerObject = (Customer) tuple[0];
                
                Customer customer = new Customer();
                customer.setId(customerObject.getId());
                customer.setVersion(customerObject.getVersion());
                customer.setCompanyType(customerObject.getCompanyType());
                 
             customer.setCompanyLegalName(customerObject.getCompanyLegalName());
             return customer;
             ...
          }

            @SuppressWarnings("rawtypes")
            @Override
            public List<Customer> transformList(List collection) {

                return collection;
            }
});

元组[0] 本质上包含客户对象的值,因为客户对象没有被代理,你会得到错误。在 transformTuple 函数中,您有机会“重新创建”每个客户对象,从而避免“非代理”错误。

请试一试。

【讨论】:

  • 1.从本质上讲,同一个客户可以有多个结算。 2.一个结算可以有多个客户。 3. 在您的第二个代码中,projectionList 从未使用过,因此它是一个死对象。 4. 既然您说要结算的客户是 1-1,您就不需要明确的预测。 5.通过resulttransformer急切地获取属性与“如何懒惰地获取”这个问题无关。你回答了“如何获取渴望”的问题。
  • 嗨@Grim。感谢您指出死代码。忘记在标准中包含设定的投影。只是为了澄清,有很多和解,他们有相同的客户。即,同一客户进行多次结算。以上是展示如何从定居点中清晰地提取客户列表。是的,你是对的,我试图展示如何获取“惰性”属性并使其“渴望”。干杯。
  • 为您服务。在您的课程Settlement 中,您使用OneToOne 注释对属性customer 进行了注释。但是您指出这是一个 1-n 关系。那么你应该使用ManyToOne/OneToMany注解而不是OneToOne注解。
【解决方案2】:

您可以简单地定义一个新实体 SimplePerson 映射到同一个 persons 数据库表,该表仅包含以下属性:

  • 身份证
  • 姓名
  • 名字

这种方式,在使用标准和HQL中选择SimplePerson时,将无法检索年龄列。

另一种选择是使用lazy loading for basic attributes,但将多个子实体映射到同一个数据库表要灵活得多。

【讨论】:

    【解决方案3】:

    您的推理是有效的(一般而言;但我们可以争论age 字段的具体示例),但不幸的是,对此没有直接的解决方案。实际上,Hibernate 有fetch profiles 的概念,但它目前非常有限(您只能使用连接样式的提取配置文件覆盖默认的提取计划/策略)。

    因此,您的问题的可能解决方法如下。

    1) 将age 移动到一个单独的实体,并将Person 实体与它以惰性的一对一关系关联:

    @Entity
    class PersonAge {
       private Integer age;
    }
    
    @Entity
    class Person {
       @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, optional = false)
       @JoinColumn(name = "PERSON_AGE_ID")
       private PersonAge personAge;
    
       public Integer getAge() {
          return personAge.getAge();
       }
    
       public void setAge(Integer age) {
          personAge.setAge(age);
       }
    }
    

    2) 定义一个覆盖默认配置文件的获取配置文件:

    @FetchProfile(name = "person-with-age", fetchOverrides = {
       @FetchProfile.FetchOverride(entity = Person.class, association = "personAge", mode = FetchMode.JOIN)
    })
    

    3) 为应用程序中的每个会话启用此配置文件:

    session.enableFetchProfile("person-with-age");
    

    根据您使用的框架,应该有一个简单的钩子/拦截器,您将使用它来为创建的每个会话(事务)启用配置文件。例如,Spring 中的一种方法可能是覆盖正在使用的事务管理器的AbstractPlatformTransactionManager.doBegin

    这样,personAge 将被急切地加载到应用程序的所有会话中,除非显式禁用提取配置文件。

    4) 禁用在您使用所需条件查询的会话中获取配置文件:

    session.disableFetchProfile("person-with-age");
    

    这种方式使用默认的获取计划/策略(在实体映射中指定),即PersonAge的延迟加载。

    【讨论】:

      【解决方案4】:

      在条件上设置“age”属性的 FetchMode 无效,因为此时的获取策略仅针对关联对象,而不针对属性。请参阅 hibernate 文档的 20.1. Fetching strategies 部分。

      Hibernate 使用获取策略来检索关联对象 如果应用程序需要导航关联。获取策略 可以在 O/R 映射元数据中声明,或者被 特定 HQL 或 Criteria 查询。

      延迟加载属性的唯一方法是将@Basic 注释设置为FetchType.LAZY。请参阅 here,或者如果您使用 .hbm.xml 文件进行映射,请使用 lazy=true,请参阅休眠文档的 this 部分。

      @Basic 注释允许您声明 获取策略 属性。如果设置为 LAZY,则指定此属性应为 首次访问实例变量时延迟获取。它 如果您的类不是,则需要构建时字节码检测 检测,属性级别的延迟加载被静默忽略。

      属性的延迟加载也使用 buildtime 字节码工具(hibernate 在编译后更改实体类以允许延迟加载属性)。阅读20.1.8. Using lazy property fetching

      解决您的问题的另一种可能的解决方案(除了所有其他解决方案)是创建一个更简单的 Person 类并使用 constructor query ,例如:

      public class PersonDTO {
          private String name;
          private String firstname;
      
          private Person(String name, String firstname) {
              this.name = name;
              this.firstname = firstname;
          }
          // getters & setters
      }
      
      Query q = session.createQuery("select new your.package.name.PersonDTO("
          + "p.name, p.firstname) from Person p");
      q.list();
      

      您甚至可以使用现有的 Person 类,只需使用适当的构造函数对其进行扩展,但我更喜欢明确性。

      但是这里介绍的所有解决方案都没有实现age 属性的延迟加载。这样做的唯一方法是@Basicannotation,否则您必须实现自己的延迟加载。

      【讨论】:

        【解决方案5】:

        如果您的年龄是 @Dragan 的 PersonAge 之类的对象,您可以将 fecth 模式与条件相关联,而不是像您那样与实体相关联。

        所以,我认为你有三个选择:

        1. 年龄就像@Paco 所说的原始和投影(Person.age 将是 null 而不是代理,你会失去你想要的懒惰)
        2. 年龄作为没有投影的原始数据(线路中有更多字节)
        3. age as PersonAge + criteria.setFetchMode(以额外的对象/表/映射为代价获得所需的惰性)

        对于投影,您可以使用 ResultTransformer 来

        Criteria crit = session.createCriteria(Person.class);
        ProjectionList projList = Projections.projectionList();
        projList.add(Projections.property("name"));
        projList.add(Projections.property("firstname"));
        crit.setProjection(projList);
        crit.setResultTransformer(new ResultTransformer() {
        
              @Override
              public Object transformTuple(Object[] tuple, String[] aliases) {
                String name = (Long) tuple[0];
                String firstName = (String) tuple[1];
                return new Person(name , firstName);
              }
        
              @Override
              public List<Reference> transformList(List collection) {
                return collection;
              }
            });
        

        我认为您可以自己创建一个 PersonProxy 来触发检索年龄的查询,但这有点糟糕。

          @Override
          public Object transformTuple(Object[] tuple, String[] aliases) {
            String name = (Long) tuple[0];
            String firstName = (String) tuple[1];
            return new PersonProxy(name , firstName);
          }
        
          class PersonProxy {
            Person realPerson;
        
            public getAge(){
               // create a query with realPerson.id for retrieve the age. 
            }
          }
        

        【讨论】:

          【解决方案6】:

          我认为惰性模式只对关联有意义。如果您访问的是普通表,它将加载所有字段。

          如果您希望age 字段不出现在 SQL 中,因此不被加载到内存中,请使用投影:

          Criteria crit = session.createCriteria(Person.class);
          ProjectionList projList = Projections.projectionList();
          projList.add(Projections.property("name"));
          projList.add(Projections.property("firstname"));
          crit.setProjection(projList);
          

          【讨论】:

          • 顺便说一句:此条件将生成一个 List,其中每个 Object[] 包含 namefirstname 的 2 个字符串。
          • 使用这种方法你应该看看 ResultTransformer。
          猜你喜欢
          • 2011-01-13
          • 2016-11-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多