2013-11-19 2 views
2

Я пытаюсь добавить/получить/обновить код, и я использовал провайдера EclipseLink и JPA2.0. и MySQL.JPA 2.0 Как обрабатывать тупик (Eclipselink JPA2.0 MySQL)

Приведенный ниже код вызывает ошибку при возникновении тупика. Проблема происходит случайным образом. Я хотел знать, как обращаться с тупиком.

Вот сообщение об ошибке:

javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException 
    Internal Exception: java.sql.SQLException: null, message from server: "Deadlock found when trying to get lock; try restarting transaction" 
    Error Code: 1213 
    Call: UPDATE activitylog SET timestampdate = ? WHERE (logid = ?) 
     bind => [2013-11-19 20:10:38.583, 1] 
    Query: UpdateObjectQuery([email protected]) 

Вот код, который я пытаюсь:

public class TestMain { 
     public static void main(String[] args) { 
      for(int j = 0; j < 10; j ++) { 
       Thread thread = new Thread(new Runnable() { 

        @Override 
        public void run() { 
         for (int i = 0; i < 200; i++) { 
          ActivityLogDAO activityLogDAO = new ActivityLogDAO(); 
          try { 
           ActivityLog theActivityLog = new ActivityLog(); 
           theActivityLog.setTimestampdate(new Date()); 
           theActivityLog.setMyId(i); 
           activityLogDAO.insert(theActivityLog); 

           ActivityLog activityLog = activityLogDAO.getActivityLog(theActivityLog); 

           activityLog.setTimestampdate(new Date()); 
           activityLogDAO.update(activityLog); 

          } catch (Exception e) { 
           e.printStackTrace(); 
          } 

         } 
        } 
       }); 
       thread.start(); 
      } 
     } 
    } 

Вот класс Entity

@Entity 
    @Table(name="activitylog") 
    public class ActivityLog implements Serializable { 

     private static final long serialVersionUID = 1L; 

     @Id 
     @GeneratedValue(strategy=GenerationType.SEQUENCE) 
     @Column(name="logid") 
     private long logid; 

     @Column(name="myid") 
     private long lMyId; 

     @Temporal(TemporalType.TIMESTAMP) 
     @Column(name="timestampdate", nullable=true) 
     private Date timestampdate; 


     public long getMyId() { 
      return lMyId; 
     } 

     public void setMyId(long lMyId) { 
      this.lMyId = lMyId; 
     } 

     public long getLogid() { 
      return logid; 
     } 

     public void setLogid(long logid) { 
      this.logid = logid; 
     } 

     public Date getTimestampdate() { 
      return timestampdate; 
     } 

     public void setTimestampdate(Date timestampdate) { 
      this.timestampdate = timestampdate; 
     } 

    } 

здесь мой класс DAO :

public class ActivityLogDAO { 
     private EntityManagerFactory _entityManagerFactory = null; 
     private EntityManager _entityManager = null; 

     public ActivityLogDAO() { 
      _entityManagerFactory = Persistence.createEntityManagerFactory("MyTestOnLock"); 
      _entityManager = _entityManagerFactory.createEntityManager(); 
     } 

     protected EntityManager getEntityManager() { 
      return _entityManager; 
     } 

     protected void setEntityManager(EntityManager _entityManager) { 
      this._entityManager = _entityManager; 
     } 

     public ActivityLog insert(ActivityLog theActivityLog) throws Exception { 
      if(null == theActivityLog) { 
       throw new Exception("Invalid ActivityLog Object"); 
      } 

      if(false == getEntityManager().getTransaction().isActive()) { 
       getEntityManager().getTransaction().begin(); 
      } 

      System.out.println("inserting"); 
      getEntityManager().persist(theActivityLog); 
      getEntityManager().getTransaction().commit(); 
      System.out.println("inserted"); 

      return theActivityLog; 
     } 

     public ActivityLog getActivityLog(ActivityLog theActivityLog) throws Exception { 
      if(null == theActivityLog) { 
       throw new Exception("Invalid ActivityLog Object"); 
      } 

      if(false == getEntityManager().getTransaction().isActive()) { 
       getEntityManager().getTransaction().begin(); 
      } 

      System.out.println("trying to get object"); 
      Query query = getEntityManager().createQuery("SELECT m FROM ActivityLog m WHERE m.lMyId = :lMyId"); 
      query.setParameter("lMyId", theActivityLog.getMyId()); 
      //deadlock happens here. 
      @SuppressWarnings("unchecked") 
      List<ActivityLog> resultList = query.getResultList(); 
      System.out.println(resultList.size()); 
      System.out.println("got object"); 
      if(null == resultList || 0 == resultList.size()) { 
       return null; 
      } else { 
       return resultList.get(0); 
      } 
     } 

     public ActivityLog update(ActivityLog theActivityLog) throws Exception { 
      if(null == theActivityLog) { 
       throw new Exception("Invalid ActivityLog Object"); 
      } 

      if(false == getEntityManager().getTransaction().isActive()) { 
       getEntityManager().getTransaction().begin(); 
      } 
      System.out.println("trying to update object"); 
      Query query = getEntityManager().createQuery("UPDATE ActivityLog m SET m.timestampdate = :timestampdate WHERE m.lMyId = :lMyId"); 
      query.setParameter("lMyId", theActivityLog.getMyId()); 
      query.setParameter("timestampdate", theActivityLog.getTimestampdate()); 

      int executeUpdate = query.executeUpdate(); 
      getEntityManager().getTransaction().commit(); 
      System.out.println("object updted."); 

      if(0 == executeUpdate) { 
       return null; 
      } 

      return theActivityLog; 
     } 
    } 

Вот мой persistance.xml

<?xml version="1.0" encoding="UTF-8"?> 
    <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> 
     <persistence-unit name="MyTestOnLock"> 
     <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> 

     <class>test.ActivityLog</class> 


     <properties> 
    <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"></property> 
    <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/locktest"></property> 
    <property name="javax.persistence.jdbc.user" value="root"></property> 
    <property name="javax.persistence.jdbc.password" value="root"></property> 

    <!-- EclipseLink should create the database schema automatically --> 
    <property name="eclipselink.ddl-generation" value="create-tables" /> 
    <property name="eclipselink.ddl-generation.output-mode" value="database" /> 
    <property name="eclipselink.id-validation" value="NULL"></property> 
    <property name="eclipselink.logging.level" value="FINE"/> 
    <property name="javax.persistence.lock.timeout" value="100"/> 
    <property name="eclipselink.order-updates" value="true"/> 
    <property name="eclipselink.connection-pool.sequence" value="max" /> 
    <property name="eclipselink.ddl-generation.output-mode" value="database" /> 
    <property name="eclipselink.target-database" value="MySQL" /> 

    </properties> 

    </persistence-unit> 

    </persistence> 

Тупик возникает, когда AcitivityDAO пытается обновить. Есть ли причина для устранения или устранения проблемы взаимоблокировки?

Любая помощь приветствуется!


Я получаю обратно следующее сообщение об ошибке:

 javax.persistence.PersistenceException: java.lang.NullPointerException 

и

 javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException 
     Internal Exception: java.sql.SQLException: Deadlock found when trying to get lock; Try restarting transaction, message from server: "Lock wait timeout exceeded; try restarting transaction" 
     Error Code: 1205 
     Call: UPDATE activitylog SET timestampdate = ? WHERE (myid = ?) 
    bind => [2013-11-20 16:54:09.646, 0] 
     Query: UpdateAllQuery(referenceClass=ActivityLog sql="UPDATE activitylog SET timestampdate = ? WHERE (myid = ?)") 

я использовал один и тот же код, указанный Ridal @ Крис.

вот код: В основном я пытался несколько раз запустить класс MainTest.

public class MainTest { 
     public static void main(String[] args) { 
      updateActivityLog(); 
     } 

     private static void updateActivityLog() { 
      final PersistenceController persistenceController = new PersistenceController(Persistence.createEntityManagerFactory("MyTestOnLock")); 
      for (int i = 0; i < 100; i++) { 
        try { 
         for(int j = 0; j < 200; j++) { 
          ActivityLog theActivityLog = new ActivityLog(); 
          theActivityLog.setMyId(j); 
          theActivityLog.setTimestampdate(new Date()); 
          persistenceController.update(theActivityLog); 
         } 

        } catch (Exception e) { 
         e.printStackTrace(); 
        } 
      } 
      persistenceController.commitAndClose(); 
     } 
    } 


    public class PersistenceController { 
     private EntityManager manager; 

     public PersistenceController(EntityManagerFactory factory) 
     { 
      /* 
      * Normally you want to split your work up into separate transactions 
      * (ie new entity managers), in a logical way which will depend on how 
      * your application works. This class will do that for you if you keep 
      * your factory. Note that factory's are expensive to create but entity 
      * managers are cheap to create. 
      */ 
      manager = factory.createEntityManager(); 
      manager.getTransaction().begin(); 
     } 

     // Call ONCE on an object after creating it, it will stay in sync with the database even when you change it remotely 
     public void persist(Serializable entityObj) 
     { 
      manager.persist(entityObj); 
      manager.flush(); 
     } 

     // Call to sync with database (even though you might not actually see the objects in the database until you commit) 
     public void flush() 
     { 
      manager.flush(); 
     } 

     /* 
     * Call when you are done with your unit of work to commit the DB changes 
     */ 
     public void commitAndClose() 
     { 
      manager.getTransaction().commit(); 
      manager.close(); 
     } 

     public ActivityLog getActivityLog(ActivityLog theActivityLog) throws Exception { 
      if(null == theActivityLog) { 
       throw new Exception("Invalid ActivityLog Object"); 
      } 
      if(false == manager.getTransaction().isActive()) { 
       manager.getTransaction().begin(); 
      } 

      System.out.println("trying to get object"); 
      Query query = manager.createQuery("SELECT m FROM ActivityLog m WHERE m.lMyId = :lMyId"); 
      query.setParameter("lMyId", theActivityLog.getMyId()); 

      @SuppressWarnings("unchecked") 
      List<ActivityLog> resultList = query.getResultList(); 
      System.out.println(resultList.size()); 
      System.out.println("got object"); 
      if(null == resultList || 0 == resultList.size()) { 
       return null; 
      } else { 
       return resultList.get(0); 
      } 
     } 

     public ActivityLog update(ActivityLog theActivityLog) throws Exception { 
      if(null == theActivityLog) { 
       throw new Exception("Invalid ActivityLog Object"); 
      } 
      if(false == manager.getTransaction().isActive()) { 
       manager.getTransaction().begin(); 
      } 
      ActivityLog activityLog = getActivityLog(theActivityLog); 
      activityLog.setTimestampdate(theActivityLog.getTimestampdate()); 
      persist(activityLog); 
      return theActivityLog; 
     } 

    } 

Должен ли я получить EntityManager для каждой вставки базы данных или объединять или обновлять или удалять? см. ниже код, с этим я не вижу тупика. Пожалуйста подтвердите.

public class ActivityLogDAO { 
    private EntityManagerFactory _entityManagerFactory = null; 
    private EntityManager _entityManager = null; 

    public ActivityLogDAO() { 
     _entityManagerFactory = Persistence.createEntityManagerFactory("MyTestOnLock"); 
    } 

    protected EntityManager getEntityManager() { 
     return _entityManager; 
    } 

    protected void setEntityManager(EntityManager _entityManager) { 
     this._entityManager = _entityManager; 
    } 

    public ActivityLog insert(ActivityLog theActivityLog) throws Exception { 
     if(null == theActivityLog) { 
      throw new Exception("Invalid ActivityLog Object"); 
     } 

     _entityManager = _entityManagerFactory.createEntityManager(); 

     if(false == getEntityManager().getTransaction().isActive()) { 
      getEntityManager().getTransaction().begin(); 
     } 

     System.out.println("inserting"); 
     getEntityManager().persist(theActivityLog); 
     getEntityManager().getTransaction().commit(); 
     System.out.println("inserted"); 

     return theActivityLog; 
    } 

    public ActivityLog getActivityLog(ActivityLog theActivityLog) throws Exception { 
     if(null == theActivityLog) { 
      throw new Exception("Invalid ActivityLog Object"); 
     } 
     _entityManager = _entityManagerFactory.createEntityManager(); 

     if(false == getEntityManager().getTransaction().isActive()) { 
      getEntityManager().getTransaction().begin(); 
     } 

     System.out.println("trying to get object"); 
     Query query = getEntityManager().createQuery("SELECT m FROM ActivityLog m WHERE m.lMyId = :lMyId"); 
     query.setParameter("lMyId", theActivityLog.getMyId()); 
     //deadlock happens here. 
     @SuppressWarnings("unchecked") 
     List<ActivityLog> resultList = query.getResultList(); 
     System.out.println(resultList.size()); 
     System.out.println("got object"); 
     if(null == resultList || 0 == resultList.size()) { 
      return null; 
     } else { 
      return resultList.get(0); 
     } 
    } 

    public ActivityLog update(ActivityLog theActivityLog) throws Exception { 
     if(null == theActivityLog) { 
      throw new Exception("Invalid ActivityLog Object"); 
     } 
     _entityManager = _entityManagerFactory.createEntityManager(); 

     if(false == getEntityManager().getTransaction().isActive()) { 
      getEntityManager().getTransaction().begin(); 
     } 
     System.out.println("trying to update object"); 
     Query query = getEntityManager().createQuery("UPDATE ActivityLog m SET m.timestampdate = :timestampdate WHERE m.lMyId = :lMyId"); 
     query.setParameter("lMyId", theActivityLog.getMyId()); 
     query.setParameter("timestampdate", theActivityLog.getTimestampdate()); 

     int executeUpdate = query.executeUpdate(); 
     getEntityManager().getTransaction().commit(); 
     System.out.println("object updted."); 

     if(0 == executeUpdate) { 
      return null; 
     } 

     return theActivityLog; 
    } 
} 
+0

Если вы этого не сделали, возможно, посмотрите на транзакции с использованием jpa и как транзакции помогут избежать взаимоблокировок. Об этом есть хорошая информация. Удостоверьтесь, что вы сосредоточены на том, когда транзакция освобождает блокировку. – user2932397

+0

это не представляется возможным из показанного кода, так как каждый поток будет касаться только строки, создаваемой в каждой итерации. В прогоне, который воспроизводит проблему, проверьте, что еще выдаёт утверждения объекту с помощью logid = 1 (или что когда-либо значение отображается в ошибке). – Chris

ответ

4

В общем you do not need to use DAO's when using JPA.

Вместо этого вы можете посмотреть использовать класс, как это (непроверенные), в результате чего собственный EntityManagerFactory:

public class PersistenceController 
{ 
    private EntityManager manager; 

    public PersistenceController(EntityManagerFactory factory) 
    { 
     /* 
     * Normally you want to split your work up into separate transactions 
     * (ie new entity managers), in a logical way which will depend on how 
     * your application works. This class will do that for you if you keep 
     * your factory. Note that factory's are expensive to create but entity 
     * managers are cheap to create. 
     */ 
     manager = factory.createEntityManager(); 
     manager.getTransaction().begin(); 
    } 

    // Call ONCE on an object after creating it, it will stay in sync with the database even when you change it remotely 
    public void persist(Serializable entityObj) 
    { 
     manager.persist(entityObj); 
    } 

    // Call to sync with database (even though you might not actually see the objects in the database until you commit) 
    public void flush() 
    { 
     manager.flush(); 
    } 

    /* 
    * Call when you are done with your unit of work to commit the DB changes 
    */ 
    public void commitAndClose() 
    { 
     manager.getTransaction().commit(); 
     manager.close(); 
    } 

} 

Чтобы использовать эту функцию, вы назвали бы persist(entityObj) когда вы создали объект, flush() для синхронизации с (если вам нужно) и commitAndClose(), когда все будет готово. Держите PersistenceController в месте, которое вы можете отправить ему, когда вам нужно сохранить объект или использовать его другие операции.

Теперь ваши транзакции не будут происходить одновременно, и вы не получите взаимоблокировок.

Примечание: в производственном коде вы должны использовать больше управления исключениями и разделить свою работу на различные транзакции EntityManager, которые этот класс делает для вас, если вы уничтожаете и создаете этот класс PersistenceController логически.

+0

После обновления строк в приведенном выше примере я использовал flush(), даже тогда произошел тупик. – User12111111

+0

Проблема - это где-то с вашей реализацией DAO. Вы управляете транзакциями вручную. Вы перестали использовать DAO, как сказал Крис? Если вы столкнулись с тупиком, это, вероятно, означает, что вы пытаетесь начать новую транзакцию из существующей транзакции. Вы должны начать транзакцию на внешней границе работы, которую хотите достичь. Никогда не начинайте одну транзакцию внутри другой. Или еще лучше: используйте контейнер, который будет управлять транзакциями для вас. Дайте Spring Data попробовать. – mwhs

+0

@mwhs, я просто назвал его DAO, но код похож на то, что сказал Крис. Однако код может быть изменен. А также я должен получить объект ActivityLog на основе MyId не на основе LogId для обновления, потому что в качестве пользователя я не буду знать LogId, сгенерированный JPA, чтобы найти() объект. Пример, который дал Крис, не имеет merge() или update(). Как обновить строку на основе столбца, а не на основе генерируемого Id и который не может вызвать тупик. – User12111111

1

For what it's worth:

Deadlocks are a classic problem in transactional databases, but they are not dangerous unless they are so frequent that you cannot run certain transactions at all. Normally, you must write your applications so that they are always prepared to re-issue a transaction if it gets rolled back because of a deadlock.

Иногда это просто невозможно, чтобы избежать тупиковой ситуации. Однако, чтобы сделать тупик менее вероятным или реже, не исследуйте слишком глубоко в коде доступа к данным. Этот вопрос скорее связан с порядком операций, которые приводят к тупиковой ситуации. Формальный способ избежать взаимоблокировок - всегда блокировать и освобождать ресурсы в том же порядке. Легче сказать, чем сделать :)

Интересный ресурс: What is a deadlock?

Вы можете отслеживать, что параллельная транзакция (ы) является (являются), участвующих в тупике с SHOW ENGINE INNODB STATUS (активные операции перечислены в «СДЕЛОК» раздел, с расширенные детали).

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