2016-07-15 12 views
2

в моем приложении У меня проблема с блокировкой и не знаю, смогу ли я ее обработать. Я использую JPA 2.1 (с eclipselink) на сервере из морской рыбы.Взаимозаполнение JPA с @TransactionAttribute (TransactionAttributeType.REQUIRES_NEW)

Есть два EJB. OuterBean должен написать LogEntry в базу данных и звонить InnerBean в цикле. InnerBean сам должен написать LogEntry и сделать некоторые другие вещи (больше манипуляций с базами данных на других объектах). Вызовы InnerBean#execute() независимы друг от друга, что означает, что если один метод не работает (отката), остальные должны продолжать работать. Поэтому InnerBean#execute() работает в своей собственной транзакции.

При выполнении кода ниже я получаю java.sql.SQLException: Lock wait timeout exeeded; try restarting transaction под MySQL и java.sql.SQLSyntaxErrorException: ORA-02049: timeout: distributed transaction waiting for lock под Oracle (Postgres просто ждет вечность, вероятно, из-за плохой сконфигурированной базы данных).

Я не эксперт по базе данных/JPA, но я думаю, проблема в том, что две транзакции хотят писать в одном и том же формате данных. То, что я не делаю , понимает, что существует проблема вообще, потому что эти манипуляции с базами данных являются вставками, которые должны быть независимыми от других . Есть ли способ, которым я могу реализовать этот вариант использования (нужно ли использовать транзакции, управляемые bean-обработкой, есть ли какие-либо аннотации, которые я могу использовать, могу ли принудительно совершить фиксацию в OuterBean#execute() после em.persist (logEntry), а до цикла так что блокировки транзакций выпущены в любом случае)?

@Stateless 
public class OuterBean 
{ 
    @PersistenceContext(unitName = "PU_LOGGER") 
    private EntityManager em; 
    @EJB 
    private InnerBean innerBean; 

    public void execute() 
    { 
     LogEntry logEntry = new LogEntry(); 
     logEntry.setDate(new Date()); 
     logEntry.setMessage("OuterBean#execute()"); 
     em.persist(logEntry); 

     for(int i = 0; i < 10; ++i) 
     { 
      innerBean.execute(); 
     } 
    } 
} 
@Stateless 
public class InnerBean 
{ 
    @PersistenceContext(unitName = "PU_LOGGER") 
    private EntityManager em; 

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void execute() 
    { 
     LogEntry logEntry = new LogEntry(); 
     logEntry.setDate(new Date()); 
     logEntry.setMessage("InnerBean#execute()" + Math.random()); 
     em.persist(logEntry); 
    } 
} 

EDIT

Вот журнал, который генерируется

FINER: client acquired: 1990452734 
FINER: TX binding to tx mgr, status=STATUS_ACTIVE 
FINER: acquire unit of work: 38847372 
FINEST: persist() operation called on: LogEntry{id=null, message=OuterBean#execute(), date=Mon Jul 18 09:00:27 CEST 2016}. 
FINER: TX beginTransaction, status=STATUS_ACTIVE 
FINEST: Connection acquired from connection pool [default]. 
FINEST: Execute query DataModifyQuery(name="SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + #PREALLOC_SIZE WHERE SEQ_NAME = #SEQ_NAME") 
FINEST: reconnecting to external connection pool 
FINE: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ? 
    bind => [2 parameters bound] 
FINEST: Execute query ValueReadQuery(name="SEQUENCE" sql="SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = #SEQ_NAME") 
FINE: SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ? 
    bind => [1 parameter bound] 
FINEST: local sequencing preallocation for SEQ_GEN: objects: 50 , first: 301, last: 350 
FINEST: assign sequence to the object (301 -> LogEntry{id=null, message=OuterBean#execute(), date=Mon Jul 18 09:00:27 CEST 2016}) 
FINER: client acquired: 187184807 
FINER: TX binding to tx mgr, status=STATUS_ACTIVE 
FINER: acquire unit of work: 2098992041 
FINEST: persist() operation called on: LogEntry{id=null, message=InnerBean#execute()0.3957184758563761, date=Mon Jul 18 09:00:28 CEST 2016}. 
FINER: TX beginTransaction, status=STATUS_ACTIVE 
FINEST: Connection acquired from connection pool [default]. 
FINEST: Execute query DataModifyQuery(name="SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?") 
FINEST: reconnecting to external connection pool 
FINE: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ? 
    bind => [2 parameters bound] 
+0

Какие блокировки вы делаете в приложении или на объекте? Вы проверили, что ваш контейнер фактически запускает новую транзакцию, или аннотацию можно просто игнорировать, а метод завершен в более крупном транзакционном контексте? Включите ведение журнала до лучших и посмотрите, что происходит, поскольку на самом деле этого недостаточно. – Chris

+0

Привет @Chris, я добавил созданный FINEST журнал. Я все не понимаю, но вижу, что две транзакции будут запущены. Это соответствовало бы моему наблюдению, что если я оставлю аннотацию '@TransactionAttribute (REQUIRES_NEW)' inInnerBean', все будет хорошо. Когда дело доходит до блокировки, я не делаю явной блокировки. Вышеупомянутый код будет выполнен через CDI bean, и больше нет кода, где я бы заблокировал что-либо. – Filou

+0

Похоже, вам нужно настроить пул соединений для последовательности, чтобы он мог получать/распределять порядковые номера из вашей таблицы последовательностей вне транзакции JTA. см. http://www.eclipse.org/eclipselink/documentation/2.4/concepts/data_access006.htm#CHDEFJHH и http://stackoverflow.com/questions/19732551/how-to-configure-an-eclipselink-jta-sequence -connection-pool, который показывает, как указать источник данных, не относящийся к JTA – Chris

ответ

1

OK Я решил эту проблему. Ответ на него был спрятан в моем вопросе:

я могу заставить коммит в OuterBean # выполнить() после em.persist (logEntry); и до петли

Я создал отдельный EJB только для сохранения LogEntry, чтобы запустить его в новой транзакции.

@Stateless 
public class OuterBean 
{ 

    @EJB 
    private SeparateLoggingBean separateLoggingBean; 

    @EJB 
    private InnerBean innerBean; 


    public void execute() 
    { 
     LogEntry logEntry = new LogEntry(); 
     logEntry.setDate(new Date()); 
     logEntry.setMessage("OuterBean#execute()"); 
     separateLoggingBean.persistLogEntry(logEntry); 

     for(int i = 0; i < 10; ++i) 
     { 
      innerBean.execute(); 
     } 
     System.out.println("!ready!"); 
    } 
} 
@Stateless 
public class SeparateLoggingBean 
{ 
    @PersistenceContext(unitName = "PU_LOGGER") 
    private EntityManager em; 

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void persistLogEntry(LogEntry logEntry) 
    { 
     em.persist(logEntry); 
    } 
} 

Во всяком случае, я думаю, правильно @ Крис со своим вторым комментарием. Проблема, похоже, является тупиком в генерации последовательности eclipselink. Для этого есть решение (http://www.eclipse.org/eclipselink/documentation/2.6/jpa/extensions/persistenceproperties_ref.htm#BABIDAGH), но я не получил его для правильной работы. В указанной ссылке говорится, что свойство

<property name="eclipselink.connection-pool.sequence" value="true"/> 

нужно положить в persistence.xml. К сожалению, в моих тестах это не имело никакого значения, если я использовал свойство или нет. Я нашел несколько примеров для этой хорошо известной проблемы.Часто некоторые устаревшие свойства используются из старых версий eclipselink, которых я не нашел в текущей (2.6) документации. Мне было непонятно, нужно ли мне указывать отдельный пул соединений для не-jta или если eclipselink управляет пулом самостоятельно, если я укажу свойство eclipselink.connection-pool.sequence.

Ну, было бы неплохо найти дополнительную информацию об этом, но я рад, что мое приложение работает как минимум.

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