Hibernate noob fetch join проблема - PullRequest
9 голосов
/ 28 мая 2010

У меня есть два класса, Test2 и Test3. Test2 имеет атрибут test3, который является экземпляром Test3. Другими словами, у меня есть однонаправленная ассоциация OneToOne, где test2 имеет ссылку на test3.

Когда я выбираю Test2 из базы данных, я вижу, что делается отдельный выбор для получения сведений о связанном классе test3. Это знаменитая проблема выбора 1 + N.

Чтобы исправить это, чтобы использовать один выбор, я пытаюсь использовать аннотацию fetch = join, которую я понимаю как @Fetch (FetchMode.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;
}

NB. Я установил для FetchType значение EAGER из-за отчаяния, даже если оно по умолчанию равно EAGER для сопоставлений OneToOne, но это не имеет значения.

Спасибо за любую помощь!

Edit: я в значительной степени разочаровался в попытке использовать FetchMode.JOIN - может ли кто-нибудь подтвердить, что он заставил его работать, то есть произвести левое внешнее соединение? В документах я вижу, что «Обычно документ сопоставления не используется для настройки выборки. Вместо этого мы сохраняем поведение по умолчанию и переопределяем его для конкретной транзакции, используя выборку левого соединения в HQL»

Если вместо этого я выберу левое соединение:

query = session.createQuery ("из Test2 t2 левый выбор соединения t2.test3");

тогда я действительно получаю желаемые результаты - то есть левое внешнее объединение в запросе.

Изменить номер 2:

Ребята, большое спасибо за ваши ответы. Теперь я хочу докопаться до сути. Обычно я обнаруживаю, что когда я что-то исследую, я узнаю намного больше, чем я думал.

Одна вещь, которую я уже изучил, - я работал на старых сборках hibernate, потому что я не осознавал, что хранилище maven устарело. Теперь я также подключен к репозиторию jboss, и у меня есть последние версии аннотаций hibernate и hibernate - 3.5.1-Final в обоих случаях.

Я создал небольшой тестовый пример, который максимально упрощает его - я все еще вижу проблему в 3.5.1-Final, хотя «Я на 99% уверен, что это просто глупость. не настроен правильно, особенно Росс, учитывая, что вы заставили его работать (спасибо, что нашли время попробовать это кстати)

Итак, у меня есть эти занятия (на этот раз полный текст)

Класс А

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());

Полагаю, это можно назвать «ошибкой». Как я упоминал ранее, в документах говорится, что «обычно» указывать режим выборки в HQL, а не в отображении, что, как я думаю, может означать, что путь HQL имеет больший объем трафика для его устранения. .

(кстати, я добавил дополнительное поле 'name' в A и B, иначе hibernate оптимизирует извлечение, потому что он может получить все B только из внешнего ключа на A)

Ответы [ 3 ]

17 голосов
/ 17 мая 2013

Как дистилляция:

@ Fetch (JOIN) будет игнорироваться, если вы используете интерфейс Query (например, session.createQuery ()), но будет использоваться правильно, если вы используете интерфейс Criteria.

Это практически ошибка в Hibernate, которая так и не была устранена. К сожалению, многие приложения используют интерфейс запросов и не могут быть легко перенесены в интерфейс Criteria.

Если вы используете интерфейс запросов, вам всегда нужно вручную добавлять операторы JOIN FETCH в HQL.

4 голосов
/ 29 мая 2010

Когда я выбираю Test2 из базы данных, я вижу, что делается отдельный выбор для получения сведений о связанном классе test3.

Меня очень интересует код другого ответа, потому что это то, что я тоже вижу при тестировании кода, который вы показываете, он генерирует два выбора для from Test2.

Я использую следующие зависимости:

  • org.hibernate: спящий режим-EntityManager: баночка: 3.4.0.GA: компилировать
  • org.hibernate: ejb3-живучесть: баночка: 1.0.2.GA: компилировать
  • org.hibernate: спящая-Викисклад аннотации: баночка: 3.1.0.GA: компилировать
  • org.hibernate: спящая-аннотации: баночка: 3.4.0.GA: компилировать
  • org.hibernate: спящий режим-ядро: баночка: 3.3.0.SP1: компилировать

Я установил для FetchType значение EAGER из отчаяния, хотя оно по умолчанию равно EAGER для сопоставлений OneToOne, но это не имело значения.

Это не оказывает влияния, если вы используете аннотации Hibernate, потому что аннотации Hibernate переопределяют параметры выборки EJB3. См. 2.4.5.1. Ленивые опции и режимы извлечения .

2 голосов
/ 29 мая 2010

Я создал очень простое приложение для проверки сценария, который вы получаете, и ваш код должен работать (он работает для меня). Единственное, что я попробовал, чтобы дать мне несколько операторов выбора, это установить max_fetch_depth в 0. Если задано значение 2 (или не настроено), я получаю левое внешнее соединение в своем запросе. Какую версию hibernate вы используете? Я использую 3.4.0.GA.

EDIT: Ниже приведено простое приложение, которое я использовал (с теми же версиями, упомянутыми Паскалем):

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>

Сущность Person - проще говоря, только @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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...