meerlol
meerlol

Reputation: 366

Spring JPA @ManyToOne save fails with duplicate key

I have the following entities:

@Entity
@Data
@Table(name = "Parents")
public class Parent {
    @Id
    @GeneratedValue
    Long id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "child")
    private Child child;
}

And

@Entity
@Data
@Table(name = "Childs")
public class Child {
    @Id
    String number;
}

For both entities I have a standard CrudRepository. At startup of the app I try to populate the database with this code:

@Component
public class TestFiller implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    ParentRepository parentRepository;

    @Autowired
    ChildRepository childRepository;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        for (int i = 0; i < 100; i++) {
            Parent parent = new Parent();

            String childId = "CHILD" + Math.round(Math.random() * 10);
            Optional<Child> childOpt = childRepository.findById(childId);
            if (childOpt.isPresent()) {
                parent.setChild(childOpt.get());
            } else {
                Child child = new Child();
                child.setNumber(childId);
                parent.setChild(child);
            }

            parentRepository.save(parent);
        }
    }
}

This is the ChildRepository:

@Repository
public interface ChildRepository extends JpaRepository<Child, String> {
}

But for some inexplicable reason this fails when a child is already present in the database:

Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "childs_pkey"
  Detail: Key (number)=(CHILD6) already exists.
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2182) ~[postgresql-9.4-1206-jdbc42.jar:9.4]
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1911) ~[postgresql-9.4-1206-jdbc42.jar:9.4]
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:173) ~[postgresql-9.4-1206-jdbc42.jar:9.4]
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:645) ~[postgresql-9.4-1206-jdbc42.jar:9.4]
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:495) ~[postgresql-9.4-1206-jdbc42.jar:9.4]
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:441) ~[postgresql-9.4-1206-jdbc42.jar:9.4]

It almost looks like the entity returned by the findById method is not managed. Why is Hibernate/JPA trying to persist the existing entity?

Edit: as per request I added the ChildRepository

Upvotes: 2

Views: 3153

Answers (3)

meerlol
meerlol

Reputation: 366

Ok, to answer my own question:

it is caused by the CascadeType @ManyToOne(cascade = CascadeType.ALL) on the Child property of the Parent. Removing that fixes it.

Apparently JPA cascades the PERSIST operation to the Child as well, even though it could have easily known that the Child is already a managed and persisted entity.

Upvotes: 2

user8826113
user8826113

Reputation: 129

As per the java documentation

This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.

so first try to get the object from the result and check if it is the expected value :-

  if (childOpt.isPresent()) {
           Child checkObj=childOpt.get():
           parent.setChild(checkObj);
  }

Upvotes: 0

Narendra Kekane
Narendra Kekane

Reputation: 86

I think this is because of your String ID. Can you please try with findOne() method it may help you.

String childId = "CHILD" + Math.round(Math.random() * 10);
Child child = new Child();
child.setNumber(childId);
Child childOpt = childRepository.findOne(child);

Upvotes: 0

Related Questions