2016-09-26 7 views
0

У меня есть объект JPA с Lazy загруженной коллекцией на нем. Мне не нужна коллекция каждый раз.OpenSessionInView против транзакции? (Spring/Hibernate/JPA)

@Entity(name = "Foo") 
@Access(AccessType.FIELD) 
@Table(name = "TEST", schema = "TEST") 
public class Foo implements Serializable { 
    private static final long serialVersionUID = 1L; 

    @OneToMany(mappedBy="foo", targetEntity=Bar.class, fetch=FetchType.LAZY, cascade=CascadeType.ALL) 
    private List<Bar> bars; 
} 

@Entity(name = "Bar") 
@Access(AccessType.FIELD) 
@Table(name = "TEST", schema = "TEST") 
public class Bar implements Serializable { 
    private static final long serialVersionUID = 1L; 

    @ManyToOne(targetEntity = Foo.class) 
    @JoinColumn(name = "FOO_ID", referencedColumnName = "ID") 
    private Foo foo; 
} 

У меня есть несколько методов в классе обслуживания, которые выполняют много взаимодействия баз данных и в конце сохранить объект Foo в базу данных. Мне нужно, чтобы это произошло примерно за 100 предметов в коллекции.

@Service 
public class FooService { 

    @Autowired 
    private FooRepository fooRepository; 

    public void processAllFoos() { 
     fooRepository.findAll().forEach(foo -> { 
      processFoo(foo); 
     }); 
    } 

    private void processFoo(Foo foo) { 
     foo.getBars().forEach(bar -> { 
      // Do a lot of time consuming stuff here that involves 
      // entities of other types and modify each bar object 
     }); 
     fooRepository.save(foo); 
    } 
} 

processAllFoos вызывается из @RESTController всякий раз, когда он получает запрос.

Однако я не хочу, чтобы processAllFoos был обернут в транзакцию одной базы данных, поскольку это блокирует всю таблицу Foo до тех пор, пока бизнес-логика не будет выполнена для всех Foos.

Если у меня есть метод processFoo@Transactional Я получаю LazyInitializationException, который жалуется, что сеанс Hibernate не существует. Для выполнения этой работы мне необходимо сделать все методы в стеке вызовов @Transactional, чтобы вложенные методы могли присоединиться к транзакции вызывающего метода. Но это блокирует всю таблицу Foo, как указано выше.

Добавление OpenSessionInViewFilter для dispatcher servlet решает мою проблему, но я прочитал, что с этим подходом возникают проблемы с производительностью и отсоединением/повторной привязкой объектов (что я делаю в других частях приложения).

Есть ли способ, которым я могу сделать то, что хочу, не используя подход OpenSessionInView? Какие еще уязвимости я могу добавить с помощью этого подхода?

Spring/Hibernate 4.x


На основе ниже ответа, я был в состоянии сделать следующее:

@Service 
public class FooService { 

    @Autowired 
    private FooRepository fooRepository; 

    @Autowired 
    private TransactionTemplate transactionTemplate; 

    public void processAllFoos() { 
     fooRepository.findAll().forEach(foo -> { 
      transactionTemplate.execute(new TransactionCallback<Object>() { 
       public Object doInTransaction(TransactionStatus status) { 
        try { 
         processFoo(foo); 
         status.flush(); 
        } catch(Exception e) { 
         status.setRollbackOnly(); 
        } 
        return null; 
       } 
      }); 
     }); 
    } 

    private void processBar(Foo foo) { 
     foo.getBars().foreEach(bar -> { 
      // Do a lot of time consuming stuff here that involves 
      // entities of other types and modify each bar object 
     }); 
     fooRepository.save(foo); 
    } 
} 

ответ

2

OpenSessionInViewFilter обычно используется для решения проблемы отложенной инициализации в View слое (компоненты пользовательского интерфейса или шаблоны страниц) , поскольку слой «Вид» не может и не должен управлять транзакциями напрямую. В вашем случае может быть применен другой способ получить все объекты Bar.

Первый Вы получаете все идентификаторы объектов Foo, чтобы получить полностью объекты.

ВторойFoo ids коллекция для итерации через связанные Bar объектов.

Third Если вы не хотите использовать одну большую транзакцию, вы можете использовать шаблон Spring Transaction для управления транзакциями явно.

Ваш пример кода может выглядеть следующим образом:

@Service 
public class FooService { 

    @Autowired 
    private FooRepository fooRepository; 

    @Autowired 
    private BarRepository barRepository; 

    @Autowired 
    private TransactionTemplate transactionTemplate; 

    public void processAllFoos() { 
     final List <Long> fooIdList = transactionTemplate.execute(new TransactionCallback() { 
      public Object doInTransaction(TransactionStatus status) { 

       return fooRepository.findIdList(); 
      } 
     }); 

     transactionTemplate.execute(new TransactionCallback() { 
      public Object doInTransaction(TransactionStatus status) { 
       barRepository.findByFooIdList(fooIdList).forEach(bar - > { 
        processBar(bar); 
       }); 
       return null; 
      } 
     }); 

    } 

    private void processBar(Bar bar) { 
     // Do a lot of time consuming stuff here that involves 
     // entities of other types and modify each bar object 
     barRepository.save(bar); 
    } 
} 

Пример ниже показывает, как решить вашу задачу без каких-либо накладных расходов производительности.Но вы должны понимать, что если таблицы Foo и Bar связаны с ограничением внешнего ключа, то связанная запись в таблице Foo может быть заблокирована RDBMS каждый раз, когда вы обновляете строку в таблице Bar.

+0

Спасибо @SergeyBespalov Это сделало весь процесс невероятно быстрым. Есть ли какие-либо оговорки к этому подходу? Вещи, которые вы не должны делать при явном управлении транзакциями? – battle2048

+0

Я могу предложить вам два простых правила, основанные на этом примере: 1. Не используйте декларативное управление транзакциями, если оно вам не подходит; 2. Не используйте полностью объекты, если вам нужно только одно свойство; –

Смежные вопросы