所以我从BalusC的this post读到如何阻止无状态会话bean在被JSF访问时不断地颠簸数据存储(例如DB)(这可能会/会进行多次调用)所以我已经根据{{3}}(以及我见过的关于这个问题的“最佳实践”的其他论坛帖子)的精神,实现了我想要的代码。
我的无状态会话bean如下所示:
@Stateless
@Named("productsService")
public class ProductService {
private static boolean changed = true;
private List<Product> products;
private long count;
@PersistenceContext(name = "myPU")
private EntityManager em;
@Inject
private Product product;
public ProductService() {
}
private void productRecordsFromDB() {
products = em.createNamedQuery("Product.getAll", Product.class).getResultList();
Object o = em.createNamedQuery("Product.getCount", Product.class).getSingleResult();
count = ((Long) o).longValue();
}
public void addProduct() {
synchronized (ProductService.class) {
changed = true;
em.persist(product);
}
}
public long countProducts() {
return count;
}
public void removeProduct(Product p) {
synchronized (ProductService.class) {
changed = true;
em.remove(em.merge(p));
}
}
public int removeAllProducts() {
synchronized (ProductService.class) {
changed = true;
return em.createNamedQuery("Product.deleteAll").executeUpdate();
}
}
public List<Product> getProducts() {
synchronized (ProductService.class) {
if (changed) {
productRecordsFromDB();
changed = false;
}
return products;
}
}
public Product getProduct() {
return product;
}
public void setProduct(Product p) {
product = p;
}
}
编辑:我已将synchronized块添加到相关部分以确保串行访问,尽管现在开始感觉更像是单例。我仍然很想知道其他人如何处理与多个数据存储调用有关的问题。
具体来说,我创建了一个'脏'标志,检查过,如果DB已经更新,那么脏标志设置为true(通过更新数据库)。检测到脏标志后,它将设置为false,因此只能对DB进行一次调用。因此,不会发生数据库颠簸。
我的问题是:我做了什么,这是解决问题的合适“最佳实践”,还是有一种我不知道的更聪明的方法?我在思考旧的“J2EE”蓝图设计模式以及可能在Java EE 6环境中可能缺少的注释。
答案 0 :(得分:5)
这是解决问题的合适“最佳实践”,还是有一种我不了解的更聪明的方法?
遗憾的是,构建会话bean的方式不是最佳做法。事实上,正如Mikko解释的那样,它完全反对会话bean应该如何正常工作。所以,尽管你做了明显的努力,但我担心你创造的是一个不好的做法的一个主要例子。它几乎完成了无状态会话bean中不所做的一切。
为了解决BalusC概述的问题,您可以使用视图绑定的单独的辅助bean,而不是让它直接绑定到服务。然后,此支持是在请求期间或视图范围持续时间内缓存结果的bean。
E.g。
服务:
@Stateless
public class ProductService {
@PersistenceContext(name = "myPU")
private EntityManager em;
public List<Product> getProducts() {
em.createNamedQuery("Product.getAll", Product.class).getResultList();
}
// ...
}
然后支持bean (如果使用CDI,通过例如CODI添加@ViewScoped注释):
@ViewScoped
@ManagedBean
public class SomeBacking {
private List<Product> products;
@EJB
ProductService productService;
@PostConstruct
public void init() {
products = productService.getProducts();
}
public List<Product> getProducts() {
return products;
}
}
我已将此服务的调用设置为@PostConstruct
,但根据您的具体要求,这当然也可以在操作方法中或通过getter中的延迟加载模式。
答案 1 :(得分:2)
您有两个主要问题:
您通过静态已更改变量在ProductService的所有实例之间共享状态。但是,对此变量的读取和写入不同步。因此,无法保证实例共享已更改的相同值。您必须通过锁控制访问(与同一锁同步)或最小化使其不稳定(只保证您将看到最后写入的值)。
下一个问题是无状态bean不应该让客户端看到状态。这就是他们被称为无国籍的原因。对于客户端哪个实例处理调用应该没有区别,因为客户端无法控制调用哪些实例。考虑以下场景(实例s1和s2,假设两者都处于新鲜状态):
回答编辑的问题和评论: 对于无状态与状态,你仍然有同样的问题。在没有状态的解决方案不适合的情况下使用无状态。正如您从下面看到的那样,仍然需要处理业务方法调用的实例:
请遵循Arjan Tijms提供的方法,或至少以同样的精神。
答案 2 :(得分:1)
这是迄今为止我见过的最糟糕的无状态会话bean滥用。
你已经创建了一种Singleton,但是在一个绝对不适合这种情况的bean中,对全局和本地操作的混合关注以及通过在类文字上同步而导致死锁的风险。
如果你需要一个Singleton,你应该使用专用的@Singleton bean,但是如果你想在请求期间阻止JSF的重复调用,请使用另一个答案中显示的支持bean。
答案 3 :(得分:0)
EJB有一个具体的应用程序作为单例,示例Here。 EJB 3.0标准允许对单个会话bean进行分时,同步访问。话虽如此,您有以下选项来阻止/控制会话缓存。
1)单例EJB可以维护数据库结果的本地缓存副本,该副本使用EJB 3.0 Timer bean按计划刷新,这是一个荒谬的简单示例Here。这个单例你可以注入你喜欢的任何上下文JSF托管bean,CDI bean等
2)或者,您的特定用例可以缓存它自己的所需数据的本地副本,在bean构造@PostConstruct
读取一次,尽管这种范例更适用于更长寿的bean而不是@RequestScoped
豆。
3)与2)相关,有一个@ApplicationScoped
bean,其功能与上述1)类似,但现在维护着各种列表的(巨大)缓存