使用此代码处理简单的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;
}
THX
答案 0 :(得分:5)
(根)JAX-RS资源的生命周期是每个请求,因此synchronized
方法上的(否则正确的)nextLink
关键字遗憾地无效
您需要的是同步访问/更新的意思。 这可以通过多种方式完成:
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中可用的悲观锁定。
答案 1 :(得分:0)
您需要使用某种形式的锁定,很可能是乐观的版本锁定。这将确保只有一个事务成功,另一个事务将失败。
答案 2 :(得分:0)
根据您认为创建新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)的副作用,而不是简单地获取资源的状态。
答案 3 :(得分:0)
在使用作为Java EE功能的JAX-RS时,据我所知,您不应像使用synchronized
块那样管理Java SE风格的线程。
在Java EE中,您可以通过单例EJB提供对方法的同步访问:
@Path("")
@Singleton
public class LinksResource {
@GET
@Path("next/{uuid}")
@Produces({"application/xml", "application/json"})
public Links nextLink(@PathParam("uuid") String uuid) {
默认情况下,它将使用@Lock(WRITE)
,它一次只允许对您的方法一个请求。