2

I have an oracle db table with columns which are in my FoobarId class.All 3 fields in FooarId are a composite pk in my Foobar table.

I'm inserting data okay on the first run. But when I run it again I'm getting a ORA-00001: unique constraint that my PK is violated. I have similar code in other projects that runs fine. This one seems to not update the record but instead trying to do an insert again. Is there something wrong with my embeddedId?

My entity class:

@Entity
@Table(name = "FOOBAR_TABLE")
public class Foobar implements Serializable {

    private static final long serialVersionUID = 4290109857781985996L;

    private FoobarId foobarId;

    private int containerId;
    private String containerType;
    private String action;

    public Foobar() { }

    public Foobar(String itemName, int itemId, Date itemDate) {
        foobarId = new FoobarId(itemName, itemId, itemDate);
    }

    @EmbeddedId
    public FoobarId getFoobarId() {
        return foobarId;
    }

    public void setFoobarId(FoobarId foobarId) {
        this.foobarId = foobarId;
    }

    // getters and settters

}

My id class:

@Embeddable
public class FoobarId  implements Serializable{

    private static final long serialVersionUID = 7393136028821250311L;

    private int itemId;
    private String itemName;
    private Date itemDate;

    public FoobarId(){}

    public FoobarId(String itemName, int itemId, Date itemDate) {
        super();
        this.itemId = itemId;
        this.itemName = itemName;
        this.itemDate = itemDate;
    }

    // getters/setters

    @Override
 public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((itemDate == null) ? 0 : itemDate.hashCode());
    result = prime * result + ((itemName == null) ? 0 : itemName.hashCode());
    result = prime * result + itemId;
    return result;
}


@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    FoobarId other = (FoobarId) obj;
    if (itemDate == null) {
        if (other.itemDate != null) {
            return false;
        }
    } else if (!itemDate.equals(other.itemDate)) {
        return false;
    }
    if (itemName == null) {
        if (other.itemName != null) {
            return false;
        }
    } else if (!itemName.equals(other.itemName)) {
        return false;
    }
    if (itemId != other.itemId) {
        return false;
    }
    return true;
}
}

I have the necessary Repository interface which extends CrudRepository.

In my service class I'm saving a list of Foobar items:

@Transactional
public void addList(List<Activity> itemList) {
    logger.info("Saving Activity List size of: " + itemList.size());
    activityRepository.save(itemList);
}

create table sql:

  CREATE TABLE "FOOBAR" 
   (    
    "ITEM_NAME" VARCHAR2(50 CHAR) NOT NULL ENABLE, 
    "ITEM_ID" NUMBER NOT NULL ENABLE, 
    "ITEM_DATE" DATE NOT NULL ENABLE, 
    "CONTAINER_ID" NUMBER, 
    "CONTAINER_TYPE" VARCHAR2(50 CHAR), 
    "ACTION" VARCHAR2(50 CHAR), 
     CONSTRAINT "FOOBAR_PK" PRIMARY KEY ("ITEM_NAME", "ITEM_ID", "ITEM_DATE")
     )
Micho Rizo
  • 1,000
  • 3
  • 12
  • 27
  • You're not really sharing code that has to do with update/insert. Do you have a DAO class that handles that (or what `activityRepository.save()` does)? – Mick Mnemonic Apr 09 '19 at 23:15
  • Can you share your overridden equals() and hashCode() methods? – Player One Apr 09 '19 at 23:19
  • @Mick activityRepository.save() call the CrudRepository save method ... 99% of the time nothing else to do but use out of the box save methods – Micho Rizo Apr 09 '19 at 23:30
  • @PlayerOne, code updated with equals and hashCode methods. These were auto generated by Eclipse. – Micho Rizo Apr 09 '19 at 23:33
  • Did you try annotating the id with `@EmbeddedId FoobarId foobarId;` directly on the field level? – Mick Mnemonic Apr 09 '19 at 23:38
  • @MickMnemonic, same result with annotation at field level. – Micho Rizo Apr 09 '19 at 23:42
  • Could you share more details about the composite PK? I would say that it's a bad practice to include a `DATE` column in the PK; one could even argue that a (simple) composite PK comes with lots of problems. What if you have entities with the other PK values equal but the `itemDate` values only differing by a fraction of a second? This would not work. – Mick Mnemonic Apr 09 '19 at 23:50
  • There are quite a few different semantics for "date". `java.sql.Date` is a date representation without _any_ time information, whereas `java.util.Date` includes the time with millisecond precision. On the other hand, Oracle SQL `DATE` data type means a date together with time up to one second precision. – Mick Mnemonic Apr 10 '19 at 00:06
  • @MickMnemonic post updated with simplified create table sql statement. Good point with date field, I can update it to timestamp to get fraction second resolution which should suffice. But I would still expect an update instead of an insert. – Micho Rizo Apr 10 '19 at 00:18
  • But what is `Date itemDate` in `FoobarId`? If it's a `java.sql.Date`, the equality semantics for that field are different from `java.util.Date` (any time within the same date equal vs any time within the same millisecond). – Mick Mnemonic Apr 10 '19 at 00:20
  • itemDate in FoobarId is java.util.Date and my getItemDate() has @Temporal(TemporalType.TIMESTAMP) annotation ... EDIT: changed db type to timestamp, looks like maybe took care of issue. Not getting excption anymore. @Mick, post answer and I'll accept since you led me down correct path – Micho Rizo Apr 10 '19 at 00:24

2 Answers2

1

equals on Java Date objects is unreliable when working with databases (see this question for example).

Change your equals method to compare the result of getTimeInMillis on each date, or better yet don't use a date as part of your primary key.

Spring-Data-Jpa also supports java.time classes such as LocalDate or LocalDateTime, which are better options than Date as well.

Player One
  • 607
  • 6
  • 12
1

"Dates" are tricky because the semantics depend on the context. For example, java.util.Date includes the time with millisecond precision but Oracle's SQL DATE data type means a date together with time up to one second precision. If your Java code is using the equality semantics of java.util.Date on a PRIMARY KEY column that has the SQL DATE data type, you will see unique values (from the JPA point-of-view) persisted into the database violating the UNIQUE CONSTRAINT any time when only a fraction of a second of the time is different.

As you seem to have observed, changing the column data type in the DB into TIMESTAMP will increase the time resolution of the column -- and your key-space, resolving the issue.

Mick Mnemonic
  • 7,808
  • 2
  • 26
  • 30
  • Thanks, luckily I was able to change the db datatype from date to timestamp and that took care of my issue. Unfortunately I'm stuck with having to have this as part of my composite pk. – Micho Rizo Apr 10 '19 at 01:31