Geoffrey Deremetz
Geoffrey Deremetz

Reputation: 75

Spring boot jpa prevent auto-update

I have a question about Jpa and the concurrent access. Here a sample code

User

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String firstName;

    private String lastName;

    ...

UserService

public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public String getFirstName(Long userId) throws InterruptedException {
        User user = userRepository.findById(userId).get();
        Thread.sleep(5000L); // 5s
        // ...
        return user.getFirstname();
    }

    @Transactional
    public void updateUser(Long userId, String firstName, String lastName) {
        User user = userRepository.findById(userId).get();
        user.setFirstname(firstName);
        user.setLastname(lastName);
        userRepository.save(user);
    }
}

if I call both endpoints at the same time ( GET before POST), the getFirstName method will overwrite the update because at the end of the transaction JPA will make an implicit save even if there is no update. My update will be overwritten since at the start of my getFirstName method, I load the user with the old first name

Is there a way to disable the sync if we don't call the save method of the repository ? I don't want to detach the entity every time. Moreover I don't want to put the transaction in read only (Maybe I need to update another entity in the getFirstName method).

I don't want JPA auto save my entites if I haven't made any changes. It can be a problem because of the concurrent access.

Thanks


EDIT (solve)

I used a converter for an attribut User

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String firstName;

    private String lastName;

    @Convert(converter = SimpleConverter.class)
    private customObject object;
    ...

@Component
@Converter
public class SimpleConverter implements AttributeConverter<CustomObject, String> {

    private static ObjectMapper objectMapper;

    @SneakyThrows
    @Override
    public String convertToDatabaseColumn(CustomObject object) {

        if (Objects.isNull(object)) {
            return null;
        }

        return objectMapper.writeValueAsString(object);
    }

    @SneakyThrows
    @Override
    public CustomObject convertToEntityAttribute(String data) {

        if (StringUtils.isBlank(data)) {
            return null;
        }
        return objectMapper.readValue(data, CustomObject.class);
    }

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        TutorialConverter.objectMapper = objectMapper;
    }
}

I cannot explain why but with this converter, my entity is update in the database at the end of each transaction even if I didn't make any changes.

I removed this attribute and now getFirstName method does not overwrite data when both methods are executed at the same time


Edit 2

CustomObject class haven't equals method ... Big mistake. That explain this behavior. Thanks !

Upvotes: 1

Views: 4232

Answers (2)

Ross Newby
Ross Newby

Reputation: 103

As pointed out by M. Deinum, you must be changing something on the 'User' object in 'getFirstName' for an update to happen, otherwise, hibernate would not update the entity in the database.

That being said, a better practice, is to return the entire 'User' object from the service

public User getUser(Long userId) throws InterruptedException {
    return userRepository.findById(userId).orElse(null); // or throw an expection / return optional
}

You would then use the service:

userService.getUser().getFirstname()

This is much more flexible and implicitly prevents problems like this

Upvotes: 1

Adrian Ticusan
Adrian Ticusan

Reputation: 21

By removing @Transactional, the persistanceContext will not be flushed. Or you can have @Transactional(readOnly=true). You can read more here: https://vladmihalcea.com/spring-read-only-transaction-hibernate-optimization/

Upvotes: 2

Related Questions