为啥 Hibernate 会为 @ManyToOne 关联的隐式连接生成 CROSS JOIN?

Posted

技术标签:

【中文标题】为啥 Hibernate 会为 @ManyToOne 关联的隐式连接生成 CROSS JOIN?【英文标题】:Why does Hibernate generate a CROSS JOIN for an implicit join of a @ManyToOne association?为什么 Hibernate 会为 @ManyToOne 关联的隐式连接生成 CROSS JOIN? 【发布时间】:2015-06-28 01:12:18 【问题描述】:

Baur & King 在他们的书中说:

隐式连接始终沿多对一或一对一关联进行,从不通过集合值关联。

[P 646,第 14 章]

但是当我在代码中这样做时,它会生成一个 CROSS JOIN 而不是一个 INNER JOIN。

映射来自Member2(多对一)-> CLub

但是Club2 没有关于成员的信息,并且Member2 有一个 Club2 的外键。

我的查询是

// Implicit: Find all UK club member who is female
Transaction t1 = HibernateUtil.begin();
Query query =
    HibernateUtil.getSession().createQuery("From Member2 m where m.club2.country = 'UK' ");
List<Member2> memList = query.list();
for (Member2 m : memList)
  System.out.println(m);
HibernateUtil.end(t1);

而且,Hibernate 正在生成以下 SQL 查询:

Hibernate: 
    select
        member2x0_.member_id as member_i1_1_,
        member2x0_.club_id as club_id5_1_,
        member2x0_.member_age as member_a2_1_,
        member2x0_.member_name as member_n3_1_,
        member2x0_.member_sex as member_s4_1_ 
    from
        TBL_MEMBER2 member2x0_ cross 
    join
        TBL_CLUB2 club2x1_ 
    where
        member2x0_.club_id=club2x1_.club_id 
        and club2x1_.country='UK'
Hibernate: 
    select
        club2x0_.club_id as club_id1_0_0_,
        club2x0_.club_name as club_nam2_0_0_,
        club2x0_.country as country3_0_0_ 
    from
        TBL_CLUB2 club2x0_ 
    where
        club2x0_.club_id=?
aaa 25 m
bbb 28 f

Club2.java

package com.lilu.de.onetomany.uni.other;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

@Entity
@Table(name = "TBL_CLUB2")
public class Club2 

  @GeneratedValue(generator = "pkey_Club2", strategy = GenerationType.SEQUENCE)
  @SequenceGenerator(name = "pkey_Club2", initialValue = 1000, allocationSize = 10,
      sequenceName = "seq_pkey_Club2")
  @Id
  private int club_id;

  private String club_name;

  private String country;

  // private Set Member2 = new HashSet();

  public Club2() 
    super();
  

  public Club2(String cname, String ccountry) 
    this.club_name = cname;
    this.country = ccountry;
  

  @Override
  public String toString() 
    String temp = club_name + " " + country + " ";
    // Iterator<Member2> iter = Member2.iterator();
    // String mems = null;
    // while (iter.hasNext()) 
    // mems += iter.next();
    // 
    // temp += "\n" + mems;
    return temp;
  

  public int getClub_id() 
    return club_id;
  

  public void setClub_id(int club_id) 
    this.club_id = club_id;
  

  public String getClub_name() 
    return club_name;
  

  public void setClub_name(String club_name) 
    this.club_name = club_name;
  

  public String getCountry() 
    return country;
  

  public void setCountry(String country) 
    this.country = country;
  

  /*
   * public Set<Member2> getMember2()  return Member2; 
   * 
   * public void setMember2(Set<Member2> Member2)  this.Member2 = Member2; 
   */

Member2.java

package com.lilu.de.onetomany.uni.other;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

@Entity
@Table(name = "TBL_MEMBER2")
public class Member2 

  @GeneratedValue(generator = "pkey_member2", strategy = GenerationType.SEQUENCE)
  @SequenceGenerator(name = "pkey_member2", sequenceName = "seq_pkey_member2")
  @Id
  private int member_id;

  private String member_name;

  private int member_age;

  private char member_sex;

  @ManyToOne(cascade = CascadeType.ALL)
  @JoinColumn(name = "club_id")
  private Club2 club2;

  public Member2() 
    super();
  

  public Member2(String mname, int age, char sex) 
    this.member_name = mname;
    this.member_age = age;
    this.member_sex = sex;
  

  public Club2 getClub2() 
    return club2;
  

  public void setClub2(Club2 club2) 
    this.club2 = club2;
  

  @Override
  public String toString() 
    return member_name + " " + member_age + " " + member_sex;
  

  public String getMember_name() 
    return member_name;
  

  public void setMember_name(String member_name) 
    this.member_name = member_name;
  

  public int getMember_age() 
    return member_age;
  

  public void setMember_age(int member_age) 
    this.member_age = member_age;
  

  public char getMember_sex() 
    return member_sex;
  

  public void setMember_sex(char member_sex) 
    this.member_sex = member_sex;
  

  public int getMember_id() 
    return member_id;
  

  public void setMember_id(int member_id) 
    this.member_id = member_id;
  


hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!--
  ~ Hibernate, Relational Persistence for Idiomatic Java
  ~
  ~ Copyright (c) 2010, Red Hat Inc. or third-party contributors as
  ~ indicated by the @author tags or express copyright attribution
  ~ statements applied by the authors.  All third-party contributions are
  ~ distributed under license by Red Hat Inc.
  ~
  ~ This copyrighted material is made available to anyone wishing to use, modify,
  ~ copy, or redistribute it subject to the terms and conditions of the GNU
  ~ Lesser General Public License, as published by the Free Software Foundation.
  ~
  ~ This program is distributed in the hope that it will be useful,
  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  ~ or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
  ~ for more details.
  ~
  ~ You should have received a copy of the GNU Lesser General Public License
  ~ along with this distribution; if not, write to:
  ~ Free Software Foundation, Inc.
  ~ 51 Franklin Street, Fifth Floor
  ~ Boston, MA  02110-1301  USA
  -->
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.postgresql.Driver</property>
        <property name="connection.url">jdbc:postgresql://localhost:5432/hibernatedb1</property>
        <property name="connection.username">postgres</property>
        <property name="connection.password">ani155</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        <property name="format_sql">true</property> 

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">update</property>

        <!-- Names the annotated entity class -->
        <mapping class="com.lilu.de.onetomany.uni.other.Club2"/>
        <mapping class="com.lilu.de.onetomany.uni.other.Member2"/>
       <!--   <mapping class="com.apal.mapping.onetoone.User"/> -->
    </session-factory>

</hibernate-configuration>

Test.java

package com.lilu.de.onetomany.uni.other;

import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class Test 
  public static void main(String[] args) 

    Test test = new Test();
    // test.setup();
    test.SelectQuery1Implicit();
    // test.SelectQuery2ExplicitFromClause();
    // test.SelectQuery3JoinFetch();
    // test.SelectQuery4ThetaJoin();
  

  private void SelectQuery1Implicit() 
    // Implicit: Find all UK club member who is female
    Transaction t1 = HibernateUtil.begin();
    Query query =
        HibernateUtil.getSession().createQuery("From Member2 m where m.club2.country = 'UK' ");
    List<Member2> memList = query.list();
    for (Member2 m : memList)
      System.out.println(m);
    HibernateUtil.end(t1);
  

  private void setup() 
    Transaction t1 = HibernateUtil.begin();
    Club2 c1 = new Club2("MulaRougne", "UK");
    Member2 m1 = new Member2("aaa", 25, 'm');
    Member2 m2 = new Member2("bbb", 28, 'f');
    m1.setClub2(c1);
    m2.setClub2(c1);
    HibernateUtil.getSession().save(c1);
    HibernateUtil.getSession().save(m1);
    HibernateUtil.getSession().save(m2);
    Club2 c2 = new Club2("Queen's Club", "UK");
    Club2 c3 = new Club2("Disney", "USA");
    Member2 m3 = new Member2("ccc", 32, 'm');
    Member2 m4 = new Member2("ddd", 23, 'm');
    m3.setClub2(c3);
    m4.setClub2(c3);
    HibernateUtil.getSession().save(m3);
    HibernateUtil.getSession().save(m4);
    HibernateUtil.getSession().save(c2);
    HibernateUtil.getSession().save(c3);


    /*
     * Club2 c1 = new Club2("MulaRougne", "UK"); Club2 c2 = new Club2("Queen's Club", "UK"); Club2
     * c3 = new Club2("Disney", "USA");
     * 
     * Member2 m1 = new Member2("aaa", 25, 'm'); Member2 m2 = new Member2("bbb", 28, 'f'); Member2
     * m3 = new Member2("ccc", 32, 'm'); Member2 m4 = new Member2("ddd", 23, 'm'); Member2 m5 = new
     * Member2("ee", 30, 'f');
     * 
     * c1.getMember2().add(m1); c1.getMember2().add(m2); c1.getMember2().add(m3);
     * 
     * c2.getMember2().add(m4); c2.getMember2().add(m5);
     * 
     * HibernateUtil.getSession().save(c2); HibernateUtil.getSession().save(m4);
     * HibernateUtil.getSession().save(m5);
     * 
     * HibernateUtil.getSession().save(c1); HibernateUtil.getSession().save(m1);
     * HibernateUtil.getSession().save(m2); HibernateUtil.getSession().save(m3);
     */
    HibernateUtil.end(t1);
  


  private static class HibernateUtil 
    private static SessionFactory factory;
    private static Session session;
    static 
      factory = new Configuration().configure().buildSessionFactory();
    

    public static Session getSession() 
      return session;
    

    public static Transaction begin() 
      session = factory.openSession();
      return session.beginTransaction();
    

    public static void end(Transaction tran) 
      tran.commit();
    
  

【问题讨论】:

已尝试 HQL 为:From Member2 as m inner join m.club2 as c where c.country = 'UK' 有什么问题? where 子句有 where member2x0_.club_id=club2x1_.club_id,所以 Hibernate 正确地从成员的俱乐部中检索。一切都很好。 是 Hib 正在正确检索数据,但它正在执行交叉连接而不是内部连接。我们知道交叉连接会做一个缓慢且消耗内存的交叉产品。不应该做一个内部连接吗?这是我关心的问题。感谢您的回复。 @Amogh 是的,我知道我们可以在 From 子句中做一个 ecplicit 内连接。根据这本书,hib 应该为隐式连接生成一个内部连接。这里没有发生这让我感到惊讶并想知道我错过了什么? 【参考方案1】:

无论如何,大多数数据库引擎都会使用 WHERE 子句将 CROSS JOIN 优化为 JOIN,但我更喜欢始终使用显式 JOIN

CROSS JOIN 是由JOIN 生成的:

where m.club2.country = 'UK'

为避免第二个 Club 查询,您可以编写如下查询:

Query query = session.createQuery("""
        select m 
        from Member2 m
        join fetch m.club2 c
        where
           c.country = :country
        """, Member2.class)
.setParameter("country", "UK");

此查询将删除CROSS JOIN 和辅助选择,同时使用绑定参数而不是硬编码参数。

【讨论】:

您好,谢谢您的回答。我知道加入获取。但是您清除了 hib 无法生成内部连接,这令人惊讶。将再试一次。看到你的博客,它是一颗宝石。 感谢您的欣赏。希望您阅读愉快。 当然。我要把它全部打印出来。 所有的文章很快就会被整理成一本书。敬请期待! @EmmanuelOgoma 如果您有新问题,请作为实际问题提出

以上是关于为啥 Hibernate 会为 @ManyToOne 关联的隐式连接生成 CROSS JOIN?的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate/JPA 多对一与单对多

为啥 XamlWriter 会为某些属性添加 ?

如果 XIB 中有更多***视图,为啥 outlet 会为零

为啥 Xcode 会为选择器添加自动完成方法?

为啥 django 1.7 会为字段选择的变化创建迁移?

为啥此代码会为大输入提供分段错误