22

Hi I am new to Spring Data JPA and I am wondering even though I pass the Id to the entity, the Spring data jpa is inserting instead of merge. I thought when I implement the Persistable interface and implement the two methods:

public Long getId();
public Boolean isNew();

It will automatically merge instead of persist.

I have an entity class called User like:

@Entity
@Table(name = "T_USER")
public class User implements Serializable, Persistable<Long> {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "USER_ID")
   private Long id;

   @Column(name = "CREATION_TIME", nullable = false)
   private Date creationTime;

   @Column(name = "FIRST_NAME", nullable = false)
   private String firstName;

   @Column(name = "LAST_NAME", nullable = false)
   private String lastName;

   @Column(name = "MODIFICATION_TIME", nullable = false)
   private Date modificationTime;

And have another class

@Entity
@Table(name = "T_USER_ROLE")
public class UserRole implements Serializable, Persistable<Long> {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private Long roleId;

   @Column(name = "ROLE_NAME")
   private String userRole;
}

I have a custom repository called UserRepostory extending JpaReopistory. I am hitting the save for merge and persist as I see the implementation demonstrate that Spring Data Jpa uses above two methods to either update or insert.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

I have been trying to figure out but didn't get any clue. Maybe you guys can help.

Woodchuck
  • 3,869
  • 2
  • 39
  • 70
Rana_S
  • 1,520
  • 2
  • 23
  • 39
  • You don't need to implement the persistable interface. Just use the inherited `save` and Spring Data will handle it for you. – Edward J Beckett Dec 28 '14 at 05:07
  • @Rana_S how did you solve this issue? I have a quite similar issue: [why saveAll() always inserts data instead of update it?](https://stackoverflow.com/questions/65162298/why-saveall-always-inserts-data-instead-of-update-it) – catch23 Dec 06 '20 at 18:30
  • @catch23 I had version column in DB and `@Version` marked on entity field, which was missing when persisting. So had to populate the version field and worked. Saw your post, try adding version column and see how it reacts. Also, would suggest implementing better `equals` and `hashCode` instead of just id. – Rana_S Dec 11 '20 at 05:57
  • @Rana_S thanks for this response. Tried this approach. The same exception is thrown. However, version param is always null - because it is fetched from remote and this field is not presented. Thus, the entity is always treated like "new" – catch23 Dec 11 '20 at 14:57
  • @catch23 So you’re saying you have version column? If so then Spring will evaluate this. Check this out [saving Entities Strategies](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.entity-persistence.saving-entites.strategies). If you are not using version then removing would do the work. – Rana_S Dec 12 '20 at 17:41

2 Answers2

54

I ran into this issue, tried to implement Persistable to no avail, and then looked into the Spring Data JPA source. I don't necessarily see this in your example code, but I have a @Version field in my entity. If there is a @Version field Spring Data will test that value to determine if the entity is new or not. If the @Version field is not a primitive and is null then the entity is considered new.

This threw me for a long time in my tests because I was not setting the version field in my representation but only on the persisted entity. I also don't see this documented in the otherwise helpful Spring Data docs (which is another issue...).

Hope that helps someone!

John Shields
  • 714
  • 7
  • 7
  • 1
    Yeah you are right. I had the Version field in the entity. I never knew Spring Data JPA would look for that field. – Rana_S Jul 27 '15 at 02:38
  • 1
    Thanks for this answer I came across the same issue! – JamesG Dec 20 '17 at 06:26
  • 1
    In regards to the issue with `@Version`, I got around that by adding the Java Validation `@Null` annotation, assigning a low default value to the version field, and removing the setter for the version field. That way the field is never null. I am using an Instant class as the `@Version` field. – BigRedBettaFish Dec 21 '17 at 15:38
  • Thanks for the answer I put this column and I forget to set a default value when i insert from SQL, it took me one day to solve. Thanks mate. – EACUAMBA Jan 23 '23 at 09:50
3

By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity will be assumed as new, otherwise as not new. It's Id-Property inspection Reference

If you are using Spring JPA with EntityManager calling .merge() will update your entity and .persist() will insert.

@PersistenceContext
private EntityManager em;

@Override
@Transactional
public User save(User user) {

    if (user.getId() == null) {
        em.persist(user);
        return user;
    } else {
        return em.merge(user);
    }
}

There is no need to implement the Persistable interface.

Alix
  • 927
  • 7
  • 21
njjnex
  • 1,490
  • 13
  • 23
  • Thank you @njjnex. But when you use the JpaRepository interface and call the save method from the Service class, you see that internally Spring Data JPA check if the entity is new or not. In this way when an entity implements Spring's Persistable interface, it overrides two methods: 1. Boolean isNew() and 2. Long getId(). But it's not working. – Rana_S Dec 28 '14 at 17:48
  • 4
    As I said I am getting into Spring Data Jpa and previously used own custom dao layers. I did the way you mentioned as above by passing the entityManager in dao implementation. That works fine but my question is why it doesn't work in the context of Spring data Jpa. – Rana_S Dec 28 '14 at 17:53