0

因此,我从 BalusC 的这篇文章中读到了如何阻止无状态会话 bean 在被 JSF 访问(可能/将进行多次调用)时不断破坏数据存储(例如 DB),所以我已经实现了我的代码'd like to think is in what was posted by BalusC (and other forum posts from "best practice" I've seen about this issue).

我的无状态会话 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(通过更新数据库)。在检测到脏标志后,它被设置回假,因此只调用了一次数据库。因此,不会发生 DB 抖动。

我的问题是:我所做的,这是解决解决方案的适当“最佳实践”,还是有一种我不知道的更聪明的方法?我在考虑旧的“J2EE”蓝图设计模式以及可能在 Java EE 6 的上下文中丢失的注释。

4

4 回答 4

5

这是解决解决方案的适当“最佳实践”还是我不知道的更聪明的方法?

不幸的是,您构建会话 bean 的方式并不是最佳实践。事实上,正如 Mikko 所解释的,这完全违背了会话 bean 的正常工作方式。因此,尽管您做出了明显的努力,但恐怕您创建的内容是不良做法的主要示例。它几乎做了所有在无状态会话 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 中的延迟加载模式。

于 2012-05-01T08:07:34.800 回答
2

你有两个主要问题:

  1. 状态在线程之间共享,没有锁保护
  2. 无状态 bean 的状态对客户端可见。

您通过静态更改变量在 ProductService 的所有实例之间共享状态。但是,对此变量的读取和写入是不同步的。因此,不能保证实例共享相同的changed值。您必须通过锁控制访问(与同一个锁同步)或至少使其易失(只是保证您将看到最后写入的值)。

下一个问题是无状态 bean 不应该具有对客户端可见的状态。这就是他们被称为无国籍的原因。哪个实例处理调用对客户端应该没有影响,因为客户端无法控制调用哪些实例。例如,考虑以下场景(实例 s1 和 s2,假设两者都处于新鲜状态):

  1. s1.getProducts => 改变 = 假
  2. s2.getProducts => 因为 changed 是 false,所以它会返回 null(没有一个给 s2 的实例变量 products 设置值)

回答已编辑的问题和评论: 无状态与状态仍然存在相同的问题。无状态用于没有状态的解决方案不适合的情况。正如您从下面看到的,哪个实例将处理业务方法调用仍然很重要:

  1. s1.getProducts => 改变 = 假;
  2. s2.getProducts => 因为 changed 是 false,所以返回 null

请遵循 Arjan Tijms 提供的方法,或者至少遵循相同的精神。

于 2012-04-30T05:06:37.883 回答
1

这是迄今为止我在很长一段时间内看到的对无状态会话 bean 的最严重滥用。

您已经创建了一种 Singleton,但在一个绝对不适合此的 bean 中,混合关注全局和本地操作以及通过在类文字上同步导致死锁的风险。

如果您需要 Singleton,则应使用专用的 @Singleton bean,但如果您想在请求期间防止来自 JSF 的重复调用,请使用另一个答案中显示的支持 bean。

于 2012-05-01T16:33:14.977 回答
0

作为单例的 EJB 有一个具体的应用程序,示例Here。EJB 3.0 标准允许对单个会话 bean 进行分时同步访问。话虽如此,您有以下选项来防止/控制会话缓存。

1) 单例 EJB 可以维护数据库结果的本地缓存副本,该副本使用 EJB 3.0 Timer bean 按计划刷新,这是一个可笑的简单示例Here。然后,您可以将这个单例注入到您喜欢的任何上下文中 JSF 托管 bean、CDI bean 等

2)或者,您的特定用例可以缓存它自己的所需数据的本地副本,该副本在 bean 构造时读取一次@PostConstruct,尽管这种范例更适用于寿命更长的 bean 而不是@RequestScopedbean。

3) 与 2) 相关,有一个@ApplicationScoped功能与上述 1) 类似的 bean,但现在维护各种列表的(巨大)缓存

于 2012-09-05T01:03:09.353 回答