1

I have some uncatchable bug in my work.

For example, I have code that looks like this:

@Entity
public class Message {
    @Id
    @GeneratedValue(strategy = SEQUENCE, generator = "message_generator")
    private long id;

    private long massMessageId;
}

public class MessageDTO {
    public final long id;
    public final long massMessageId;
}

@Transactional
@Service
public class ExtendedMessageService {
   private MessageService messageService;

   public MessageDTO createMessage(MessageCreateDTO createDTO) {
       var messageDTO = messageService.create();
       return messageService.linkMassMessage(messageDTO.id, createDTO.massMessageId);
   }
}

@Transactional
@Service
public class MessageService {
    private final MessageRepository repository;
    private final ObjectMapper mapper;

    public MessageDTO create() {
         var message = new Message();
         var savedMessage = repository.save(message);
         return mapper.map(savedMessage, MessageDTO.class);
    }
    
    public MessageDTO linkMassMessage(long messageId, long massMessageId) {
         var message = repository.findById(messageId)
             .orElseThrow(() -> new ObjectNotFoundException("Message with id " + id + " was not found"));
         return mapper.map(repository.save(message.setMassMessageId(massMessageId)), MessageDTO.class);
    }
}

What will happen in this situation? I have some bugs, when repository.findById(id) can't find entity and throws exception. And i have no reason, why this bug is only on prod (i tried to repeat it on dev and nothing succeeded)

And when i try to find the reason of it, i get a question: "Can i save entity and get it in one transaction in Spring?"

TheOnly1
  • 21
  • 3
  • @TheOnly1 Could you please add more real code? – v.ladynev Apr 15 '22 at 11:32
  • 1
    @v.ladynev yea, I was slightly wrong, I didn't saw that ```@Transactional``` on first class. I couldn't edit first comment so I deleted it. Anyway according to this example with default propagation of ```@Transactional``` the transaction should be set only on doSomething method, and persist should occur after leaving this function. ```@Transactional``` on class B and C won't matter because spring will detect that there is an active transaction and won't create new one. – Morph21 Apr 15 '22 at 11:41
  • @TuanHoang, thank you for answer. I understand, that in normal way I need to commit transaction. The question is more about "Can Spring or Hibernate find not flushed entity in transaction context? Or there are some other ways to do it?" – TheOnly1 Apr 16 '22 at 12:54
  • @v.ladynev thank you for correcting me. @ TheOnly1,in that case, I am sorry about that, and I would like to delete the comment for less confusion. – Tuan Hoang Apr 17 '22 at 16:26
  • @TuanHoang Always ready to help :) – v.ladynev Apr 18 '22 at 10:52

1 Answers1

3

How saving works

  1. repository.save() doesn't save anything to database, this method puts entity to the session (persistent context) in memory.

  2. flush step — on this step actual SQL insert happens. It can be invoked manually repository.saveAndFlush(), repository.flush(). Hibernate can do flush in the background, before operations that can use saved to the database value, like JPQL statements. Also flush happens when the end of @Transactional boundary is reached.

What can be an issue

You are using incorrect method. This method from the old version of Spring data and it doesn't perform search in the database. You have to use findById() method instead.

Hibernate: findById vs getbyId

The most simple way, if you want to use id after save — flush the data immediately.

Entity entity = new Entity(some_information);
repository.saveAndFlush(entity);

Entity findedEntity = repository.findById(entity.getId())
  .orElseThrow(() -> new RuntimeException("Can't find id=" + entity.getId()));

Hibernate will not necessary perform SQL select to get findedEntity. It can get it from the session, if it happens in the same @Transactional boundaries. So if the above code resides in the method with @Transaction SQL will not performed. if there is not @Transaction SQL will be performed.

About this question

"Can Spring or Hibernate find not flushed entity in transaction context? Or there are some other ways to do it?"

Hibernate can't find not flushed entity. if id is autogenerated, Hibernate needs to perform SQL INSERT (flush) to get the id from a database. Another option to set up an id manually. Probably in this case it will be possible to get an entity from the persistent context.

v.ladynev
  • 19,275
  • 8
  • 46
  • 67