[Java] Rendere pigro un rapporto OneToOne


Answers

Per ottenere il caricamento lazy lavorando su mapping uno-a-uno nullable è necessario lasciare che la funzione di ibernazione compili il tempo e aggiunga un @LazyToOne(value = LazyToOneOption.NO_PROXY) alla relazione one-to-one.

Mappatura di esempio:

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

Esempio Ant Estensione del file Build (per fare la strumentazione di compilazione del tempo di Hibernate):

<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>
Question

In questa applicazione che stiamo sviluppando, abbiamo notato che una vista era particolarmente lenta. Ho profilato la vista e ho notato che c'era una query eseguita da Hibernate che impiegava 10 secondi anche se c'erano solo due oggetti nel database da recuperare. Tutte le relazioni OneToMany e ManyToMany erano pigre, quindi non era questo il problema. Durante l'ispezione dell'effettivo SQL in esecuzione, ho notato che c'erano oltre 80 join nella query.

Ispezionando ulteriormente il problema, ho notato che il problema era causato dalla profonda gerarchia delle relazioni OneToOne e ManyToOne tra le classi di entità. Quindi, ho pensato, li renderò semplicemente pigri, questo dovrebbe risolvere il problema. Ma l'annotazione di @OneToOne(fetch=FetchType.LAZY) o @ManyToOne(fetch=FetchType.LAZY) non sembra funzionare. O ottengo un'eccezione, altrimenti non vengono effettivamente sostituiti con un oggetto proxy e quindi sono pigri.

Qualche idea su come far funzionare questo? Nota che non uso il persistence.xml per definire relazioni o dettagli di configurazione, tutto è fatto in codice java.




Come già perfettamente spiegato da ChssPly76, i proxy di Hibernate non aiutano con le associazioni uno-a-uno non vincolate, ma c'è un trucco spiegato here per evitare di impostare la strumentazione. L'idea è di imbrogliare Hibernate sul fatto che la classe entità che vogliamo usare sia già stata strumentata: lo si strumenta manualmente nel codice sorgente. È facile! L'ho implementato con CGLib come provider bytecode e funziona (assicurati di configurare lazy = "no-proxy" e fetch = "select", non "join", nella tua HBM).

Penso che questa sia una buona alternativa alla strumentazione reale (intendo automatica) quando si ha una sola relazione nullable one-to-one che si desidera rendere pigro. Lo svantaggio principale è che la soluzione dipende dal provider bytecode che si sta utilizzando, quindi commenta accuratamente la classe perché potresti dover cambiare il fornitore bytecode in futuro; ovviamente, stai anche modificando il tuo bean di modello per un motivo tecnico e questo non è corretto.




Ecco qualcosa che ha funzionato per me (senza strumentazione):

Invece di usare @OneToOne su entrambi i lati, uso @OneToMany nella parte inversa della relazione (quella con mappedBy ). Ciò rende la proprietà una raccolta ( List nell'esempio seguente), ma la traduco in un elemento nel getter, rendendolo trasparente ai client.

Questa configurazione funziona pigramente, cioè i selezioni vengono fatti solo quando vengono chiamati getPrevious() o getNext() - e solo una selezione per ogni chiamata.

La struttura della tabella:

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

La classe:

@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); }

}