2016-07-08 1 views
1

Tout d'abord, je sais que cela ne devrait pas être une solution, mais j'ai besoin de résoudre le problème de cette façon.Force Hibernate @OneToOne relation (LIMIT 1)

J'ai une OneToOne relation qui, très rarement, n'est pas respectée (DB renvoie deux lignes au lieu d'une). J'ai besoin de forcer cette relation, quelque chose comme LIMIT 1 pour cette requête Hibernate spécifique.

@Entity 
@Table(name="contact", uniqueConstraints = @UniqueConstraint(columnNames="user_id")) 
public class ContactDTO implements Serializable { 

(...) 

// Force this relation 
@OneToOne(cascade = CascadeType.REFRESH, fetch = FetchType.LAZY, optional = true) 
@JoinColumn(name = "user_id", unique = true, insertable = false, updatable = false) 
public UserDTO getBaseUser() { 
    return baseUser; 
} 

public void setBaseUser(UserDTO baseUser) { 
    this.baseUser = baseUser; 
} 

@Column(name = "user_id", unique = true) 
public Integer getUserId() { 
    return this.userId; 
} 

@Entity 
@Table(name = "base_user") 
public class UserDTO implements Serializable { 

(...) 

@OneToOne(fetch = FetchType.LAZY, mappedBy = "baseUser") 
public ContactDTO getContact() { 
    return contact; 
} 

public void setContact(ContactDTO contact) { 
    this.contact = contact; 
} 

Quand je reçois deux lignes (maximum), Hibernate génère l'exception suivante:

org.hibernate.HibernateException: More than one row with the given identifier was found 

Est-ce possible ou je vraiment besoin de transformer cette relation en @OneToMany ?

Remarque: Hibernate 3.3, malheureusement, je ne peux pas utiliser JoinColumnsOrFormula.

Nous vous remercions de votre aide! Cordialement.

+0

Pourriez-vous préciser les entités et les tables impliquées? –

+0

Bien sûr. Ajouté un peu plus d'informations, je pense que c'est suffisant pour comprendre la logique. –

+0

Vous pouvez toujours passer à la relation OneToMany, mais utilisez-la dans un champ privé. Ensuite, fournissez des getters et setters qui fonctionnent uniquement sur le premier élément. Il va essentiellement encapsuler votre situation indésirable sans avoir d'autres codes à connaître à ce sujet. –

Répondre

0

Si vous recevez des enregistrements en double, il est probable que la relation ne soit pas de 1: 1 dans votre base de données. Vous devrez ajouter des contraintes de base de données pour appliquer cette relation ou modifier votre annotation en 1: plusieurs. Il est à noter que l'annotation @OneToOne n'applique rien dans la base de données. Elle définit simplement ce que Hibernate attend de la relation, c'est pourquoi elle renvoie une erreur lorsque vous recevez plusieurs enregistrements.

Vérifiez la réponse à ce post pour voir comment votre relation devrait regarder dans votre base de données:

Defining a one-to-one relationship in SQL Server

+0

Merci pour votre réponse Steve.Le problème est que le client ne veut pas de contrainte de base de données et ne veut pas non plus résoudre le problème de code. Logique? Bien sûr que non, je sais. La seule solution que j'ai est de garantir que cette requête retourne une seule ligne pour respecter la relation d'hibernation, ou la changer en OneToMany, bien que cela ne se produise que 5 fois par an. –

+0

Parmi ces deux approches, je dirais que l'approche OnetoMany serait une meilleure pratique. Si vous avez limité les résultats de la requête, vous excluez les données valides. – Steve

+0

Il s'agit de données dupliquées, donc j'ai juste besoin de l'une des deux lignes. L'approche OneToMany est celle que j'essaie d'éviter, mais dans le dernier cas je le ferai. Pour l'instant, j'essaie de résoudre le problème avec un déclencheur de vue. Merci pour votre aide Steve. –

0

Pour ce faire, vous pouvez utiliser des vues. Supposons que vous avez SOME_TABLE avec deux colonnes REAL_ID et BAD_ID. Le premier est unique, le second est votre colonne non unique brisée. Il suffit de créer vue avec par exemple:

select * from SOME_TABLE where REAL_ID in (
    select min(REAL_ID) from SOME_TABLE group by BAD_ID 
) 

Ce qui est fondamentalement vos enregistrements moins de table d'origine qui dupliquent BAD_ID valeurs. Ensuite, dans le mappage d'hibernation, vous utilisez cette vue à la place de la table d'origine.

De toute évidence, c'est un hack, donc je ne peux pas garantir ses performances, mais cela devrait vous permettre d'utiliser la liaison @OneToOne. Le plus grand inconvénient est que, par défaut, ce sera en lecture seule (qui pourrait probablement changé avec instead oftriggers). EDIT Certains DB permettent de mettre à jour des vues simples, de sorte qu'il peut fonctionner sans aucun déclencheur.

Vous pouvez également regarder dans la génération de vos entités directement à partir de la requête.

+0

Merci pour votre réponse Rafal. C'est la solution à laquelle je pense et qui conviendrait parfaitement dans ce cas. Comme vous l'avez dit c'est un hack, je suis totalement d'accord. De plus, comme vous l'avez dit, ce serait un mappage en lecture seule et j'en ai besoin pour être en lecture-écriture. Donc, vous avez une idée de comment puis-je résoudre cela avec des déclencheurs? –

+0

Voici un article qui donne un exemple d'utilisation des déclencheurs 'au lieu de' sur la vue de faire des insertions et des mises à jour sur la table réelle: http://in.relation.to/2004/06/20/history-triggers-and-hibernate/ J'espère que ça aide. –

+0

Cela peut être plus facile dans certains DB. Comme je lis les deux [sqlserver] (https://stackoverflow.com/questions/3866216/updatable-views-sql-server-2008) et [oracledb] (https://docs.oracle.com/cd/B28359_01/server .111/b28310/views001.htm # i1007148) ont des vues pouvant être mises à jour - Rafał S il ya 1 heure –