【问题标题】:Hibernate and no PK休眠无PK
【发布时间】:2010-10-20 12:25:21
【问题描述】:

是否可以创建一个不包含主键/Id 的表(来自 JPA 注释的 Hibernate @Entity)?

我知道这不是一个好主意;一个表应该有一个主键。

【问题讨论】:

  • 您可以创建一个从 Sequence 自动插入的代理键(如果您不使用 Oracle,则为等效键)。对于旧数据,您可以使用运行数字填充新创建的键。

标签: java database hibernate jpa


【解决方案1】:

罗杰的自我回答是正确的。详细说明一下是什么意思(起初我并不清楚,认为这会有所帮助):

假设你有一个这样的表 Foo:

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

通常,您可以编写一个带注释的类来使用此表:

// 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;    
  }

}

.. 但是,该死的。该表没有我们可以用作 id 的任何内容,它是我们用于 [插入重要业务功能] 的遗留数据库。我认为他们不会让我开始修改表以便我使用休眠。

相反,您可以将对象拆分为可用于休眠的结构,该结构允许将整行用作键。 (当然,这假设该行是唯一的。)

将 Foo 对象分成两部分:

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

  @Id
  private FooKey id;

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

  public void getId() {
    return id;
  }
}

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

}

.. 应该就是这样。 Hibernate 将使用 Embeddable 密钥作为其所需的身份,您可以正常拨打电话:

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

希望这有助于初次使用此功能的人。

【讨论】:

  • 我想提一下,因为我刚刚遇到了这个问题,如果您用于复合键的列中的任何一个为 NULL,那么 Hibernate 将返回 null 对象。我从视图中提取所有内容,其中一列全部为空,因此休眠返回一个列表,其中包含正确数量的项目,但所有项目均为空。就我而言,我无法修改该字段,所以我只是将其从 Key 中删除。
【解决方案2】:

使用以下代码; Hibernate没有自己的逻辑来区分重复记录

如果这种方法有任何问题,请告诉我

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

【讨论】:

  • 这是最好的答案。它使用自己作为 ID 类,而不必做两个单独的类。我将 Id 注释移到我的 getter 中,因为它们具有所需的 Column 映射。它运行良好,并且不会破坏缓存。 +1
  • @JohnStricler:这种方法有什么缺点吗?它看起来更像是一个黑客
  • 到目前为止我还没有找到。您只需要注释唯一的属性(如果没有唯一的属性,则可能是所有属性)。我不会将其归类为 hack - 它使用 JPA 注释的方式是它们的使用方式。
  • @HoàngLong:正如@Casey 所说,您的列不能为NULL。
  • 至少 Hibernate-3.5.2 不能完全使用这种非常好的方法。在我尝试检索具有空列的实体之前,一切似乎对我来说都很好。即使我没有将它们标记为@IdHibernate 使用所有列作为主键并且无法处理主键中的空值。这导致检索结果中出现空对象。将所有 @Id 字段复制到静态内部类中,在那里再次生成 getter/setter 以及 equals() 和 hashCode() 方法,并将这个内部类用作 @IdClass 为我完成了这项工作。
【解决方案3】:

我发现这个技巧很有效:

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

【讨论】:

  • 你太厉害了!这是一个完美的解决方案!
  • 在hbm.xml文件开头好像定义了一个主键
  • 当您使用任何形式的休眠缓存时,这不是一个好主意
  • rowid 用缓存保存,rownum 会让你陷入麻烦
【解决方案4】:

我发现这是不可能的。对于那些使用遗留系统的人来说,运气太差了。

如果您进行逆向工程(从现有 JDBC 连接创建 JPA 注释实体),该表将创建两个 Java 类,一个实体和一个字段; id,以及一个包含关系中所有列的可嵌入 id。

【讨论】:

  • 真的吗?下面@awied 的解决方案呢?它 WFM。
  • javamonkey79,@awied 的解决方案和上面的解决方案是一样的。
  • 所以它是可能的。另请参阅:sourcedexter.com/…
【解决方案5】:

虽然ROWID是一个伪列,但是由于ROWID对应的是一个ROW的物理地址,它是检索任何行数据的最快方法。由于@Id 用于唯一标识对象,而 ROWID 在表中是唯一的,我们可以利用它来解决我们正在讨论的问题。实际上,如果我们不需要任何有意义的唯一标识符,ROWID 是使用@Id 注释进行注释的最佳列,因为它对应于行的物理地址。以下代码对我有用。 @Id @Column(name = "ROWID") private String Id;

【讨论】:

  • 如何防止Hibernate尝试选择无效的FOO.ROWID,只使用“ROWID”或“ROWNUM”而不尝试前缀?
【解决方案6】:

您不需要创建一个单独的类来作为您的@Id 或主键。只需使用整数(或其他)。另外,不要发布假密钥,因为使用它的开发人员可能会认为它是真实的并尝试使用它。最后,这最好用在 VIEW 中。我同意之前的帖子,在大多数情况下(如果不是所有情况),表都应该有一个主键。例如:

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


}

【讨论】:

  • 这不起作用。添加此内容后出现未找到某些列的错误。
  • 这确实有效。您必须在视图上执行此操作。此外,当然,将成员更改为数据库的列。如果您使用“realColumn”,那么它当然不会起作用。我在实际系统中使用过这种方法。
  • 获取 org.hibernate.HibernateException: Missing column: id in View_Blabla 当我尝试在视图中使用它时
【解决方案7】:

从表中创建 Pojo - 使用 eclipse 中存在的逆向工程方法。 对于非主键表,eclipse会生成两个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());
            }
        }      
}

【讨论】:

    【解决方案8】:

    添加到 Awied 的评论中。 如果您想搜索柱线,请使用以下 HQL。

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

    【讨论】:

      【解决方案9】:

      当涉及到视图而不是在 Hibernate 中搜索解决方法时,在数据库视图中添加虚拟 id 可能更容易。我在另一个问题中写过:https://stackoverflow.com/a/44050230/1673775

      【讨论】:

        【解决方案10】:

        我找到了没有主键和 null 作为值的表的解决方案。它将在 oracle DB 上运行。也许其他数据库也存在类似的情况。

        1. 您应该在 POJO 类中创建新的主键:

          @Id @Column(name="id") 私有整数 id;

        并像这样使用 createNativeQuery

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

        原生查询会生成主键,你会得到唯一的结果。

        【讨论】:

        • 请注意,这个技巧在缓存中表现得非常糟糕(当使用不同的参数多次执行查询时,相同的 rownum 可能会引用不同的“实体”).. 所以请确保你的 @Entity POJO 永远不会进入缓存...
        猜你喜欢
        • 2013-06-04
        • 1970-01-01
        • 2016-03-17
        • 2023-03-09
        • 1970-01-01
        • 1970-01-01
        • 2011-12-16
        • 2012-09-18
        • 1970-01-01
        相关资源
        最近更新 更多