簡體   English   中英

對REST Web服務的同步訪問

[英]Synchronous access to REST web service

使用此代碼使用簡單的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倍。 我怎么能解決這個問題?

這里是resquest使用:@NamedQuery(name =“Links.getNext”,query =“SELECT l FROM Links l WHERE l.statusCode = 2”)

和super.next()方法:

public T next() {

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

謝謝

(根)JAX-RS資源的生命周期是每個請求 ,因此nextLink方法上的(否則正確的) synchronized關鍵字遺憾地是無效的。

您需要的是同步訪問/更新的意思。 這可以通過多種方式完成:

I)您可以在外部對象上進行同步,由框架注入(例如:CDI注入@ApplicationScoped),如下所示:

@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中可用的悲觀鎖定。

您需要使用某種形式的鎖定,很可能是樂觀的版本鎖定。 這將確保只有一個事務成功,另一個事務將失敗。

請參閱http://en.wikibooks.org/wiki/Java_Persistence/Locking

根據您在創建新Links相信爭用的頻率,您應該選擇使用@Version屬性進行樂觀鎖定或使用悲觀鎖定。

我的猜測是樂觀鎖定會更好地為您服務。 在任何情況下,讓您的Resource類充當服務外觀,並將模型相關的代碼放入無狀態會話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;
  }
}

這是一個半拋光的例子,說明了這種貓皮膚的一種方法,這里有很多東西。 如果您有任何疑問,請告訴我。

此外,從REST端可以考慮將@PUT用於此資源而不是@GET,因為您的端點具有更新(UUID和/或statusCode)資源狀態的副作用,而不僅僅是獲取它。

當使用JAX-RS這是一個Java EE特性時,根據我的理解,你不應該像使用synchronized塊一樣管理Java SE風格的線程。

在Java EE中,您可以使用singleton EJB提供對方法的同步訪問:

@Path("")
@Singleton
public class LinksResource {

    @GET
    @Path("next/{uuid}")
    @Produces({"application/xml", "application/json"})
    public Links nextLink(@PathParam("uuid") String uuid) {

默認情況下,這將使用@Lock(WRITE) ,它一次只允許一個請求到您的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM