10

I learn about JPA and had task to make database and insert some values to it. I wondered how I can find out what was the ID of recently inserted object, so I found a way that I need to use flush method of EntityManager.

Unfortunately I got the

Null or zero primary key encountered in unit of work clone

exception when I use the above method. I think the problem lies in that my database has all ID's set on autoincrement ( I use ORACLE 11G Express ), so before commiting it has null value and it rollbacks transaction.

What I can do to fix it ?

This is DB ( ID's are autoincrement[Sequences and Triggers in oracle]):

public class Client {    
    public static void main(String[] args) {
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaIntroductionPU");        
        EntityManager em = emf.createEntityManager();
        EntityTransaction et = em.getTransaction();

        et.begin();

        Address ad1 = new Address();
        ad1.setStreet("Skaraktki");
        ad1.setCode("64-340");

        em.persist(ad1);
        em.flush();

        System.out.println(ad1.getAId());
        et.commit();
    }   
}

Address class

@Entity
@Table(name = "ADDRESS")
@NamedQueries({
    @NamedQuery(name = "Address.findAll", query = "SELECT a FROM Address a"),
    @NamedQuery(name = "Address.findByAId", query = "SELECT a FROM Address a WHERE a.aId = :aId"),
    @NamedQuery(name = "Address.findByStreet", query = "SELECT a FROM Address a WHERE a.street = :street"),
    @NamedQuery(name = "Address.findByCode", query = "SELECT a FROM Address a WHERE a.code = :code")})
public class Address implements Serializable {
    private static final long serialVersionUID = 1L;
    // @Max(value=?)  @Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation
    @Id
    @Basic(optional = false)
    @Column(name = "A_ID")
    private BigDecimal aId;

    @Basic(optional = false)
    @Column(name = "STREET")
    private String street;

    @Basic(optional = false)
    @Column(name = "CODE")
    private String code;

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "aId")
    private Employee employee;

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "aId")
    private Department department;

    public Address() {
    }

    public Address(BigDecimal aId) {
        this.aId = aId;
    }

    public Address(BigDecimal aId, String street, String code) {
        this.aId = aId;
        this.street = street;
        this.code = code;
    }

    public BigDecimal getAId() {
        return aId;
    }

    public void setAId(BigDecimal aId) {
        this.aId = aId;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Employee getEmployee() {
        return employee;
    }

    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }       

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (aId != null ? aId.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Address)) {
            return false;
        }
        Address other = (Address) object;
        if ((this.aId == null && other.aId != null) || (this.aId != null && !this.aId.equals(other.aId))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "jpaintroduction.Address[ aId=" + aId + " ]";
    }

}
ashur
  • 4,177
  • 14
  • 53
  • 85

3 Answers3

19

This happend to me because I manually added an entry to my database with the id 0 (zero). In my case EclipseLink "couldn't" handle an id with zero. So I added following to my persistence.xml:

     <property name="eclipselink.allow-zero-id" value="true"/>

This property says EclipseLink to handle zero as a valid id.

[1] http://meetrohan.blogspot.de/2011/11/eclipselink-null-primary-key.html

Ahmad Hosny
  • 597
  • 1
  • 6
  • 23
kdoteu
  • 1,527
  • 1
  • 20
  • 26
  • 2
    Thanks. This is the exact solution I was looking for. I had what @Camilo Bermudez suggested (GeneratedValue annotation) yet I started getting this error at the exact same time after I added a user in my DB with ID = 0. – rbaleksandar May 30 '15 at 10:55
  • This happened to me when using a *SEQUENCE*. [Oracle](https://docs.oracle.com/cd/B12037_01/server.101/b10759/statements_6014.htm) starts with 1, but [HSQLDB](http://www.hsqldb.org/doc/guide/ch09.html#create_sequence-section) starts with 0. – SebastianH Feb 29 '16 at 12:44
  • 2
    This property is now deprecated. Use `` – Bedla May 13 '19 at 11:51
  • do you have any offical documentation about the deprecation? – kdoteu May 16 '19 at 13:06
  • @kdoteu sure, it is stated in javadoc: https://www.eclipse.org/eclipselink/api/2.7/org/eclipse/persistence/config/PersistenceUnitProperties.html#ALLOW_ZERO_ID – Bedla May 21 '19 at 23:22
6

You need to annotate your id field with @GeneratedValue, in order for JPA to know that the DB will generate the id automatically:

@Id
@Basic(optional = false)
@Column(name = "A_ID")
@SequenceGenerator( name = "mySeq", sequenceName = "MY_SEQ", allocationSize = 1, initialValue = 1 )
@GeneratedValue(strategy=GenerationType.IDENTITY, generator="mySeq")
private BigDecimal aId;

With oracle you can use GenerationType.IDENTITY and @SequenceGenerator in which case you don't need a trigger to query the sequence and populate the ID, JPA will do it for you. I'm not sure if GenerationType.AUTO will work with oracle but if it does, you'd need a trigger to query the sequence and populate the id. GenerationType.TABLE is the most portable solution, since you use an independent table managed by JPA to store the sequence, it works across all databases.

Check the docs in the link above.

Camilo
  • 1,889
  • 2
  • 17
  • 16
  • I needed to add the following line `@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "SEQ_ADDRESS")`, all other combinations with `SEQUENCE`, `AUTO` etc. throw exceptions. I'm not sure is it working correctly or it's just an accident. For `AUTO` it throws `table or view does not exist`, for `SEQUENCE` - `The sequence named [SEQ_ADDRESS] is setup incorrectly. Its increment does not match its pre-allocation size.` Could you maybe explain it to me please ? :P – ashur Feb 26 '14 at 10:33
  • 1
    sure, just edited, again, if you need more info there's plenty of documentation about this around. good luck. – Camilo Feb 26 '14 at 18:57
0

As you said, the problem lies in the fact that even when you commit a transaction, the Auto-increment function isn't invoked right away. Actually, while you are managing database interactions with the same EntityManager it won't. My question would rather be: why are you persisting, then committing the transaction if you flush it anyway? Looks like duplicated code to me. See this on flush.

  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/28496051) – Giulio Caccin Mar 10 '21 at 08:42
  • @GiulioCaccin This is an Answer. "the Auto-increment function isn't invoked right away. Actually, while you are managing database interactions with the same EntityManager it won't" is answering. – Scratte Mar 10 '21 at 09:06
  • The question include code that can be involved in your answer. The link you referred also include code, i think it should be better for your answer to include relevant code, thus making your answer valid even if the link is down. – Giulio Caccin Mar 10 '21 at 09:15
  • Thank you for the tip. I'll keep it in mind ; I am still unused to the tooltip and was trying to keep the answer as short as possible. – JavaProScript Mar 16 '21 at 13:55