58

Is it possible to create a table (from a JPA annotated Hibernate @Entity) that does not contain a primary key / Id?

I know this is not a good idea; a table should have a primary key.

abhi
  • 1,760
  • 1
  • 24
  • 40
Schildmeijer
  • 20,702
  • 12
  • 62
  • 79
  • You could create a surrogate key that is auto inserted from `Sequence` (or equivalent if you are not using Oracle). For old data you could populate the newly created key with running numbers. – Rosdi Kasim Sep 29 '11 at 07:52

10 Answers10

56

Roger's self-answer is correct. To elaborate a bit on what is meant (I wasn't clear on it at first and figured this would help):

Say you have you have a table Foo as such:

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

Normally, you can write a class w/Annotations to work with this 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;    
  }

}

.. But, darn. This table has nothing we can use as an id, and it's a legacy database that we use for [insert vital business function]. I don't think they'll let me start modifying tables in order for me to use hibernate.

You can, instead, split the object up into a hibernate-workable structure which allows the entire row to be used as the key. (Naturally, this assumes that the row is unique.)

Split the Foo object into two thusly:

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

  @Id
  private FooKey id;

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

  public void getId() {
    return id;
  }
}

and

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

}

.. And that should be it. Hibernate will use the Embeddable key for its required identity and you can make a call as normal:

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

Hope this helps first-timers with getting this working.

awied
  • 2,698
  • 5
  • 33
  • 37
  • 25
    I wanted to mention, because I just had this problem, that if any of those columns you are using for your composite key are NULL, than Hibernate will return null objects. I was pulling everything from a View and one of the columns was all null so hibernate was returning a list with the right number of items in it, but all the items were null. In my case I couldn't modify the field, so I just removed it from the Key. – Casey Nov 23 '10 at 16:25
49

Use following code; Hibernate doesn't have its own logic to distinguish duplicate records

Let me know if there are any issues with this approach

@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;    
  }
}
McDowell
  • 107,573
  • 31
  • 204
  • 267
Amit
  • 491
  • 4
  • 2
  • 2
    This is THE best answer. It uses itself as the ID class w/o having to do two separate classes. I moved the Id annotations to my getters since they had the needed Column mappings. It's working perfect and it doesnt break caching. +1 – John Strickler May 04 '12 at 18:45
  • 1
    @JohnStrickler: is there any drawback of this approach? It looks more like a hack – Hoàng Long Oct 02 '12 at 08:53
  • 1
    So far I've found none. You only need to annotate the properties that are unique (which could be all of them if none are unique). I wouldn't classify it as a hack - it's using JPA annotations the way they were meant to be used. – John Strickler Oct 02 '12 at 14:41
  • 3
    @HoàngLong: as [@Casey](http://stackoverflow.com/questions/767277/hibernate-and-no-pk#comment4613715_1776125) said, your columns can't be NULL. – LoganMzz Jun 02 '14 at 14:02
  • 3
    At least Hibernate-3.5.2 won't completely work with that really nice approach. Everything seemed to work fine for me until I tried to retrieve entities with null columns. Even if I did not mark them as `@Id` Hibernate used ALL columns as a primary key and can't handle null values in primary keys. This leaded to null objects in retrieved results. Copying all `@Id` fields into a static inner class, generating getters/setters again there as well as equals() and hashCode() methods and using this inner class as `@IdClass` did the job for me. – GreenTurtle Feb 27 '17 at 15:56
  • Works like a dream. If you are using JpaRepository/ CrudRepository with it, you could do something like this; public interface FooRepository extends JpaRepository Foo findByBar(String bar); – ScottSummers Nov 09 '18 at 10:56
  • Worked for me :) – Gunnlaugur Jan 17 '19 at 09:32
10

i found that this trick works:

<id column="ROWID" type="string" />
RudiDudi
  • 455
  • 1
  • 7
  • 18
9

I found that its not possible to do so. So bad luck for those working with legacy systems.

If you reverse engineer (create JPA annotated entities from existing JDBC connection) the table will create two Java classes, one Entity and with one field; id, and one embeddable id containing all the columns from your relation.

Tiny
  • 27,221
  • 105
  • 339
  • 599
Schildmeijer
  • 20,702
  • 12
  • 62
  • 79
5

Though ROWID is a pseudo-column,yet as ROWID corresponds to the physical address of a ROW, it is the quickest mean to retrieve any row data. As @Id is used to identify the object uniquely and ROWID is unique inside a table, we can exploit it to overcome the issue we are discussing about. Actually, if we don't need any meaningful unique identifier, ROWID is the best column to annotate with @Id annotation as it corresponds to the physical address of the row. The following code worked for me. @Id @Column(name = "ROWID") private String Id;

  • How do you prevent Hibernate from trying to select FOO.ROWID, which is invalid, and use just "ROWID" or "ROWNUM" without trying to prefix? – Sloloem Feb 05 '20 at 16:05
2

When it comes to views instead of searching for workarounds in Hibernate it might be easier to add dummy id in your database view. I wrote about it in another question: https://stackoverflow.com/a/44050230/1673775

Community
  • 1
  • 1
luke
  • 3,435
  • 33
  • 41
2

You don't need to create a separate class to be your @Id or Primary Key. Just use an Integer (or whatever). Also, don't publish the fake key as developers who use it might think it's real and otherwise try to use it. Lastly, this is best used in a VIEW. I agree with earlier posts that in most, if not all cases, tables should have a primary key. For example:

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


}
Domenic D.
  • 5,276
  • 4
  • 30
  • 41
  • 3
    This does not work. Got some column not found error after adding this. – Jeremy Aug 12 '11 at 08:52
  • This does work. You have to do it on a view. Also, of course, change the members to columns of your database. If you used "realColumn", well of course it won't work. I've used this approach in a real system. – Domenic D. Dec 06 '11 at 02:18
  • 3
    Getting an org.hibernate.HibernateException: Missing column: id in View_Blabla when I try to use this in on a View – StefanTo Nov 18 '16 at 08:26
1

To Create the Pojo from table - use the reverse Engineering method existing in eclipse. For the non- primary key table, eclipse will generate the two Pojo classes.

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());
            }
        }      
}
Andy
  • 49,085
  • 60
  • 166
  • 233
Kuldeep
  • 11
  • 1
1

Adding to Awied's comment. If then you want to search for a bar, use following HQL.

Query fooQuery = getSession().createQuery("from Foo.id.bar = '<barName>'");
punkck
  • 83
  • 5
0

I have found solution for tables without primary key and null as values. It will work on oracle DB. Maybe something similar exists for other DBs.

  1. You should create new primary key in the POJO class:

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

and use createNativeQuery like this

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

The native query will generate primary key and you will get unique results.

Kanaris007
  • 304
  • 2
  • 6
  • Be aware that this trick behaves very badly with cache (same rownum may references different "entities" when query is executed several times with different parameters) .. so make sure your `@Entity` POJO never goes in the cache... – p3consulting May 16 '18 at 07:02