ChatGPT解决这个技术问题 Extra ChatGPT

Why use returned instance after save() on Spring Data JPA Repository?

Here is the code:

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {}

JpaRepository from Spring Data JPA project.

Here is the testing code:

public class JpaAccountRepositoryTest extends JpaRepositoryTest {
    @Inject
    private AccountRepository accountRepository;

    @Inject
    private Account account;

    @Test
    @Transactional
    public void createAccount() {
        Account returnedAccount = accountRepository.save(account);

        System.out.printf("account ID is %d and for returned account ID is %d\n", account.getId(), returnedAccount.getId());
    }
}

Here is the result:

account ID is 0 and for returned account ID is 1

Here is from CrudReporsitory.save() javadoc:

Saves a given entity. Use the returned instance for further operations as the save operation might have changed the entity instance completely.

Here is the actual code for SimpleJpaRepository from Spring Data JPA:

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

So, the question is why do we need to use the returned instance instead of the original one? (yes, we must do it, otherwise we continue to work with detached instance, but why)

The original EntityManager.persist() method returns void, so our instance is attached to the persistence context. Does some proxy magic happens while passing account to save to repository? Is it the architecture limitation of Spring Data JPA project?


O
Oliver Drotbohm

The save(…) method of the CrudRepository interface is supposed to abstract simply storing an entity no matter what state it is in. Thus it must not expose the actual store specific implementation, even if (as in the JPA) case the store differentiates between new entities to be stored and existing ones to be updated. That's why the method is actually called save(…) not create(…) or update(…). We return a result from that method to actually allow the store implementation to return a completely different instance as JPA potentially does when merge(…) gets invoked.

Also, persistence implementations actually capable of dealing with immutable objects (i.e. not JPA) might have to return a fresh instance if the actual implementation requires populating an identifier or the like. I.e. it's generally wrong to assume that the implementation would just consume the entity state.

So generally it's more of an API decision to be lenient (permissible, tolerant) regarding the actual implementation and thus implementing the method for JPA as we do. There's no additional proxy massaging done to the entities passed.


Can I presume it's safe not to use the return results for new entities?
Where is some documentation on the state of the returned Object? I.e., sometimes it returns updated fields (timestamps), sometimes the fields are not updated from the DB.
I have a question here, I am saving an entity using save, which has few null columns. But those columns have default values in DB and it is saving fine in DB. But, the returned entity from save has all null values for the default columns. Why this behaviour?
Without the JPA provider knowing about the fact that those values have defaults in the database, it's not going to be able to materialize those. JPA knows about @GeneratedValue but it's specified to be used with primary keys only. I.e. JPA currently simply doesn't support this.
Similar problem for optimistic locking and @Version column. It is updated in database but not updated in entity returned. JPA should definitely know it is updated...
J
JB Nizet

You missed the second part: if the entity isn't new, merge is called. merge copies the state of its argument into the attached entity with the same ID, and returns the attached entity. If the entity isn't new, and you don't use the returned entity, you'll make modifications to a detached entity.


Yes, agree. At least, in this case it is compatible with EntityManager. Will be it better to rename this method to saveOrMerge()?
What about transient members of the Object? In the case of when it merges, I get a brand new actual Object (which honestly I don't care about) but I lose all my original Object's transient values.