NaorDavid2
NaorDavid2

Reputation: 51

jpa one to many insert duplicate key insert

I defined 2 entities with unidirectional one to many relationship:

in Command.class:

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="STOCK_ID", referencedColumnName="id")
public StockDetails getStockDetails() {
    return stockDetails;
}

in StockDetails.class:

@Id
public String getId() {
    return id;
}

means that Command holds one StockDetails and StockDetails can be in many Commands, but not holding the commands.

I have 2 problems:

  1. when I'm trying to insert new command with existing StockDetails I got Exception on "cannot insert duplicate key in object..." then I changed the CascadeType to MERGE and then I dont get this exception. but, when the cascade type is MERGE, when I have new StockDetails which not exsits yet in the DB, i get exception on

object references an unsaved transient instance - save the transient instance before flushing

  1. when the cascade type is MERGE AND I change some details iN StockDetils, and insert new Command with the changed StockDetails, this not Updates the existing record example of what i try:

    StockDetails sd1 = new StockDetails("GOOL", "Google Inc");
    Command c1 = new Command(123456l, 12345614l, sd1, GlobalVeriables.COMMAND_TYPE_ASK, 200, 300d, 1200d,
            new Date(System.currentTimeMillis()), GlobalVeriables.COMMAND_STATUS_OPEN); 
    commandManager.addNewCommand(c1);
    
    StockDetails sd2 = new StockDetails("GOOL", "Google Inc LTD");
    Command c2 = new Command(5674l, 5678l, sd2, GlobalVeriables.COMMAND_TYPE_ASK, 200, 300d, 1200d,
            new Date(System.currentTimeMillis()), GlobalVeriables.COMMAND_STATUS_OPEN); 
    commandManager.addNewCommand(c2);
    

Please help me solv that :)

Upvotes: 0

Views: 3736

Answers (1)

ujulu
ujulu

Reputation: 3309

Problem 1

when I'm trying to insert new command with existing StockDetails I got Exception on "cannot insert duplicate key in object..."

I guess this happens if one the following case occurs:

  1. you have detached instance of StockDetails, or
  2. set the ID of StockDetails manually, and do the folloing:

    c1.setStockDetails(sd1);
    entityManager.persist(c1);
    

    What is happening here is the following: the entity manager tries to persist c1. As CascadeType.PERSIST is in place it tries to propagate the persist operation on sd1 too to find out that there is an entry in the database with the same ID which is causing constraint violation exception.

Solution1

// start transaction here
StockDetails sd1 = entityManager.find(StockDetails.class, <id_of_existing_stockdetails>);
c1.setStockDetails(sd1);
entityManager.persist(c1);
// commit transaction here

Problem 2

object references an unsaved transient instance - save the transient instance before flushing

You are getting this exception if you are doing the following:

@ManyToOne(cascade = CascadeType.MERGE)
@JoinColumn(name="STOCK_ID", referencedColumnName="id")
public StockDetails getStockDetails() {
    return stockDetails;
}

and

StockDetails sd1 = new StockDetails("GOOL", "Google Inc");
Command c1 = new Command(123456l, 12345614l, sd1,...);
c1.setStockDetails(sd1);
commandManager.addNewCommand(c1); // assuming this line is saving c1

In this case, you're telling the entity manager to persist c1 assigning it a new sd1 (unsaved instance). Now the entity manager has a problem: you told it to save the id of the StockDetails in its foreign key field, but the object you associated is not saved yet and does not have an id (because you are telling to propagate MERGE but not PERSIST). That is the reason why you're getting the exception.

Solution 2

You have two options to solve the problem:

  1. First save the sd1 and assign the persistent version to c1 and save it as follows:

    // start transaction
    entityManager.persist(sd1);
    StockDetails sdPersistent = entityManager.find(StockDetails.class, <id>); // where id is the primary key of the newly saved sd1
    c1.setStockDetails(sdPersistent);
    entityManager.persist(c1);
    // commit transaction
    

    or

  2. Change the cascade attribute to CascadeType.PERSIST, which is the better option in this case (see above Solution1).

Upvotes: 1

Related Questions