2009-12-21 3 views
2

У меня есть сервлет, который выполняет некоторую работу для пользователя, а затем уменьшает кредит пользователя. Когда я просматриваю кредит пользователя в базе данных в режиме реального времени, если есть много параллельных запросов от одного и того же пользователя, кредит вычитается неправильно из-за контроля параллелизма. T Предположим, что для управления сервером и базой данных используется спящий режим. Я использую управление транзакциями для охвата всего запроса, пожалуйста, см. Код для подробностей. У меня есть несколько вопросов:параллелизм в hibernate

  1. Почему кредитный счетчик в db прыгает повсюду, столкнувшись со многими параллельными запросами одного и того же пользователя? почему мой контроль транзакций не работает?

  2. Если базовые данные были изменены после того, как я получил учетную запись пользователя, а затем попытаюсь ее обновить, почему я не получил HibernateException(eg.StaleObjectException)?

  3. У меня есть транзакция по полному запросу пользователя, есть ли лучший способ? Пожалуйста, критикуйте. Не стесняйтесь переписывать структуру кода кода, если чувствуете, что я делаю все это неправильно.

 
Main servlet class: 
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 

      try{ 
       Manager.beginTransaction(); 
       cmdDowork(request, response); 
       Manager.commitTransaction(); 
      }catch(Exception exp){ 
       Manager.rollbackTransaction(); 
       exp.printStackTrace(); 
      } 
      finally{ 
       Manager.closeSession(); 
      } 
} 

public void cmdDowork(){ 
try{ 
    UserAccount userAccount = lazyGetUserAccount(request.getParameter("userName")); 

    doWorkForUser(userAccount);//time and resource consuming process 

    if(userAccount!=null) { 

    decUserAccountQuota(userAccount); 

    } 

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

} 
} 

public static UserAccount lazyGetUserAccount(String userName) { 
     UserAccount userAccount = Manager.getUserAccount(userName); 
     if(userAccount == null){ 
      userAccount = new UserAccount(userName); 
      userAccount.setReserve(DEFAULT_USER_QUOTA); 
      userAccount.setBalance(DEFAULT_USER_QUOTA); 
      Manager.saveUserAccount(userAccount); 
     } 
    return userAccount; 
} 
    private boolean decUserAccountQuota(UserAccount userAccount) { 

     if(userAccount.getBalance() 

Edit: code I used to test optimistic locking as suggested by the answer, I am not getting a any StaleObjectException, the update were committed successfully.. Session em1=Manager.sessionFactory.openSession(); Session em2=Manager.sessionFactory.openSession();

em1.getTransaction().begin(); em2.getTransaction().begin(); UserAccount c1 = (UserAccount)em1.get(UserAccount.class, "jonathan"); UserAccount c2 = (UserAccount)em2.get(UserAccount.class, "jonathan"); c1.setBalance(c1.getBalance() -1); em1.flush(); em1.getTransaction().commit(); System.out.println("balance1 is "+c2.getBalance()); c2.setBalance(c2.getBalance() -1); em2.flush(); // fail em2.getTransaction().commit(); System.out.println("balance2 is "+c2.getBalance());

ответ

14

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

  • С оптимистичной блокировкой, Hibernate будет проверять, что учетная запись пользователя не была изменена между временем ее чтения и сохранения. Одновременная транзакция может завершиться неудачей и откат.

  • С пессимистической блокировкой вы блокируете строку, когда вы ее читаете, и она разблокирована только при завершении транзакции. Это предотвращает одновременную транзакцию для чтения данных, которые будут устаревать.

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

Читайте об этом other question О SO о пессимизме и блокировании оптимистов. Также взгляните на раздел спящего режима «transaction and concurrency» и «hibernate annotations».

Это должно быть столь же просто, как добавление @Version на соответствующем поле, то это optimisticLockStrategydefault valueVERSION (используется отдельный столбец).

- ОБНОВЛЕНИЕ -

Вы можете проверить, работает ли он в тестовом случае. Я создал простой объект Counter с полями ID, value и version.

public class Counter implements Serializable { 

    @Id 
    @GeneratedValue(strategy=GenerationType.AUTO) 
    @Basic(optional = false) 
    @Column(name = "ID") 
    private Integer id; 

    @Column(name = "VALUE") 
    private Integer value; 

    @Column(name = "VERSION") 
    @Version 
    private Integer version; 
    ... 
} 

Если вы обновляете один субъект последовательно это работает:

id = insertEntity(...); 

    em1.getTransaction().begin();    
    Counter c1 = em1.find(Counter.class, id);     
    c1.setValue(c1.getValue() + 1); 
    em1.flush(); 
    em1.getTransaction().commit(); 


    em2.getTransaction().begin(); 
    Counter c2 = em2.find(Counter.class, id); 
    c2.setValue(c2.getValue() + 1); 
    em2.flush(); // OK 
    em2.getTransaction().commit(); 

я получаю один объект с value=2 и version=2.

Если я моделировать два одновременно обновления:

id = insertEntity(...); 

em1.getTransaction().begin(); 
em2.getTransaction().begin(); 

Counter c1 = em1.find(Counter.class, id); 
Counter c2 = em2.find(Counter.class, id); 

c1.setValue(c1.getValue() + 1); 
em1.flush();  
em1.getTransaction().commit(); 

c2.setValue(c2.getValue() + 1); 
em2.flush(); // fail  
em2.getTransaction().commit(); 

то второй поток не может:

Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=? 
Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=? 
Dec 23, 2009 11:08:46 AM org.hibernate.event.def.AbstractFlushingEventListener performExecutions 
SEVERE: Could not synchronize database state with session 
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.ewe.Counter#15] 
     at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765) 

Это объясняется тем, что фактические параметры в отчетности SQL являются:

update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0 
    --> 1 row updated 
    update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0 
    --> 0 row updated, because version has been changed in between 
+0

отличный ответ, чтобы использовать оптимистичную блокировку, поэтому я просто создаю дополнительное поле в таблице и аннотировать его с помощью @version? будет ли спящий режим автоматически генерировать номер версии? – user217631

+0

Это идея, да. «Приложение не должно изменять номер версии, настроенный Hibernate каким-либо образом», из ссылки на hib. аннотаций. – ewernli

+0

Он по-прежнему не работает, версии никогда не превышают 1, даже когда существует много одновременных запросов. и кредит учетной записи пользователя все еще прыгает повсюду. – user217631

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