Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing findOrCreate strategy with spring data in concurrent environment

I'm facing the following problem:

I have an entity:

        @Table(name = "host",
                uniqueConstraints = 
    { 
@UniqueConstraint(name = "uq_host_0", 
        columnNames = {"orgName", "hostName"})}
)
class Host {

   private String id;

   private String hostName;
   private String orgName;

   //gets
   //sets
   //constructors
   //...

} 

This entity has uniqueness constrain on orgName + hostName fields.

And corresponding Repository for the entity:

public interface HostRepository extends JpaRepository<Host, String> {

    Page<Host> findByOrgId(String orgId, Pageable pageable);

    Host findOneByOrgNameIdAndId(String orgName, String id);

    Host findOneByOrgNameAndHostName(String orgName, String hostName);

   //..
}

I want to create a service with findOrCreate method that would:

  1. Create new Host in case if Host does not exist
  2. If Host does exist, return the Host

Taking into account uniqueness constraint on hostName + orgName fields.

This method should work in assumption that it can be executed on multiple different instances of the same application as well as in different threads.

Currently I came up with two solutions:

  1. Use separate method for creation with Propagation = RequiresNew

    @Service
     public class HostService {
    
       @Autowired
       private HostRepository hostRepository;
    
        @Transactional
        public Host findOrCreate(Host host) {
    
           try {
    
            return create(host);
    
          } catch(ConstraintViolationException e) {
            //means the host has already been created by other transaction
             return hostRepository.findFirstByOrgNameAndHostName(host.getHostName(), host.getOrgName());
          }
    
        }
    
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     public Host create(Host host) {
         //constraint violation may be thrown
          hostRepository.save(host);
       }
    }
    
  2. Perform all the logic in one method, but with isolation level = serializable:

    @Service
    public class HostService {
    
     @Autowired
     private HostRepository hostRepository;
    
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public Host findOrCreate(Host host) {
    
       Optional<Host> existing = Optional.ofNullable(hostRepository.findOneByOrgNameAndHostName(host.getOrgName(), host.getHostName()));
    
       if(existing.isPresent()) {
          return existing;
       }
    
       return hostRepository.save(host);
    }
    

    }

It seems to me that both of these options would work in concurrent environment and the first option is preferable because works faster. However, I'm afraid that I may miss underwater rocks.

Did anyone face the problem before?

If so, I would really grateful for any advice or alternative solutions apart from listed above,

Thanks, cheers

like image 560
Andrey Yaskulsky Avatar asked Jul 01 '26 01:07

Andrey Yaskulsky


1 Answers

I have an idea. You can define one method in HostRepository like:

Optional<Host> findByOrgNameAndHostName(String orgName, String hostName)

And in HostService, you can use the method:

Host host = hostRepository.findByOrgNameAndHostName("hoge", "fuga")
               .orElseGet(() -> hostRepository.save(new Host("hoge", "fuga")))
like image 72
tainakag Avatar answered Jul 03 '26 19:07

tainakag