我有两个类,Test2和Test3。 Test2有一个属性test3,它是Test3的一个实例。换句话说,我有一个单向的OneToOne关联,test2引用了test3。
当我从db中选择Test2时,我可以看到正在进行单独的选择以获取相关test3类的详细信息。这是着名的1 + N选择问题。
要修复此问题以使用单个选择,我尝试使用fetch = join注释,我理解为@Fetch(FetchMode.JOIN)
但是,如果将fetch设置为join,我仍会看到单独的选择。以下是我设置的相关部分..
hibernate.cfg.xml中:
<property name="max_fetch_depth">2</property>
的Test2:
public class Test2 {
@OneToOne (cascade=CascadeType.ALL , fetch=FetchType.EAGER)
@JoinColumn (name="test3_id")
@Fetch(FetchMode.JOIN)
public Test3 getTest3() {
return test3;
}
注意我将FetchType设置为绝望,尽管对于OneToOne映射它默认为EAGER,但它没有任何区别。
感谢您的帮助!
编辑:我几乎放弃了尝试使用FetchMode.JOIN - 任何人都可以确认他们已经让它工作,即产生左外连接? 在文档中,我看到“通常,映射文档不用于自定义提取。相反,我们保留默认行为,并使用HQL中的左连接提取覆盖特定事务”
如果我执行左连接提取:
query = session.createQuery(“来自Test2 t2 left join fetch t2.test3”);
然后我确实得到了我想要的结果 - 即查询中的左外连接。
编辑编号2:
伙计们,非常感谢您的回复。现在我想深究这一点。我经常发现,当我调查某些事情时,我的学习结果比我想象的要多得多。
我已经学到了一件事 - 我正在运行旧版本的hibernate,因为我没有意识到maven存储库已经过时了。现在我也加入了jboss存储库,我有最新版本的hibernate和hibernate注释 - 在两种情况下都是3.5.1-Final。
我已经设置了一个小的测试用例,尽可能地简化它 - 我仍然在3.5.1-Final中看到问题,因为'我99%肯定这只是一些愚蠢的我是没有正确的设置,特别是罗斯,因为你得到它的工作(感谢花时间尝试顺便说一句)
所以我有这些课程(这次是全文)
A类
package com.play.hibernate2;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
@Entity
public class A {
private Integer id;
private B b;
public A() {
super();
}
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@OneToOne (cascade=CascadeType.ALL)
@Fetch(FetchMode.JOIN)
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
B类
package com.play.hibernate2;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class B {
private Integer id;
public B() {
super();
}
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
我的整个hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- <property name="connection.driver_class">com.p6spy.engine.spy.P6SpyDriver</property> -->
<property name="connection.url">jdbc:mysql://localhost:3306/play</property>
<property name="connection.username">play</property>
<property name="connection.password">play</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="generate_statistics">true</property>
<!--
<property name="cache.use_structured_entries">true</property>
<property name="cache.use_query_cache">true</property>
-->
<property name="format_sql">true</property>
<property name="use_sql_comments">true</property>
<!-- I think this may fix my individual requests for OneToOne problem -->
<property name="max_fetch_depth">2</property>
<!-- <property name="default_batch_fetch_size">10</property> -->
</session-factory>
</hibernate-configuration>
测试类
package com.play.hibernate2;
import java.util.List;
import java.util.Map;
import org.hibernate.FlushMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
public class RunTests4 {
private SessionFactory sessionFactory;
public static void main(String[] args){
RunTests4 d = new RunTests4();
d.run3();
}
public void run3(){
Session session = getSession();
session.beginTransaction();
createEntities(session);
session.getTransaction().commit();
System.out.println("NOW WITH A NEW TRANSACTION");
session = getSession();
session.beginTransaction();
Query query = session.createQuery("from A");
List results = query.list();
for (int i=0; i<results.size(); i++){
System.out.println("Row "+i+" was:");
A a = (A)results.get(i);
System.out.println("Result "+i);
System.out.println(a.toString());
}
session.getTransaction().commit();
}
public void createEntities(Session session){
for (int i=0; i<2; i++){
A a = new A();
B b = new B();
a.setB(b);
session.save(a);
}
}
public Session getSession(){
if (sessionFactory == null){
AnnotationConfiguration config = new AnnotationConfiguration();
config.addAnnotatedClass(A.class);
config.addAnnotatedClass(B.class);
config.configure();
new SchemaExport(config).create(true,true);
sessionFactory = config.buildSessionFactory();
}
Session session = sessionFactory.getCurrentSession();
return session;
}
}
最后,日志输出显示额外的选择以返回关联的类
2 [main] INFO org.hibernate.cfg.annotations.Version - Hibernate Annotations 3.5.1-Final
23 [main] INFO org.hibernate.cfg.Environment - Hibernate 3.5.1-Final
28 [main] INFO org.hibernate.cfg.Environment - hibernate.properties not found
32 [main] INFO org.hibernate.cfg.Environment - Bytecode provider name : javassist
37 [main] INFO org.hibernate.cfg.Environment - using JDK 1.4 java.sql.Timestamp handling
160 [main] INFO org.hibernate.annotations.common.Version - Hibernate Commons Annotations 3.2.0.Final
176 [main] INFO org.hibernate.cfg.Configuration - configuring from resource: /hibernate.cfg.xml
176 [main] INFO org.hibernate.cfg.Configuration - Configuration resource: /hibernate.cfg.xml
313 [main] INFO org.hibernate.cfg.Configuration - Configured SessionFactory: null
338 [main] INFO org.hibernate.dialect.Dialect - Using dialect: org.hibernate.dialect.MySQLDialect
462 [main] INFO org.hibernate.cfg.AnnotationBinder - Binding entity from annotated class: com.play.hibernate2.Test2
545 [main] INFO org.hibernate.cfg.annotations.EntityBinder - Bind entity com.play.hibernate2.Test2 on table Test2
649 [main] INFO org.hibernate.cfg.AnnotationBinder - Binding entity from annotated class: com.play.hibernate2.Test3
650 [main] INFO org.hibernate.cfg.annotations.EntityBinder - Bind entity com.play.hibernate2.Test3 on table Test3
651 [main] INFO org.hibernate.cfg.AnnotationBinder - Binding entity from annotated class: com.play.hibernate2.A
651 [main] INFO org.hibernate.cfg.annotations.EntityBinder - Bind entity com.play.hibernate2.A on table A
653 [main] INFO org.hibernate.cfg.AnnotationBinder - Binding entity from annotated class: com.play.hibernate2.B
653 [main] INFO org.hibernate.cfg.annotations.EntityBinder - Bind entity com.play.hibernate2.B on table B
678 [main] INFO org.hibernate.cfg.AnnotationConfiguration - Hibernate Validator not found: ignoring
687 [main] INFO org.hibernate.tool.hbm2ddl.SchemaExport - Running hbm2ddl schema export
688 [main] INFO org.hibernate.tool.hbm2ddl.SchemaExport - exporting generated schema to database
691 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - Using Hibernate built-in connection pool (not for production use!)
691 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - Hibernate connection pool size: 1
698 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - autocommit mode: false
711 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost:3306/play
711 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - connection properties: {user=play, password=****}
alter table A
drop
foreign key FK412E010759
alter table Test2
drop
foreign key FK4CF5DC04B7E1B79
drop table if exists A
drop table if exists B
drop table if exists Test2
drop table if exists Test3
create table A (
id integer not null auto_increment,
b_id integer,
primary key (id)
)
create table B (
id integer not null auto_increment,
primary key (id)
)
create table Test2 (
id integer not null auto_increment,
name varchar(255),
value integer not null,
test3_id integer,
primary key (id)
)
create table Test3 (
id integer not null auto_increment,
name varchar(255),
value integer not null,
primary key (id)
)
alter table A
add index FK412E010759 (b_id),
add constraint FK412E010759
foreign key (b_id)
references B (id)
alter table Test2
add index FK4CF5DC04B7E1B79 (test3_id),
add constraint FK4CF5DC04B7E1B79
foreign key (test3_id)
references Test3 (id)
2562 [main] INFO org.hibernate.tool.hbm2ddl.SchemaExport - schema export complete
2564 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - cleaning up connection pool: jdbc:mysql://localhost:3306/play
2571 [main] INFO org.hibernate.cfg.search.HibernateSearchEventListenerRegister - Unable to find org.hibernate.search.event.FullTextIndexEventListener on the classpath. Hibernate Search is not enabled.
2575 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - Using Hibernate built-in connection pool (not for production use!)
2575 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - Hibernate connection pool size: 1
2575 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - autocommit mode: false
2575 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost:3306/play
2575 [main] INFO org.hibernate.connection.DriverManagerConnectionProvider - connection properties: {user=play, password=****}
2622 [main] INFO org.hibernate.cfg.SettingsFactory - RDBMS: MySQL, version: 5.1.30
2622 [main] INFO org.hibernate.cfg.SettingsFactory - JDBC driver: MySQL-AB JDBC Driver, version: mysql-connector-java-5.1.9 ( Revision: ${svn.Revision} )
2633 [main] INFO org.hibernate.dialect.Dialect - Using dialect: org.hibernate.dialect.MySQLDialect
2635 [main] INFO org.hibernate.engine.jdbc.JdbcSupportLoader - Disabling contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
2636 [main] INFO org.hibernate.transaction.TransactionFactoryFactory - Using default transaction strategy (direct JDBC transactions)
2638 [main] INFO org.hibernate.transaction.TransactionManagerLookupFactory - No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
2638 [main] INFO org.hibernate.cfg.SettingsFactory - Automatic flush during beforeCompletion(): disabled
2638 [main] INFO org.hibernate.cfg.SettingsFactory - Automatic session close at end of transaction: disabled
2638 [main] INFO org.hibernate.cfg.SettingsFactory - JDBC batch size: 15
2638 [main] INFO org.hibernate.cfg.SettingsFactory - JDBC batch updates for versioned data: disabled
2638 [main] INFO org.hibernate.cfg.SettingsFactory - Scrollable result sets: enabled
2638 [main] INFO org.hibernate.cfg.SettingsFactory - JDBC3 getGeneratedKeys(): enabled
2638 [main] INFO org.hibernate.cfg.SettingsFactory - Connection release mode: auto
2639 [main] INFO org.hibernate.cfg.SettingsFactory - Maximum outer join fetch depth: 2
2639 [main] INFO org.hibernate.cfg.SettingsFactory - Default batch fetch size: 1
2639 [main] INFO org.hibernate.cfg.SettingsFactory - Generate SQL with comments: enabled
2639 [main] INFO org.hibernate.cfg.SettingsFactory - Order SQL updates by primary key: disabled
2639 [main] INFO org.hibernate.cfg.SettingsFactory - Order SQL inserts for batching: disabled
2639 [main] INFO org.hibernate.cfg.SettingsFactory - Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
2641 [main] INFO org.hibernate.hql.ast.ASTQueryTranslatorFactory - Using ASTQueryTranslatorFactory
2641 [main] INFO org.hibernate.cfg.SettingsFactory - Query language substitutions: {}
2641 [main] INFO org.hibernate.cfg.SettingsFactory - JPA-QL strict compliance: disabled
2641 [main] INFO org.hibernate.cfg.SettingsFactory - Second-level cache: enabled
2641 [main] INFO org.hibernate.cfg.SettingsFactory - Query cache: disabled
2644 [main] INFO org.hibernate.cfg.SettingsFactory - Cache region factory : org.hibernate.cache.impl.bridge.RegionFactoryCacheProviderBridge
2644 [main] INFO org.hibernate.cache.impl.bridge.RegionFactoryCacheProviderBridge - Cache provider: org.hibernate.cache.NoCacheProvider
2644 [main] INFO org.hibernate.cfg.SettingsFactory - Optimize cache for minimal puts: disabled
2644 [main] INFO org.hibernate.cfg.SettingsFactory - Structured second-level cache entries: disabled
2648 [main] INFO org.hibernate.cfg.SettingsFactory - Echoing all SQL to stdout
2648 [main] INFO org.hibernate.cfg.SettingsFactory - Statistics: enabled
2649 [main] INFO org.hibernate.cfg.SettingsFactory - Deleted entity synthetic identifier rollback: disabled
2649 [main] INFO org.hibernate.cfg.SettingsFactory - Default entity-mode: pojo
2649 [main] INFO org.hibernate.cfg.SettingsFactory - Named query checking : enabled
2649 [main] INFO org.hibernate.cfg.SettingsFactory - Check Nullability in Core (should be disabled when Bean Validation is on): enabled
2697 [main] INFO org.hibernate.impl.SessionFactoryImpl - building session factory
2796 [Finalizer] INFO org.hibernate.connection.DriverManagerConnectionProvider - cleaning up connection pool: jdbc:mysql://localhost:3306/play
2929 [main] INFO org.hibernate.impl.SessionFactoryObjectFactory - Not binding factory to JNDI, no JNDI name configured
Hibernate:
/* insert com.play.hibernate2.B
*/ insert
into
B
values
( )
Hibernate:
/* insert com.play.hibernate2.A
*/ insert
into
A
(b_id)
values
(?)
Hibernate:
/* insert com.play.hibernate2.B
*/ insert
into
B
values
( )
Hibernate:
/* insert com.play.hibernate2.A
*/ insert
into
A
(b_id)
values
(?)
NOW WITH A NEW TRANSACTION
Hibernate:
/*
from
A */ select
a0_.id as id2_,
a0_.b_id as b2_2_
from
A a0_
Hibernate:
/* load com.play.hibernate2.B */ select
b0_.id as id3_0_
from
B b0_
where
b0_.id=?
Hibernate:
/* load com.play.hibernate2.B */ select
b0_.id as id3_0_
from
B b0_
where
b0_.id=?
Row 0 was:
Result 0
com.play.hibernate2.A@351daa0e
Row 1 was:
Result 1
com.play.hibernate2.A@2e879860
编辑3:
如果我按照罗斯的方式行事,加载时会创建一个左外连接。如果我使用列表执行此操作,则会发出单独的选择。这是相关的代码。只有改变这种情况才能再现行为上的差异:
/* generates the left outer join
A a = (A)session.load(A.class,1);
System.out.println(a.getId()+" = "+a.getB().getName());
*/
// Creates separate selects for each object b associated with each a
Query query = session.createQuery("from A");
List results = query.list();
A a = (A)results.get(0);
System.out.println(a.getId()+" = "+a.getB().getName());
我猜它可能被称为'bug'。正如我之前提到的,在文档中,他们说在HQL中而不是在映射中指定获取模式是“通常的”,我认为这可能意味着HQL方式有更多的人流量来控制它。 ?
(顺便说一下,我给A和B添加了一个额外的'name'字段,否则hibernate会优化检索,因为它可以从A上的外键中获取所有B)
答案 0 :(得分:17)
作为一种蒸馏:
如果您使用Query接口(例如:session.createQuery()),@Fetch(JOIN)将被忽略,但如果您使用Criteria接口,它将被正确使用。
这实际上是Hibernate中的一个从未解决的错误。很不幸,因为许多应用程序使用Query接口,无法轻松迁移到Criteria接口。
如果使用Query接口,则必须手动将JOIN FETCH语句添加到HQL中。
答案 1 :(得分:4)
当我从db中选择Test2时,我可以看到正在进行单独的选择以获取相关test3类的详细信息。
我对其他答案的代码非常感兴趣,因为这也是我在测试您正在展示的代码时所看到的,它会为from Test2
生成两个选项。
我正在使用以下依赖项:
我将FetchType设置为绝望,尽管对于OneToOne映射它无论如何都默认为EAGER,但它没有任何区别。
如果使用Hibernate注释,这没有任何影响,因为Hibernate注释会覆盖EJB3提取选项。请参阅2.4.5.1. Lazy options and fetching modes。
答案 2 :(得分:2)
我创建了一个非常简单的应用程序来测试你得到的场景,你的代码应该正常工作(它对我有用)。我尝试过的唯一能给我多个select语句的是将max_fetch_depth设置为0.如果设置为2(或未配置),我在查询中得到左外连接。您使用的是什么版本的hibernate?我使用的是3.4.0.GA。
编辑: 下面是我使用的简单应用程序(与Pascal提到的相同版本):
CFG:
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="hibernate.connection.url">jdbc:hsqldb:hibscribs</property>
<property name="hibernate.connection.username">sa</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="hbm2ddl.auto">create-drop</property>
<property name="current_session_context_class">thread</property>
<!-- property name="max_fetch_depth">0</property--><!-- uncomment to see where 2 selects are used instead of join -->
<mapping class="com.mydomain.bo.Person" />
<mapping class="com.mydomain.bo.Phone" />
</session-factory>
</hibernate-configuration>
人物实体 - 保持简单只有@OneToOne,添加JoinColumn等没有区别。
@Entity
@Table(name="person")
public class Person {
private Long id;
private String name;
private Phone phone;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToOne(cascade=CascadeType.ALL)
public Phone getPhone() {
return phone;
}
public void setPhone(Phone phone) {
this.phone = phone;
}
}
@Entity
@Table(name="phone")
public class Phone {
private Long id;
private String number;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
简单测试:
SessionFactory session = HibernateUtil.getSessionFactory();
Session sess = session.getCurrentSession();
Transaction tx = sess.beginTransaction();
Phone phone = new Phone();
phone.setId(1L);
phone.setNumber("1234567");
Person person = new Person();
person.setId(1L);
person.setName("Bob");
person.setPhone(phone);
sess.save(person);
tx.commit();
sess = session.openSession();
//Person p1 = (Person)sess.load(Person.class,1L);
//System.out.println(p1.getPhone().getNumber());
// changed the above code to use the Criteria interface below:
Criteria criteria = sess.createCriteria(Person.class);
List<Person> results = criteria.list();
for (int i=0; i<results.size(); i++){
Person p = (Person)results.get(i);
System.out.println(p.getPhone().getNumber());
}
输出:
Hibernate:
select
phone_.id,
phone_.number as number1_
from
phone phone_
where
phone_.id=?
Hibernate:
insert
into
phone
(number, id)
values
(?, ?)
Hibernate:
insert
into
person
(name, phone_id, id)
values
(?, ?, ?)
Hibernate:
select
person0_.id as id0_1_,
person0_.name as name0_1_,
person0_.phone_id as phone3_0_1_,
phone1_.id as id1_0_,
phone1_.number as number1_0_
from
person person0_
left outer join
phone phone1_
on person0_.phone_id=phone1_.id
where
person0_.id=?
1234567