4

このコードを使用した単純な REST サービスに問題があります。

@GET
@Path("next/{uuid}")
@Produces({"application/xml", "application/json"})
public synchronized Links nextLink(@PathParam("uuid") String uuid) {
    Links link = null;
    try {
        link = super.next();
        if (link != null) {
            link.setStatusCode(5);
            link.setProcessUUID(uuid);
            getEntityManager().flush(); 
            Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id  {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
        }
    } catch (NoResultException ex) {
    } catch (IllegalArgumentException ex) {
    }
    return link;
}

これにより、リンク オブジェクトが提供され、使用済みとしてマークされ (setStatusCode(5))、サービスへの次のアクセスが同じオブジェクトを送信するのを防ぐ必要があります。問題は、Web サービスにアクセスする高速なクライアントが多数ある場合、このクライアントが同じリンク オブジェクトを異なるクライアントに 2 回または 3 回提供することです。どうすればこれを解決できますか??

@NamedQuery(name = "Links.getNext", query = "SELECT l FROM Links l WHERE l.statusCode = 2") を使用した resquest は次のとおりです。

および super.next() メソッド:

public T next() {

    javax.persistence.Query q = getEntityManager().createNamedQuery("Links.getNext");
    q.setMaxResults(1);
    T res = (T) q.getSingleResult();
    return res;
}

どうも

4

4 に答える 4

6

(ルート)JAX-RSリソースのライフサイクルはリクエストごとであるため、メソッドの(それ以外の場合は正しい)synchronizedキーワードnextLinkは残念ながら効果がありません。

必要なのは、アクセス/更新を同期するための手段です。これは多くの方法で行うことができます。

I)次のように、フレームワーク(例:@ApplicationScopedに注入されたCDI)によって注入された外部オブジェクトで同期できます。

@ApplicationScoped
public class SyncLink{
    private ReentrantLock lock = new ReentrantLock();
    public Lock getLock(){
       return lock;
    }
}
....
public class MyResource{
  @Inject SyncLink sync;

  @GET
  @Path("next/{uuid}")
  @Produces({"application/xml", "application/json"})
  public Links nextLink(@PathParam("uuid") String uuid) {
    sync.getLock().lock();
    try{
      Links link = null;
      try {
        link = super.next();
        if (link != null) {
            link.setStatusCode(5);
            link.setProcessUUID(uuid);
            getEntityManager().flush(); 
            Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id  {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
        }
      } catch (NoResultException ex) {
      } catch (IllegalArgumentException ex) {
      }
      return link;
    }finally{
       sync.getLock().unlock();
    }
  }
}

II)あなたは怠惰でクラスで同期することができます

public class MyResource{
  @Inject SyncLink sync;

  @GET
  @Path("next/{uuid}")
  @Produces({"application/xml", "application/json"})
  public Links nextLink(@PathParam("uuid") String uuid) {
     Links link = null;
    synchronized(MyResource.class){
      try {
        link = super.next();
        if (link != null) {
            link.setStatusCode(5);
            link.setProcessUUID(uuid);
            getEntityManager().flush(); 
            Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id  {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
        }
      } catch (NoResultException ex) {
      } catch (IllegalArgumentException ex) {
      }

    }
    return link;
  }
}

III)データベースを使用して同期できます。その場合、JPA2で利用可能な悲観的なロックを調査します。

于 2013-02-05T17:42:43.397 に答える
0

新規作成時に競合が発生すると思われる頻度に応じて、プロパティを使用した楽観的ロックまたは悲観Links的ロックのいずれかを選択する必要があります。@Version

私の推測では、楽観的ロックの方がうまくいくと思います。いずれにせよ、リソース クラスをサービス ファサードとして機能させ、モデル関連のコードをステートレス セッション Bean EJB に配置し、単純な再試行で OptimisticLockExceptions を処理します。

ロック関連の例外をキャッチするのに問題があるとおっしゃっていましたが、Eclipselink を使用しているようにも見えます。その場合、次のようなことを試すことができます。

@Stateless
public class LinksBean {

  @PersistenceContext(unitName = "MY_JTA_PU")
  private EntityManager em;

  @Resource
  private SessionContext sctx;

  public Links createUniqueLink(String uuid) {
    Links myLink = null;
    shouldRetry = false;
    do {
      try
        myLink = sctx.getBusinessObject(LinksBean.class).createUniqueLinkInNewTX(uuid);
      }catch(OptimisticLockException olex) {
        //Retry
        shouldRetry = true;
      }catch(Exception ex) {
       //Something else bad happened so maybe we don't want to retry 
       log.error("Something bad happened", ex);
       shouldRetry = false;   
    } while(shouldRetry);
    return myLink;
  }

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public Links createUniqueLinkInNewTX(uuid) {
      TypedQuery<Links> q = em.createNamedQuery("Links.getNext", Links.class);
      q.setMaxResults(1);
      try {
        myLink = q.getSingleResult();
      }catch(NoResultException) {
        //No more Links that match my criteria
        myLink = null;
      }
      if (myLink != null) {
        myLink.setProcessUUID(uuid);
        //If you change your getNext NamedQuery to add 'AND l.uuid IS NULL' you 
        //could probably obviate the need for changing the status code to 5 but if you 
        //really need the status code in addition to the UUID then:
        myLink.setStatusCode(5);
      }
      //When this method returns the transaction will automatically be committed 
      //by the container and the entitymanager will flush. This is the point that any 
      //optimistic lock exception will be thrown by the container. Additionally you 
      //don't need an explicit merge because myLink is managed as the result of the 
      //getSingleResult() call and as such simply using its setters will be enough for 
      //eclipselink to automatically merge it back when it commits the TX
      return myLink; 
  }  
}

Your JAX-RS/Jersey Resource class should then look like so:

@Path("/links")
@RequestScoped
public class MyResource{
  @EJB LinkBean linkBean;

  @GET
  @Path("/next/{uuid}")
  @Produces({"application/xml", "application/json"})
  public Links nextLink(@PathParam("uuid") String uuid) {
     Links link = null;
     if (uuid != null) {
         link = linkBean.createUniqueLink(uuid);
         Logger.getLogger("Glassfish Rest Service").log(Level.INFO, "Process {0} request url : {1} #id  {2} at {3} #", new Object[]{uuid, link.getLinkTxt(), link.getLinkID(), Calendar.getInstance().getTimeInMillis()});
      }
    return link;
  }
}

これは、この猫の皮を剥ぐ 1 つのアプローチの半ば洗練された例であり、ここでは多くのことが行われています。ご不明な点がございましたら、お知らせください。

また、REST エンドポイントから、このリソースに @GET の代わりに @PUT を使用することを検討することもできます。これは、エンドポイントには、単にリソースをフェッチするのではなく、リソースの状態を更新 (UUID および/または statusCode) するという副作用があるためです。

于 2013-02-08T06:33:21.187 に答える
0

何らかの形式のロック、おそらく楽観的なバージョンのロックを使用する必要があります。これにより、1 つのトランザクションのみが成功し、もう 1 つのトランザクションは失敗します。

http://en.wikibooks.org/wiki/Java_Persistence/Lockingを参照してください 。

于 2013-02-04T14:37:04.210 に答える