为什么 Hibernate 会为 @ManyToOne 关联的隐式连接生成 CROSS JOIN?
Why does Hibernate generate a CROSS JOIN for an implicit join of a @ManyToOne association?
Baur & King 在他们的书中说:
Implicit joins are always directed along many-to-one or one-to-one association, never through a collection-valued association.
[P 646, Ch 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();
}
}
}
大多数数据库引擎会将带有 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
和辅助 select,同时使用绑定参数而不是硬编码参数。
Baur & King 在他们的书中说:
Implicit joins are always directed along many-to-one or one-to-one association, never through a collection-valued association.
[P 646, Ch 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();
}
}
}
大多数数据库引擎会将带有 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
和辅助 select,同时使用绑定参数而不是硬编码参数。