Реализация стратегии findOrCreate с данными Spring в параллельной среде - PullRequest
0 голосов
/ 16 мая 2018

Я столкнулся со следующей проблемой:

У меня есть объект:

        @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
   //...

} 

Этот объект имеет ограничение уникальности orgName + hostName fields.

И соответствующий репозиторий для сущности:

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);

   //..
}

Я хочу создать сервис с методом findOrCreate, который бы:

  1. Создать новый хостесли Хост не существует
  2. Если Хост существует, вернуть Хост

С учетом ограничения уникальности полей hostName + orgName .

Этот метод должен работать в предположении, что он может выполняться в нескольких разных экземплярах одного и того же приложения, а также в разных потоках.

В настоящее время у меня есть два решения:

  1. Использовать отдельный метод для создания с распространением = Требуется новый

    @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. Выполните всю логику в одном методе, но с уровнем изоляции = 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);
    }
    

    }

Мне кажется, что оба эти варианта будут работать одновременносреда и первый вариант предпочтительнее, потому что работает быстрее.Однако я боюсь, что могу пропустить подводные камни.

Кто-нибудь сталкивался с проблемой раньше?

Если это так, я был бы очень признателен за любые советы или альтернативные решения, помимо перечисленных выше,

Спасибо, ура

1 Ответ

0 голосов
/ 29 января 2019

У меня есть идея.Вы можете определить один метод в HostRepository, например:

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

А в HostService вы можете использовать метод:

Host host = hostRepository.findByOrgNameAndHostName("hoge", "fuga")
               .orElseGet(() -> hostRepository.save(new Host("hoge", "fuga")))
...