代码之家  ›  专栏  ›  技术社区  ›  KCL

让一对一的关系变得懒惰

  •  190
  • KCL  · 技术社区  · 16 年前

    在我们正在开发的这个应用程序中,我们注意到视图的速度特别慢。我对视图进行了分析,发现Hibernate执行了一个查询,即使数据库中只有两个对象要获取,也需要10秒钟。所有 OneToMany ManyToMany 关系很懒惰,所以这不是问题所在。在检查实际执行的SQL时,我注意到查询中有超过80个联接。

    进一步检查这个问题,我注意到这个问题是由 OneToOne ManyToOne 实体类之间的关系。所以,我想,我只会让他们懒惰,这样就能解决问题。但也要注释 @OneToOne(fetch=FetchType.LAZY) @ManyToOne(fetch=FetchType.LAZY) 似乎不起作用。要么我得到一个异常,要么它们实际上并没有被代理对象替换,从而变得懒惰。

    你知道我该怎么做吗?请注意,我不使用 persistence.xml 为了定义关系或配置细节,一切都是用Java代码完成的。

    6 回复  |  直到 6 年前
        1
  •  191
  •   Christoph Böhme    6 年前

    首先,对 克尔 答案是:

    1. 不受约束(可以为空)的一对一关联是唯一一个在没有字节码检测的情况下无法代理的关联。这样做的原因是所有者实体必须知道关联属性应该包含代理对象还是NULL,并且由于一对一通常是通过共享pk映射的,因此无法通过查看其基表的列来确定这一点,因此无论如何都必须急切地获取它,使代理毫无意义。这里有一个 more detailed 说明。

    2. 许多对一的协会(以及一对多的协会,显然)不受这个问题的影响。所有者实体可以很容易地检查自己的FK(如果是一对多,则最初创建空的集合代理并按需填充),因此关联可能比较懒惰。

    3. 用一对多替换一对一从来都不是一个好主意。您可以用唯一的多对一替换它,但还有其他(可能更好)选项。

    罗布·H 有一个有效的点,但是您可能无法根据您的模型实现它(例如,如果您的一对一关联 可以为空)。

    现在,就最初的问题而言:

    a) @ManyToOne(fetch=FetchType.LAZY) 应该工作得很好。您确定查询本身没有覆盖它吗?可以具体说明 join fetch 在hql和/或通过标准api显式设置fetch模式,这将优先于类注释。如果情况并非如此,而且您仍然有问题,请发布类、查询和结果SQL以获得更多的对点对话。

    b)安装 @OneToOne 更棘手。如果绝对不能为空,请按照Rob H.的建议进行说明:

    @OneToOne(optional = false, fetch = FetchType.LAZY)
    

    否则,如果可以更改数据库(将外键列添加到所有者表中),请执行此操作并将其映射为“joined”:

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="other_entity_fk")
    public OtherEntity getOther()
    

    在其他实体中:

    @OneToOne(mappedBy = "other")
    public OwnerEntity getOwner()
    

    如果你不能做到这一点(并且不能忍受急切的获取),那么字节码检测是你唯一的选择。我必须同意 卡普金斯 但是-如果你有 80!!!! 由于渴望一对一关联而加入,因此您遇到了更大的问题,如下所示:—)

        2
  •  17
  •   Kdeveloper    15 年前

    要让懒惰的加载处理可为空的一对一映射,需要让Hibernate这样做。 compile time instrumentation 添加一个 @LazyToOne(value = LazyToOneOption.NO_PROXY) 一对一的关系。

    示例映射:

    @OneToOne(fetch = FetchType.LAZY)  
    @JoinColumn(name="other_entity_fk")
    @LazyToOne(value = LazyToOneOption.NO_PROXY)
    public OtherEntity getOther()
    

    示例Ant构建文件扩展名(用于执行休眠编译时检测):

    <property name="src" value="/your/src/directory"/><!-- path of the source files --> 
    <property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
    <property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 
    
    <fileset id="applibs" dir="${libs}"> 
      <include name="hibernate3.jar" /> 
      <!-- include any other libraries you'll need here --> 
    </fileset> 
    
    <target name="compile"> 
      <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
        <classpath> 
          <fileset refid="applibs"/> 
        </classpath> 
      </javac> 
    </target> 
    
    <target name="instrument" depends="compile"> 
      <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
        <classpath> 
          <fileset refid="applibs"/> 
        </classpath> 
      </taskdef> 
    
      <instrument verbose="true"> 
        <fileset dir="${destination}"> 
          <!-- substitute the package where you keep your domain objs --> 
          <include name="/com/mycompany/domainobjects/*.class"/> 
        </fileset> 
      </instrument> 
    </target>
    
        3
  •  10
  •   KLE rslite    16 年前

    在冬眠中,除了Xtoones之外,最基本的想法是他们在大多数情况下并不懒惰。

    一个原因是,当Hibernate必须决定放置一个代理(带有ID)或一个空值时,
    不管怎样,它必须查另一张桌子 加入。访问数据库中的另一个表的成本是很高的,因此它可能会在此时获取该表的数据(非惰性行为),而不是在以后的请求中获取该数据,后者需要对同一个表进行第二次访问。

    编辑:详情请参阅CHSSPLY76的回答。 . 这个不太准确,也不详细,没有什么可提供的。感谢CHSSPLY76。

        4
  •  8
  •   acdcjunior Mukul Kumar    11 年前

    以下是一些对我有用的东西(没有仪器):

    而不是使用 @OneToOne 两边,我用 @OneToMany 在关系的相反部分(与 mappedBy )使属性成为集合( List 在下面的示例中),但是我将它转换为getter中的一个项,使其对客户机透明。

    此设置工作缓慢,也就是说,只有在 getPrevious() getNext() 被称为-而且只有 为每个呼叫选择。

    表格结构:

    CREATE TABLE `TB_ISSUE` (
        `ID`            INT(9) NOT NULL AUTO_INCREMENT,
        `NAME`          VARCHAR(255) NULL,
        `PREVIOUS`      DECIMAL(9,2) NULL
        CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
    );
    ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                     FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);
    

    班级:

    @Entity
    @Table(name = "TB_ISSUE") 
    public class Issue {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        protected Integer id;
    
        @Column
        private String name;
    
        @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
        @JoinColumn(name="previous")
        private Issue previous;
    
        // use @OneToMany instead of @OneToOne to "fake" the lazy loading
        @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
        // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
        private List<Issue> next;
    
        public Integer getId() { return id; }
        public String getName() { return name; }
    
        public Issue getPrevious() { return previous; }
        // in the getter, transform the collection into an Issue for the clients
        public Issue getNext() { return next.isEmpty() ? null : next.get(0); }
    
    }
    
        5
  •  5
  •   Rob H    16 年前

    在本机Hibernate XML映射中,可以通过声明 one-to-one 映射 约束的 属性设置为true。我不确定Hibernate/JPA注释与之等效的是什么,快速搜索文档没有提供任何答案,但希望这能给您一个继续下去的线索。

        6
  •  3
  •   Pino    13 年前

    正如chssply76已经很好地解释的那样,Hibernate的代理对不受约束(可以为空)的一对一关联没有帮助,但是有一个技巧可以解释。 here 避免设置仪表。我们的想法是愚弄Hibernate,我们要使用的实体类已经被检测到:您可以在源代码中手动检测它。很简单!我已经使用cglib作为字节码提供程序实现了它,并且它可以工作(确保在您的HBM中配置lazy=“no proxy”和fetch=“select”,而不是“join”)。

    我认为这是一个很好的选择 真实的 (我的意思是自动的)仪表化,当你只有一对一的空关系,你想让它变懒。主要的缺点是,解决方案取决于您使用的字节码提供程序,因此请准确地评论您的类,因为您将来可能需要更改字节码提供程序;当然,出于技术原因,您也在修改模型bean,这并不好。