web-dev-qa-db-fra.com

Hibernate et pas de PK

Est-il possible de créer une table (à partir d'un @Entity Hibernate JPA annoté) qui ne contient pas de clé primaire/Id?

Je sais que ce n'est pas une bonne idée. une table devrait avoir une clé primaire.

51
Schildmeijer

J'ai trouvé que ce n'est pas possible de le faire. Tellement de malchance pour ceux qui travaillent avec des systèmes hérités.

Si vous procédez au reverse engineering (créez des entités annotées JPA à partir d'une connexion JDBC existante), la table crée deux classes Java, une entité et un champ. id, et un identifiant incorporable contenant toutes les colonnes de votre relation.

9
Schildmeijer

La réponse de Roger est correcte. Pour expliquer un peu ce que cela signifie (je n’étais pas clair au début et j’ai pensé que cela aiderait):

Disons que vous avez une table Foo en tant que telle:

TABLE Foo (
bar varchar(20),
bat varchar(20)
)

Normalement, vous pouvez écrire une classe w/Annotations pour travailler avec cette table: 

// Technically, for this example, the @Table and @Column annotations 
// are not needed, but don't hurt. Use them if your column names 
// are different than the variable names.

@Entity
@Table(name = "FOO")
class Foo {

  private String bar;
  private String bat;


  @Column(name = "bar")
  public String getBar() {
   return bar;    
  }

  public void setBar(String bar) {
   this.bar = bar;    
  }

  @Column(name = "bat")
  public String getBat() {
   return bat;    
  }

  public void setBat(String bat) {
   this.bat = bat;    
  }

}

.. Mais, sacrément. Cette table n'a rien que nous puissions utiliser comme identifiant et c'est une base de données héritée que nous utilisons pour [insérer une fonction métier vitale]. Je ne pense pas qu'ils me laisseront commencer à modifier les tables pour que je puisse utiliser le mode veille prolongée. 

Vous pouvez, à la place, scinder l'objet en une structure exploitable en veille prolongée qui permet à la ligne entière d'être utilisée comme clé. (Naturellement, cela suppose que la ligne est unique.)

Divisez l'objet Foo en deux comme suit:

@Entity
@Table(name = "FOO")
class Foo {

  @Id
  private FooKey id;

  public void setId(FooKey id) {
    this.id = id;
  }

  public void getId() {
    return id;
  }
}

et

@Embeddable
class FooKey implements Serializable {
  private String bar;
  private String bat;

  @Column(name = "bar")
  public String getBar() {
   return bar;    
  }

  public void setBar(String bar) {
   this.bar = bar;    
  }

  @Column(name = "bat")
  public String getBat() {
   return bat;    
  }

  public void setBat(String bat) {
   this.bat = bat;    
  }

}

.. Et ça devrait être ça. Hibernate utilisera la clé incorporable pour l’identité requise et vous pourrez passer un appel normalement:

Query fooQuery = getSession().createQuery("from Foo");

J'espère que cela aide les débutants à réussir. 

50
awied

Utilisez le code suivant. Hibernate n'a pas sa propre logique pour distinguer les enregistrements en double 

Faites-moi savoir s'il y a des problèmes avec cette approche

@Entity @IdClass(Foo.class)
class Foo implements Serializable {
  @Id private String bar;
  @Id private String bat;

  public String getBar() {
   return bar;    
  }

  public void setBar(String bar) {
   this.bar = bar;
  }


  public String getBat() {
   return bat;    
  }

  public void setBat(String bat) {
   this.bat = bat;    
  }
}
45
Amit

j'ai trouvé que cette astuce fonctionne:

<id column="ROWID" type="string" />
9
RudiDudi

Vous n'avez pas besoin de créer une classe distincte pour devenir votre @ Id ou votre clé primaire. Utilisez simplement un entier (ou autre chose). En outre, ne publiez pas la fausse clé, car les développeurs qui l'utilisent pourraient penser qu'elle est réelle, sinon essayez de l'utiliser. Enfin, ceci est mieux utilisé dans une vue. Je suis d'accord avec les publications précédentes que dans la plupart des cas, sinon tous, les tables devraient avoir une clé primaire. Par exemple:

@Entity
@Table(name = "FOO")
class Foo {

  @SuppressWarnings("unused")
  @Id
  private Integer id;

  @Column(name = "REAL_COLUMN")
  private String realColumn;

  public String getRealColumn() {
    return realColumn;    
  }

  public void setRealColumn(String realColumn) {
    this.realColumn= realColumn;    
  }


}
2
Domenic D.

Ajout au commentaire de Awied . Si vous souhaitez ensuite rechercher un bar, utilisez HQL suivant.

Query fooQuery = getSession().createQuery("from Foo.id.bar = '<barName>'");
1
punkck

Pour créer le fichier Pojo à partir d'une table, utilisez la méthode de reverse engineering existant dans Eclipse. Pour la table de clés non primaire, Eclipse générera les deux classes Pojo.

Eclipse generated class and hbm.xml - 
---
Foo.Java 
//

public class Foo implements Java.io.Serializable {

    private FooId id;

    public Foo() {
    }

    public Foo(FooId id) {
        this.id = id;
    }

    public FooId getId() {
        return this.id;
    }

    public void setId(FooId id) {
        this.id = id;
    }

}
---
FooId.Java
//

public class FooId implements Java.io.Serializable {

    private String bar;
    private String bat;

    public FooId() {
    }

    public FooId(String bar, String bat) {
        this.bar = bar;
        this.bat = bat;
    }

    public String getBar() {
        return this.bar;
    }

    public void setBar(String bar) {
        this.bar = bar;
    }

    public String getBat() {
        return this.bat;
    }

    public void setBat(String bat) {
        this.bat = bat;
    }

    public boolean equals(Object other) {
        if ((this == other))
            return true;
        if ((other == null))
            return false;
        if (!(other instanceof FooId))
            return false;
        FooId castOther = (FooId) other;

        return ((this.getBar() == castOther.getBar()) || (this.getBar() != null
                && castOther.getBar() != null && this.getBar().equals(
                castOther.getBar())))
                && ((this.getBat() == castOther.getBat()) || (this.getBat() != null
                        && castOther.getBat() != null && this.getBat().equals(
                        castOther.getBat())));
    }

    public int hashCode() {
        int result = 17;

        result = 37 * result
                + (getBar() == null ? 0 : this.getBar().hashCode());
        result = 37 * result
                + (getBat() == null ? 0 : this.getBat().hashCode());
        return result;
    }

}
---
Foo.hbm.xml

<hibernate-mapping>
    <class name="com.Foo" table="foo" schema="public" catalog="hibernate_poc">
        <composite-id name="id" class="com.FooId">
            <key-property name="bar" type="string">
                <column name="bar" length="20" />
            </key-property>
            <key-property name="bat" type="string">
                <column name="bat" length="20" />
            </key-property>
        </composite-id>
    </class>
</hibernate-mapping>

---
entry in the Hibernate.cfg.xml - 

<mapping class="com.poc.Foo" resource="Foo.hbm.xml"/>

---
Fetch the Data from table -
FooDataFetch.Java
//

import Java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class FooDataFetch {
        private static Session session = null;

   public static void main(String[] args) {
          try{            
            Configuration cfg = new Configuration();
            cfg.configure("/hibernate.cfg.xml");
            SessionFactory sf = cfg.buildSessionFactory();
            session = sf.openSession();

            session.beginTransaction();
              queryPerson(session);
            session.close(); 

          }catch (Throwable ex) { 
             System.err.println("Failed to create sessionFactory object." + ex);
            ex.printStackTrace();
             throw new ExceptionInInitializerError(ex); 
          }       
       }    

       private static void queryPerson(Session session) {
            Query query = session.createQuery("from Foo");                 
            List <Foo>list = query.list();
            Java.util.Iterator<Foo> iter = list.iterator();
            while (iter.hasNext()) {
                Foo foo = iter.next();
                System.out.println("Foo Details: \"" + foo.getId().getBar() +"\", " + foo.getId().getBat());
            }
        }      
}
1
Kuldeep

Au lieu de rechercher des solutions de contournement dans Hibernate, il peut être plus facile d’ajouter un identifiant factice dans votre vue de base de données. J'ai écrit à ce sujet dans une autre question: https://stackoverflow.com/a/44050230/1673775

1
luke

Bien que ROWID soit une pseudo-colonne, pourtant, étant donné que ROWID correspond à l'adresse physique d'un ROW, il s'agit du moyen le plus rapide pour récupérer des données de ligne. Comme @Id est utilisé pour identifier l'objet de manière unique et que ROWID est unique dans une table, nous pouvons l'exploiter pour résoudre le problème dont nous discutons. En fait, si nous n'avons pas besoin d'identifiant unique significatif, ROWID est la meilleure colonne à annoter avec l'annotation @Id car elle correspond à l'adresse physique de la ligne. Le code suivant a fonctionné pour moi . @Id @Column(name = "ROWID") private String Id;

0
Nazmul Hoque Shafin

J'ai trouvé la solution pour les tables sans clé primaire et null comme valeurs. Cela fonctionnera sur Oracle DB. Peut-être que quelque chose de similaire existe pour d'autres bases de données.

  1. Vous devez créer une nouvelle clé primaire dans la classe POJO:

    @Id @ Column (name = "id") Private Integer id;

et utilisez createNativeQuery comme ceci

getEntityManager().createNativeQuery("select rownum as id, .....

La requête native générera une clé primaire et vous obtiendrez des résultats uniques.

0
Kanaris007