2017-10-05 13 views
1

J'utilise Hibernate 5.1.2annotation Hibernate @Where ne fonctionne pas avec l'héritage

J'ai rencontré un problème inattendu que je ne peux pas sembler travailler autour. Voici le résumé de mon modèle de données: enter image description here

dfip_project_version est ma table de superclasse et dfip_appln_proj_version est ma table de la sous-classe. dfip_application contient une liste de dfip_appln_proj_version s.

Je cartographié comme suit:

@Table(name = "DFIP_PROJECT_VERSION") 
@Entity 
@Inheritance(strategy = InheritanceType.JOINED) 
public abstract class AbstractProjectVersion { 
    @Id @GeneratedValue 
    @Column(name = "PROJECT_VERSION_OID") 
    Long oid; 

    @Column(name = "PROJ_VSN_EFF_FROM_DTM") 
    Timestamp effFromDtm; 

    @Column(name = "PROJ_VSN_EFF_TO_DTM") 
    Timestamp effToDtm; 

    @Column(name = "PROJECT_VERSION_TYPE") 
    @Type(type = "project_version_type") 
    ProjectVersionType projectVersionType; 
} 


@Table(name = "DFIP_APPLN_PROJ_VERSION") 
@Entity 
class ApplicationProjectVersion extends AbstractProjectVersion { 

    @OneToOne 
    @JoinColumn(name = "APPLICATION_OID", nullable = false) 
    Application application; 

    public ApplicationProjectVersion() { 
     projectVersionType = ProjectVersionType.APPLICATION; 
    } 
} 

@Table(name = "DFIP_APPLICATION") 
@Entity 
class Application { 

    @Id @GeneratedValue 
    @Column(name = "APPLICATION_OID") 
    Long oid; 

    @OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.EAGER) 
    @Fetch(FetchMode.SELECT) 
    @Where(clause = "PROJ_VSN_EFF_TO_DTM is null") 
    List<ApplicationProjectVersion> applicationVersions = []; 
} 

J'utilise l'annotation @Where de sorte que seul le ApplicationProjectVersion courant est le Application récupéré. Le problème avec ceci est que Hibernate suppose que la colonne que je référence est dans la table dfip_appl_proj_version, quand elle est réellement sur la table de super-classe (dfip_project_version).

Voici ce que j'ai essayé jusqu'à présent pour contourner cette limitation:

Tentative 1

J'ai essayé de mettre l'annotation @Where sur la AbstractProjectVersion super-classe, comme ceci:

@Table(name = "DFIP_PROJECT_VERSION") 
@Entity 
@Inheritance(strategy = InheritanceType.JOINED) 
@Where(clause = "PROJ_VSN_EFF_TO_DTM is null") 
public abstract class AbstractProjectVersion { 
    ...etc... 
} 

Cela n'a rien fait, car la clause WHERE ne semble pas être remarquée lors de la récupération du Application.


Tentative 2

J'ai fait la liste applicationVersions sur Application LAZY, et a essayé de la carte latestVersion manuellement comme ceci:

@Table(name = "DFIP_APPLICATION") 
@Entity 
class Application { 

    @Id @GeneratedValue 
    @Column(name = "APPLICATION_OID") 
    Long oid; 

    @OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.LAZY) 
    @Fetch(FetchMode.SELECT) 
    List<ApplicationProjectVersion> applicationVersions = []; 

    @ManyToOne 
    @JoinColumnsOrFormulas([ 
     @JoinColumnOrFormula(formula = @JoinFormula(value = "(APPLICATION_OID)", referencedColumnName="APPLICATION_OID")), 
     @JoinColumnOrFormula(formula = @JoinFormula(value = "(select apv.PROJECT_VERSION_OID from DFIP_PROJECT_VERSION pv, DFIP_APPLN_PROJ_VERSION apv where apv.PROJECT_VERSION_OID = pv.PROJECT_VERSION_OID and apv.APPLICATION_OID = APPLICATION_OID and pv.PROJ_VSN_EFF_TO_DTM is null)", referencedColumnName="PROJECT_VERSION_OID")), 
    ]) 
    ApplicationProjectVersion latestVersion; 
} 

Cela a provoqué Hibernate pour générer le SQL (extrait suivant):

from DFIP_APPLICATION this_ 
left outer join DFIP_APPLN_PROJ_VERSION applicatio2_ 
    on (this_.APPLICATION_OID)=applicatio2_.APPLICATION_OID and 
     (select apv.PROJECT_VERSION_OID from DFIP_PROJECT_VERSION pv, DFIP_APPLN_PROJ_VERSION apv 
     where apv.PROJECT_VERSION_OID = pv.PROJECT_VERSION_OID and apv.APPLICATION_OID = this_.APPLICATION_OID 
     and pv.PROJ_VSN_EFF_TO_DTM is null)=applicatio2_.PROJECT_VERSION_OID 

qui a abouti à ORA-01799: a column may not be outer-joined to a subquery.

Si je ne peux pas spécifier une sous-requête dans ma formule se joindre, alors je ne peux pas adhérer à la super-classe manuellement ...


Tentative 3

J'ai remarqué que l'utilisation de @JoinFormula fait remarquer à Hibernate mon annotation @Where sur la super-classe. J'ai donc essayé les éléments suivants:

@Table(name = "DFIP_PROJECT_VERSION") 
@Entity 
@Inheritance(strategy = InheritanceType.JOINED) 
@Where(clause = "PROJ_VSN_EFF_TO_DTM is null") 
public abstract class AbstractProjectVersion { 
    ...etc... 
} 

@Table(name = "DFIP_APPLICATION") 
@Entity 
class Application { 

    @Id @GeneratedValue 
    @Column(name = "APPLICATION_OID") 
    Long oid; 

    @OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.LAZY) 
    @Fetch(FetchMode.SELECT) 
    List<ApplicationProjectVersion> applicationVersions = []; 

    @ManyToOne 
    @JoinFormula(value = "(APPLICATION_OID)", referencedColumnName="APPLICATION_OID") 
    ApplicationProjectVersion latestVersion; 
} 

Cela a généré SQL (extrait) suivant:

from DFIP_APPLICATION this_ 
left outer join DFIP_APPLN_PROJ_VERSION applicatio2_ 
    on (this_.APPLICATION_OID)=applicatio2_.APPLICATION_OID and (applicatio2_1_.PROJ_VSN_EFF_TO_DTM is null) 
left outer join DFIP_PROJECT_VERSION applicatio2_1_ on applicatio2_.PROJECT_VERSION_OID=applicatio2_1_.PROJECT_VERSION_OID 

C'est presque correct!Malheureusement, il n'est pas SQL valide, puisque applicatio2_1_ est utilisé avant qu'il ne soit déclaré sur la ligne suivante :(.


Maintenant, je suis d'idées, de sorte que toute aide serait appréciée. Est-il possible de spécifier un clause WHERE qui apportera seulement le ProjectVersion actuel, sans se débarrasser de ma structure d'héritage?

Related Hibernate issue ticket

Répondre

0

J'ai une solution à ce problème. Je dois admettre, il a fini par être un peu plus lourd que ce J'espérais, mais ça marche plutôt bien, j'ai attendu quelques mois avant de poster, pour faire e qu'il n'y a pas de problèmes et jusqu'à présent, je n'ai pas eu de problèmes.

Mes entités sont encore cartographié exactement comme décrit dans la question, mais au lieu d'utiliser l'annotation @Where problématique, je devais utiliser @Filter annotation à la place:

public class Application { 

    @OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.EAGER) 
    @Cascade([SAVE_UPDATE, DELETE, MERGE]) 
    @Fetch(FetchMode.SELECT) 

    // Normally we'd just use the @Where(clause = "PROJ_VSN_EFF_TO_DTM is null"), but that doesn't work with collections of 
    // entities that use inheritance, as we have here. 
    // 
    // Hibernate thinks that PROJ_VSN_EFF_TO_DTM is a column on DFIP_APPLN_PROJ_VERSION table, but it is actually on the "superclass" 
    // table (DFIP_PROJECT_VERSION). 
    // 
    // B/c of this, we have to do the same thing with a Filter, which is defined on AbstractProjectVersion. 
    // NOTE: This filter must be explicitly enabled, which is currently achieved by HibernateForceFiltersAspect 
    // 
    @Filter(name="currentProjectVersionOnly", 
     condition = "{pvAlias}.PROJ_VSN_EFF_TO_DTM is null", 
     deduceAliasInjectionPoints=false, 
     aliases=[ @SqlFragmentAlias(alias = "pvAlias", table = "DFIP_PROJECT_VERSION") ] 
    ) 
    List<ApplicationProjectVersion> projectVersions = []; 

} 

Puisque nous utilisons un filtre, il faut aussi définir:

// NOTE: This filter needs to be explicitly turned on with session.enableFilter("currentProjectVersionOnly"); 
// This is currently achieved with HibernateForceFiltersAspect 
@FilterDef(name="currentProjectVersionOnly") 

@Table(name = "DFIP_PROJECT_VERSION") 
@Inheritance(strategy = InheritanceType.JOINED) 
public abstract class AbstractProjectVersion { 

} 

Et bien sûr, nous devons activer, puisqu'Hibernate ne dispose pas d'un paramètre pour activer automatiquement sur tous les filtres.

Pour ce faire, je créé un aspect de l'ensemble du système, dont le travail consiste à activer les filtres spécifiques avant chaque appel à un DAO:

/** 
* Enables provided Hibernate filters every time a Hibernate session is openned. 
* 
* Must be enabled and configured explicitly from Spring XML config (i.e. no auto-scan here) 
* 
* @author Val Blant 
*/ 
@Aspect 
public class HibernateForceFiltersAspect { 

    List<String> filtersToEnable = []; 

    @PostConstruct 
    public void checkConfig() throws Exception { 
     if (filtersToEnable.isEmpty()) { 
      throw new IllegalArgumentException("Missing required property 'filtersToEnable'"); 
     } 
    } 

    /** 
    * This advice gets executed before all method calls into DAOs that extend from <code>HibernateDao</code> 
    * 
    * @param jp 
    */ 
    @Before("@target(org.springframework.stereotype.Repository) && execution(* ca.gc.agr.common.dao.hibernate.HibernateDao+.*(..))") 
    public void enableAllFilters(JoinPoint jp) { 
     Session session = ((HibernateDao)jp?.getTarget())?.getSession(); 

     if (session != null) { 
      filtersToEnable.each { session.enableFilter(it) } // Enable all specified Hibernate filters 
     } 
    } 

} 

Et la configuration du ressort correspondant:

<!-- This aspect is used to force-enable specified Hibernate filters for all method calls on DAOs that extend HibernateDao --> 
<bean class="ca.gc.agr.common.dao.hibernate.HibernateForceFiltersAspect"> 
    <property name="filtersToEnable"> 
     <list> 
      <value>currentProjectVersionOnly</value>   <!-- Defined in AbstractProjectVersion --> 
     </list> 
    </property> 
</bean> 

Et là vous l'avez - clause polymorphe @Where :).