JPA одновременных транзакций - PullRequest
4 голосов
/ 28 февраля 2011

У меня проблема с параллельными транзакциями, использующими JPA-1.0, Hibernate и MySQL 5.0.84 (таблицы innoDB), а также Postgres 8.1.11 (другая база данных для другого клиента). Я не знаю, пропускаю ли я что-то в отношении конфигурации, так как я прочитал спецификации по транзакциям JPA и в соответствии с моей проблемой, Мне нужно указать конкретный уровень изоляции для аннотации транзакции. Это я и сделал, но он просто отключает транзакцию, поэтому ничего не сохраняется / обновляется.

Я запускаю посты http на веб-сервере (в моем случае tomcat), который затем пытается порождать несколько транзакций БД при поступлении http-запросов. Каждая транзакция состоит из 1 вставки и 2 обновлений. Похоже, что проблема всегда возникает в последнем обновлении, основанном на предыдущей вставке. Итак, я вставляю запись A, а затем обновляю запись B, которая является внешним ключом для записи A.

Ниже приводится журнал, который я получаю при выполнении одного http-запроса:


org.springframework.orm.jpa.JpaTransactionManager:365 - Creating new transaction with name [biz.cytrus.overlord.v2.core.ExecutionLogAPI.create]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
org.springframework.orm.jpa.JpaTransactionManager:323 - Opened new EntityManager [org.hibernate.ejb.EntityManagerImpl@221f81] for JPA transaction
org.springframework.orm.jpa.JpaTransactionManager:355 - Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@9f5742]
org.springframework.orm.jpa.JpaTransactionManager:752 - Initiating transaction commit
org.springframework.orm.jpa.JpaTransactionManager:462 - Committing JPA transaction on EntityManager [org.hibernate.ejb.EntityManagerImpl@221f81]
org.springframework.orm.jpa.JpaTransactionManager:548 - Closing JPA EntityManager [org.hibernate.ejb.EntityManagerImpl@221f81] after transaction
org.springframework.orm.jpa.EntityManagerFactoryUtils:329 - Closing JPA EntityManager

Ниже приводится логирование, которое я получаю при одновременном выполнении нескольких http-запросов:


org.hibernate.util.JDBCExceptionReporter:357 - SQL Error: 1213, SQLState: 40001
org.hibernate.util.JDBCExceptionReporter:454 - Deadlock found when trying to get lock; try restarting transaction
org.hibernate.event.def.AbstractFlushingEventListener:532 - Could not synchronize database state with session
org.hibernate.exception.LockAcquisitionException: Could not execute JDBC batch update
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:105)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
        at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
        at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
        at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
        at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
        at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
        at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
        at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:467)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:375)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
        at $Proxy66.create(Unknown Source)
        at biz.cytrus.overlord.v2.web.action.task.LogAction.createLogEntry(LogAction.java:84)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:592)
        at net.sourceforge.stripes.controller.DispatcherHelper$6.intercept(DispatcherHelper.java:442)
        at net.sourceforge.stripes.controller.ExecutionContext.proceed(ExecutionContext.java:158)
        at net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor.intercept(BeforeAfterMethodInterceptor.java:113)
        at net.sourceforge.stripes.controller.ExecutionContext.proceed(ExecutionContext.java:155)
        at net.sourceforge.stripes.controller.ExecutionContext.wrap(ExecutionContext.java:74)
        at net.sourceforge.stripes.controller.DispatcherHelper.invokeEventHandler(DispatcherHelper.java:440)
        at net.sourceforge.stripes.controller.DispatcherServlet.invokeEventHandler(DispatcherServlet.java:278)
        at net.sourceforge.stripes.controller.DispatcherServlet.service(DispatcherServlet.java:160)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at net.sourceforge.stripes.controller.StripesFilter.doFilter(StripesFilter.java:247)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:390)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
        at java.lang.Thread.run(Thread.java:595)
Caused by: java.sql.BatchUpdateException: Deadlock found when trying to get lock; try restarting transaction
        at com.mysql.jdbc.ServerPreparedStatement.executeBatch(ServerPreparedStatement.java:657)
        at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)
        at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
        at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
        ... 48 more
org.springframework.orm.jpa.JpaTransactionManager:893 - Initiating transaction rollback after commit exception
org.springframework.dao.CannotAcquireLockException: Could not execute JDBC batch update; SQL [update application_instances set application_id=?, create_date=?, for_ongoing_task=?, last_log_id=?, last_notified_date=?, name=?, param_string=?, application_status_id=?, status_date=? where id=?]; nested exception is org.hibernate.exception.LockAcquisitionException: Could not execute JDBC batch update
        at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:633)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:97)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:471)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:375)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
        at $Proxy66.create(Unknown Source)
        at biz.cytrus.overlord.v2.web.action.task.LogAction.createLogEntry(LogAction.java:84)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:592)
        at net.sourceforge.stripes.controller.DispatcherHelper$6.intercept(DispatcherHelper.java:442)
        at net.sourceforge.stripes.controller.ExecutionContext.proceed(ExecutionContext.java:158)
        at net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor.intercept(BeforeAfterMethodInterceptor.java:113)
        at net.sourceforge.stripes.controller.ExecutionContext.proceed(ExecutionContext.java:155)
        at net.sourceforge.stripes.controller.ExecutionContext.wrap(ExecutionContext.java:74)
        at net.sourceforge.stripes.controller.DispatcherHelper.invokeEventHandler(DispatcherHelper.java:440)
        at net.sourceforge.stripes.controller.DispatcherServlet.invokeEventHandler(DispatcherServlet.java:278)
        at net.sourceforge.stripes.controller.DispatcherServlet.service(DispatcherServlet.java:160)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at net.sourceforge.stripes.controller.StripesFilter.doFilter(StripesFilter.java:247)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:390)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
        at java.lang.Thread.run(Thread.java:595)
Caused by: org.hibernate.exception.LockAcquisitionException: Could not execute JDBC batch update
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:105)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
        at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
        at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
        at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
        at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
        at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
        at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
        at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:467)
        ... 39 more
Caused by: java.sql.BatchUpdateException: Deadlock found when trying to get lock; try restarting transaction
        at com.mysql.jdbc.ServerPreparedStatement.executeBatch(ServerPreparedStatement.java:657)
        at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)
        at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
        at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
        ... 48 more
org.springframework.orm.jpa.JpaTransactionManager:488 - Rolling back JPA transaction on EntityManager [org.hibernate.ejb.EntityManagerImpl@7ca9bd]
org.springframework.orm.jpa.JpaTransactionManager:548 - Closing JPA EntityManager [org.hibernate.ejb.EntityManagerImpl@7ca9bd] after transaction
org.springframework.orm.jpa.EntityManagerFactoryUtils:329 - Closing JPA EntityManager

Пытаясь решить эту проблему, я попытался установить уровень изоляции для аннотации транзакции, но это не приводит к активности в базе данных:


org.springframework.orm.jpa.JpaTransactionManager:365 - Creating new transaction with name [biz.cytrus.overlord.v2.core.ExecutionLogAPI.create]: PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ; ''
org.springframework.orm.jpa.JpaTransactionManager:323 - Opened new EntityManager [org.hibernate.ejb.EntityManagerImpl@adddd6] for JPA transaction
org.springframework.orm.jpa.EntityManagerFactoryUtils:329 - Closing JPA EntityManager

Буду очень признателен за помощь в решении этой проблемы.

Вот фрагмент кода в методе, помеченном аннотацией @Transactional:


Task task = ongoingTaskAPI.findById(taskId);
ExecutionLog executionLog = new ExecutionLog();
executionLog.setStatusDate(new Date());
executionLog.setStatus(status);

        executionLog.setApplicationInstance(task.getApplicationInstance());

executionLog.getApplicationInstance().setLastLog(executionLog);
em.persist(executionLog);

Соответствующие части сущностных компонентов следующие:


public class ExecutionLog implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @ManyToOne
    @JoinColumn(name="application_instance_id")
    private ApplicationInstance applicationInstance;

public class ApplicationInstance implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @OneToOne
    @JoinColumn(name="last_log_id", nullable = true)
    private ExecutionLog lastLog;

Существует взаимно-однозначное отношение, поскольку в любой момент конкретная запись журнала может быть связана только с конкретной записью экземпляра приложения.

Итак, по сути, я создаю записи журнала, а затем обновляю экземпляр приложения, чтобы он связывался с самой последней соответствующей записью журнала. Это делается одним транзакционным методом, но сбой, по-видимому, возникает при обновлении записи экземпляра приложения. Тем не менее, это прекрасно работает для одного вызова, но не для одновременных вызовов.

Надеюсь, это добавит ясности в мой вопрос.

1 Ответ

4 голосов
/ 28 февраля 2011

Ваша общая строка «экземпляр приложения», указывающая на последнюю запись в журнале, просит о блокировке.Мне кажется, что последовательность будет выглядеть следующим образом:

  • выбрать из application_instance
  • вставить в файл_процесса, ссылаясь на application_instance (принимает общую блокировку на строку application_instance)
  • update application_instance(принимает эксклюзивную блокировку для строки application_instance)

Таким образом, два потока могут иметь общую блокировку для application_instance;Затем поток A блокирует поток B, пытаясь выполнить обновление, а затем поток B блокирует поток A ...

. Я бы решил эту проблему, немедленно получив экземпляр приложения с эксклюзивной блокировкой.В прямых терминах Hibernate это можно сделать, указав LockMode.UPGRADE (или LockMode.PESSIMISTIC_WRITE) при загрузке сущности (session.get или в запросе).

(Я бы также повторно проверил, действительно ли вам нужнообновлять application_instance так, чтобы он указывал на последнюю запись журнала каждый раз - это узкое место, поскольку все транзакции должны синхронизироваться / сериализироваться на нем.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...