【问题标题】:Creating master-detail pages for entities, how to link them and which bean scope to choose为实体创建主从页面,如何链接它们以及选择哪个 bean 范围
【发布时间】:2018-02-23 13:19:52
【问题描述】:

我已经开始学习 JSF,但遗憾的是,大多数教程只提供登录或注册部分。

你能指出一些更深入的例子吗?我感兴趣的一件事是显示产品列表的页面。我在页面 home 上并按下页面 products 以便我可以看到添加的最新 products。每次我访问该页面时,都会根据数据库中的最新条目创建产品列表。我该如何处理?

解决这个问题的一种方法是创建一个会话范围的托管 bean,我将在其中放置通过其他托管 bean 更新的不同实体。我在一些教程中发现了这种方法,但似乎相当困难和笨拙。

解决此类问题的最佳方法是什么?两页主从用户界面中会话范围的正确用法是什么?

【问题讨论】:

  • 好的,谢谢。在这种情况下,我应该将产品列表存储在哪里?现在我有一个名为 Products Controller 的控制器 Bean(包含为产品实体调用的所有操作)和一个产品模型(一个与相应数据库表具有相同属性的 Java Bean)。我应该在哪里存储 productList 属性,以便我可以从 product 页面访问它
  • 只需要产品页面上的产品列表。每次我访问该页面时,都会根据数据库中的最新条目创建产品列表。我该如何处理?
  • 我希望有更多这样的问题和答案来正确使用 backing bean。你看到的大多数例子都是错误的。
  • 问题标题不代表您的内容。主从概念与简单的产品列表页面完全不同。

标签: jsf session scope master-detail


【解决方案1】:

会话范围的正确用法是什么

仅将其用于会话范围的数据,仅此而已。例如,登录用户、其设置、选择的语言等等。

另见:


而且每次我访问该页面时,都会根据数据库中的最新条目创建产品列表。我该如何处理?

通常您使用请求或视图范围。列表的加载应该在 @PostConstruct 方法中进行。如果页面不包含任何<h:form>,那么请求范围就可以了。当没有 <h:form> 时,视图范围的 bean 的行为类似于请求范围。

检索信息(即幂等)的所有“查看产品”和“编辑产品”链接/按钮都应该是普通的 GET <h:link> / <h:button> 其中您将实体标识符传递为<f:param>的请求参数。

所有将操作信息(即非幂等)的“删除产品”和“保存产品”链接/按钮应由<h:commandLink>/<h:commandButton> 执行 POST(您不希望它们是可收藏的/可搜索机器人索引的!)。这又需要<h:form>。为了保留验证和 ajax 请求的数据(这样您就不需要在每个请求上重新加载/预初始化实体),bean 最好是视图范围。

请注意,基本上每个视图都应该有一个单独的 bean,并且还请注意,这些 bean 不一定需要相互引用。

所以,给定这个“产品”实体:

@Entity
public class Product {

    @Id
    private Long id;
    private String name;
    private String description;

    // ...
}

还有这个“产品服务”EJB:

@Stateless
public class ProductService {

    @PersistenceContext
    private EntityManager em;

    public Product find(Long id) {
        return em.find(Product.class, id);
    }

    public List<Product> list() {
        return em.createQuery("SELECT p FROM Product p", Product.class).getResultList();
    }

    public void create(Product product) {
        em.persist(product);
    }

    public void update(Product product) {
        em.merge(product);
    }

    public void delete(Product product) {
        em.remove(em.contains(product) ? product : em.merge(product));
    }

    // ...
}

您可以在/products.xhtml 上“查看产品”:

<h:dataTable value="#{viewProducts.products}" var="product">
    <h:column>#{product.id}</h:column>
    <h:column>#{product.name}</h:column>
    <h:column>#{product.description}</h:column>
    <h:column>
        <h:link value="Edit" outcome="/products/edit">
            <f:param name="id" value="#{product.id}" />
        </h:link>
    </h:column>
</h:dataTable>
@Named
@RequestScoped
public class ViewProducts {

    private List<Product> products; // +getter

    @EJB
    private ProductService productService;

    @PostConstruct
    public void init() {
        products = productService.list();
    }

    // ...
}

您可以在/products/edit.xhtml 上拥有这个“编辑产品”:

<f:metadata>
    <f:viewParam name="id" value="#{editProduct.product}" 
        converter="#{productConverter}" converterMessage="Unknown product, please use a link from within the system."
        required="true" requiredMessage="Bad request, please use a link from within the system."
    />
</f:metadata>

<h:messages />

<h:form rendered="#{not empty editProduct.product}>
    <h:inputText value="#{editProduct.product.name}" />
    <h:inputTextarea value="#{editProduct.product.description}" />
    ...
    <h:commandButton value="save" action="#{editProduct.save}" />
</h:form>
@Named
@ViewScoped
public class EditProduct {

    private Product product; // +getter +setter

    @EJB
    private ProductService productService;

    public String save() {
        productService.update(product);
        return "/products?faces-redirect=true";
    }

    // ...
}

这个转换器用于“编辑产品”的&lt;f:viewParam&gt;

@Named
@RequestScoped
public class ProductConverter implements Converter {

    @EJB
    private ProductService productService;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            Long id = Long.valueOf(value);
            return productService.find(id);
        } catch (NumberFormatException e) {
            throw new ConverterException("The value is not a valid Product ID: " + value, e);
        }
    }

    @Override    
    public String getAsString(FacesContext context, UIComponent component, Object value) {        
        if (value == null) {
            return "";
        }

        if (value instanceof Product) {
            Long id = ((Product) value).getId();
            return (id != null) ? String.valueOf(id) : null;
        } else {
            throw new ConverterException("The value is not a valid Product instance: " + value);
        }
    }

}

您甚至可以使用通用转换器,Implement converters for entities with Java Generics 对此进行了说明。

另见:

【讨论】:

  • +1 非常适合在 JSF 中进行主/细节编辑的模式。只是这应该放在某个地方的蓝图中。
  • 谢谢!你的博文给了我所有需要的答案。
  • @BalusC 当该帖子具有设置编辑布尔值的支持 bean 方法时,您将如何将您的建议纳入此帖子stackoverflow.com/questions/8768117/… 中用于编辑和视图的同一页面,但在这里您建议使用简单的 GET 链接?我喜欢上面的内容,它很简单,但我也想说“嘿,你无权编辑”和 redirec/fwd 仅当有人猜到 URL 'edit.xhtml?product_id=###' 我知道这一点帖子太旧了,也许你现在有一些更新的方法来做事
  • 非常感谢你提供了这个很好的例子,你拯救了我的一天:)
  • EditProduct.save() 中引用的 ProductService.save() 方法似乎在 ProductService EJB 类中缺失。
【解决方案2】:

作为对 BalusC 建议的一个小改进,有时您可以从“详细信息”屏幕的 &lt;f:viewParam&gt; 中删除 required / requiredMessage 部分,而是使用编辑表单的条件渲染(就像 BalusC 所做的那样) 使用反向条件来推荐“列表/主”屏幕的特定链接,或者甚至使用可以测试参数并强制重定向到该列表的 viewAction。

【讨论】:

    猜你喜欢
    相关资源
    最近更新 更多