Search code examples
hibernatejpaspring-dataspring-data-jpaderby

Enforcing primary key constraint in Spring JPA


I am using Spring JPA backed by an embedded in-memory database, specifically Apache Derby.

Simply put, I have an entity class like this:

@Entity
public class Person {

    @Id
    @NotNull
    @Column(unique = true, updatable = false, name = "PERSON_ID")
    private Long personId;

    @NotNull
    @Size(min = 1)
    private String name;

    public Person() {
    } 
    //continue....

and in a @Service component I have a method like this:

@Autowired
private PersonRepository personRepository;

@Override
public void addPerson(Person person)  {
        personRepository.save(movie);
}

where the repository is defined as follows:

public interface PersonRepository extends CrudRepository<Person, Long> { }

The problem is the following: the primary key constraint is not enforced as I would expect.

More specifically, If I have two Person object with the same key:

Person person1 = new Person(1L, "Johan");
Person person2 = new Person(1L, "Tom");

and I call in sequence the add method:

serviceObject.addPerson(person1);
serviceObject.addPerson(person2);

what will happen is that the personRepository.save(movie) method is going to save a Person with same ID twice, thus overwriting the former record. In the end there is only one Person with that ID, and its name is "Tom". I retrieve a record using the findOne method as follows:

public Person getPersonById(long personId) {
    Person person = personRepository.findOne(personId);

However I would have expected the second attempt to fail because of some PrimaryKey Constraint Violation, with the name not being changed from "Johan" to "Tom".

Questions:

  • Why this is happening?
  • I understand that I could have enforced this rule at the database level. Is this the only way to achieve it? In my case, using Apache Derby, can I achieve this? Or how to enforce at the JPA level the usual primary key constraint so that records with a duplicate key are rejected rather than overwriting the previous one?

P.S.: I am interested into inserting the primary key manually, so @GeneratedValue or any other mechanism that would prevent in principle to pass twice the same key are not an option.


Solution

  • If you look at the implementation on save method,

    @Transactional
    public <S extends T> S save(S entity) {
    
        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);
        }
    }
    

    it is trying to merge if the record already exists. In your case leave the generation of primary key to spring and use the generated id in your code.