5

I have one entity having composite key and I am trying to persist it by using spring data jpa repository to mysql databse as given below:

@Embeddable
public class MobileVerificationKey implements Serializable{
private static final long serialVersionUID = 1L;

@Column(name="CUSTOMERID")
private Long customerId;

@Column(name="CUSTOMERTYPE")
private Integer customerType;

@Column(name="MOBILE")
private Long mobile;
@Embeddable
public class MobileVerificationKey implements Serializable{

    private static final long serialVersionUID = 1L;

    @Column(name="CUSTOMERID")
    private Long customerId;

    @Column(name="CUSTOMERTYPE")
    private Integer customerType;

    @Column(name="MOBILE")
    private Long mobile;
//getter and setters
}

And Entity as

@Entity
@Table(name="mobileverificationdetails")
public class MobileVerificationDetails {

    @EmbeddedId
    private MobileVerificationKey key;

    @Column(name="MOBILETYPE")
    private String mobileType;

    @Column(name="MOBILEPIN")
    private Integer mobilePin;
//getters and setters

}

My spring data jpa repository look like this:

public interface MobileVerificationDetailsRepository extends
        CrudRepository<MobileVerificationDetails, MobileVerificationKey> {

    @Override
    MobileVerificationDetails save(MobileVerificationDetails mobileVerificationDetails);

    @Override
    MobileVerificationDetails  findOne(MobileVerificationKey id);
}

Now if I am trying to add duplicate record with same key for original record and different values for other fields .when i try to insert second record it results in update of existing record with new values instead of throwing exception for violating primary key constraint...can any one please explain me this behavior.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
MasterCode
  • 975
  • 5
  • 21
  • 44
  • What is your update code (DAO)? You have only model code here. – Atilla Ozgur Nov 26 '14 at 14:34
  • Spring data jpa repository merges your entity if it already exists. Naked EntityManager#persist() shall produce the behaviour you are asking for. – Michal Nov 26 '14 at 15:53
  • @Michal is there any solution using spring data jpa? – MasterCode Nov 27 '14 at 04:01
  • You might extend your spring data jpa repository/ repositories with custom persist() method and implement it via EntityManager#persist(), the EntityManager can be injected into the implementation class of the custom method. That would be the official, supported way of doing it. – Michal Nov 27 '14 at 09:30
  • You might also try to overwrite the save() method on SimpleJpaRepository directly. You would then need to change the spring internal wiring in order for your extension being used in place of SimpleJpaRepository. Note that tinkering with SimpleJpaRepository is not the official/ supported way of doing things and might - or might not - have some side effects. – Michal Nov 27 '14 at 09:38
  • Both of the options are doable, if one of them is OK for you, I might register an answer with one or both of them and add some links etc /both has been done before/. – Michal Nov 27 '14 at 09:40
  • @Michal OK..Please add that information – MasterCode Nov 27 '14 at 14:01
  • It turns out Spring Framework documents / suport both approaches as described in spring data docs 1.3.1 Adding custom behavior to single repositories and 1.3.2 Adding custom behavior to all repositories. My answer takes that into account. – Michal Nov 27 '14 at 14:36

2 Answers2

4

The easiest (and least invasive) way to work around this is probably by making sure the id only gets set right before the persist. This can be achieved in a @PrePersist callback:

abstract class MobileVerificationDetails {

  @EmbeddedId
  private MobileVerificationKey id;

  @PrePersist
  void initIdentifier() {

    if (id == null) {
      this.id = … // Create ID instance here.
    }
  }
}

Alternatively to that you can enforce persist(…) being used by implementing Persistable and implementing isNew() accordingly. Make sure this method returns true on first insert. We usually see people holding a transient boolean flag that is updated in an @PostPersist/@PostLoad annotated method.

abstract class AbstractEntity<ID extends Serializable> implements Persistable<ID> {

  private @Transient boolean isNew = true;

  @Override
  public boolean isNew() {
    return isNew;
  }

  @PostPersist
  @PostLoad
  void markNotNew() {
    this.isNew = false;
  }
}
Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • Thanks for pointing that out, I was not aware of that. I think this is nice solution as long as one can live with the import org.springframework.data.domain.Persistable somewhere in the model project. Might be OK for some users and not for another. – Michal Nov 27 '14 at 17:07
  • @Michal - You can also tweak the `EntityInformation` instance used by the repository by customizing the `JpaRepositoryFactory` and `FactoryBean`. However, this is usually more effort. An alternative to that is making sure the identifier property only gets set pre-persist. I'll update my answer accordingly. – Oliver Drotbohm Nov 28 '14 at 07:45
2

Spring Data Jpa Repository functionality is implemented via the SimpleJpaRepository class containing following 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);
    }
}

Thus the Spring Jpa Data Repository save(...) method merges an already existing entity.

Opposed to that the naked EntityManager#persist() throws an exception if invoked with already existing entity.

The problem might be solved by adding custom behavior to Spring Data Repository/ies. The custom behavior might be added using one of the approaches as described in 1.3.1 Adding custom behavior to single repositories with example here or in 1.3.2 Adding custom behavior to all repositories with example here. In both cases the custom behavior would include a new persist() method delegating to EntityManager#persist(). Note that in approach 1.3.2. you already have a EntityManager instance, in the approach 1.3.1 you are able to inject EntityManager instance using the @PersistenceContext.

Opposed to my comment I would recommend adding new method to the repository and not overwriting the existing save(...).

Community
  • 1
  • 1
Michal
  • 2,353
  • 1
  • 15
  • 18
  • I would recommend adding new method to the repository and not overwriting the existing save(...). Could you please suggest, how to do this? – PAA Jul 03 '19 at 13:50
  • It stands already in the answer - the linked-in documentation and example shows how to do exactly that. Is there anything still not clear? – Michal Jul 04 '19 at 13:41