[英]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中可用的悲觀鎖定。
您需要使用某種形式的鎖定,很可能是樂觀的版本鎖定。 這將確保只有一個事務成功,另一個事務將失敗。
根據您在創建新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.