所以我从BalusC的这篇文章中读到了如何阻止无状态会话bean在被JSF访问时不断捶打数据存储(例如.DB((可能会/将进行多次调用(,所以我以我认为是BalusC发布的精神实现了我的代码(以及我看到的关于这个问题的"最佳实践"的其他论坛帖子(。
我的无状态会话 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;
}
}
编辑:我已将同步块添加到相关部分以确保串行访问,尽管现在开始感觉更像是单例。我仍然很好奇其他人如何处理与多次调用数据存储有关的问题。
具体来说,我创建了一个经过检查的"脏"标志,如果数据库已更新,则脏标志设置为 true(通过对数据库的更新(。检测到脏标志后,它会设置回 false,因此只对数据库进行一次调用。因此,不会随之而来的数据库捶打。
我的问题是:我所做的,是解决解决方案的合适"最佳实践",还是我不知道的更聪明的方法?我正在考虑旧的"J2EE"蓝图设计模式,以及在Java EE 6上下文中可能缺少的注释。
这是解决解决方案的适当"最佳实践",还是我不知道的更聪明的方法?
不幸的是,构建会话 Bean 的方式不是最佳实践。事实上,正如 Mikko 所解释的那样,这与会话豆的正常工作方式完全相反。所以,尽管你做出了明显的努力,恐怕你创造的是一个不好的做法的最好例子。它几乎可以执行在无状态会话 Bean 中不应执行的所有操作。
为了解决 BalusC 概述的问题,您可以使用视图绑定到的单独后备 Bean,而不是让它直接绑定到服务。然后,此支持是在请求期间或视图范围期间缓存结果的 Bean。
例如
服务:
@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 中的延迟加载模式。
您有两个主要问题:
- 状态在线程之间共享,而不使用锁定进行保护
- 无状态 Bean 具有对客户端可见的状态。
您可以通过静态更改变量在产品服务的所有实例之间共享状态。但是,对此变量的读取和写入不会同步。因此,无法保证实例共享相同的更改值。您必须通过锁定(与同一锁同步(或至少使其易变(仅保证您将看到最后写入的值(来控制访问。
下一个问题是无状态 bean 不应该有状态对客户端可见。这就是为什么他们被称为无国籍。哪个实例处理调用对客户端没有区别,因为客户端无法控制调用哪些实例。考虑以下示例场景(实例 s1 和 s2,假设两者都处于新状态(:
- s1.getProducts => 更改 = 假
- s2.getProducts => 因为已更改为假,它将返回 null(没有人为实例变量设置值适用于 S2 ( 的产品(
回答编辑的问题和评论:您仍然有无状态与状态相同的问题。无状态用于没有状态的解决方案不适合的情况。从下面可以看出,哪个实例将处理业务方法调用仍然很重要:
- s1.getProducts => changed = false;
- s2.getProducts => 因为更改为假,则返回 空值
请遵循Arjan Tijms提供的方法,或者至少具有相同的精神。
这是迄今为止我很长一段时间以来看到的最严重的无状态会话豆滥用。
您已经创建了一种单例,但是在绝对不适合此的 Bean 中,对全局和本地操作以及通过同步类文本来产生死锁的风险混合在一起。
如果你需要一个单例,你应该使用专用的@Singleton bean,但是如果你想防止在请求期间来自 JSF 的重复调用,请使用一个后备 bean,如另一个答案所示。
EJB 作为单例有一个具体的应用,示例在这里。EJB 3.0 标准允许对单个会话 Bean 进行分时、同步的访问。话虽如此,您有以下选项来防止/控制会话缓存。
1(单例 EJB 可以维护数据库结果的本地缓存副本,该副本使用 EJB 3.0 计时器 Bean 按计划刷新,这里是一个非常简单的例子。然后,您可以将此单例注入到您喜欢的任何上下文中 JSF 管理的 bean、CDI bean 等
2(或者,您的特定用例可以缓存其自己的所需数据的本地副本,该副本在 Bean 构造@PostConstruct
读取一次,尽管此范例比@RequestScoped
Bean 更适用于寿命更长的 Bean。
3(与 2( 相关(,有一个功能类似于上述 1( 的 @ApplicationScoped
bean,但现在维护着各种列表的(巨大(缓存