Creating master-detail pages for entities, how to link them and which bean scope to choose

后端 未结 2 1365
礼貌的吻别
礼貌的吻别 2020-11-21 06:26

I have started learning JSF, but sadly most tutorials out there present only a log in or a register section.

Can you point me to some more in depth examples? One thi

相关标签:
2条回答
  • 2020-11-21 06:46

    What is the correct usage of session scope

    Use it for session scoped data only, nothing else. For example, the logged-in user, its settings, the chosen language, etcetera.

    See also:

    • How to choose the right bean scope?

    And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?

    Typically you use the request or view scope for it. Loading of the list should happen in a @PostConstruct method. If the page doesn't contain any <h:form>, then the request scope is fine. A view scoped bean would behave like a request scoped when there's no <h:form> anyway.

    All "view product" and "edit product" links/buttons which just retrieve information (i.e. idempotent) whould be just plain GET <h:link> / <h:button> wherein you pass the entity identifier as a request parameter by <f:param>.

    All "delete product" and "save product" links/buttons which will manipulate information (i.e. non-idempotent) should perform POST by <h:commandLink>/<h:commandButton> (you don't want them to be bookmarkable/searchbot-indexable!). This in turn requires a <h:form>. In order to preserve the data for validations and ajax requests (so that you don't need to reload/preinitialize the entity on every request), the bean should preferably be view scoped.

    Note that you should basically have a separate bean for each view and also note that those beans doesn't necessarily need to reference each other.

    So, given this "product" entity:

    @Entity
    public class Product {
    
        @Id
        private Long id;
        private String name;
        private String description;
    
        // ...
    }
    

    And this "product service" 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));
        }
    
        // ...
    }
    

    You can have this "view products" on /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();
        }
    
        // ...
    }
    

    And you can have this "edit product" on /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";
        }
    
        // ...
    }
    

    And this converter for <f:viewParam> of "edit product":

    @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);
            }
        }
    
    }
    

    You can even use a generic converter, this is explained in Implement converters for entities with Java Generics.

    See also:

    • How to navigate in JSF? How to make URL reflect current page (and not previous one)
    • JSF Controller, Service and DAO
    • JSF Service Layer
    • How to inject @EJB, @PersistenceContext, @Inject, @Autowired, etc in @FacesConverter?
    • Communication in JSF 2.0 - Contains several examples/hints
    0 讨论(0)
  • 2020-11-21 06:55

    As a small improvement to what BalusC recommended, sometimes you can remove the required / requiredMessage part from the <f:viewParam> of your "details" screen and instead use the conditional rendering of the editing form (as BalusC did) with a reverse condition for recommending a specific link for the "list/master" screen or, even use a viewAction that would test the param and force a redirect to that list.

    0 讨论(0)
提交回复
热议问题