Crear páginas maestras y de detalles para entidades, cómo vincularlas y qué alcance de bean elegir

Resuelto TGM asked hace 12 años • 2 respuestas

Empecé a aprender JSF, pero lamentablemente la mayoría de los tutoriales que existen solo presentan una sección de inicio de sesión o registro.

¿Puede indicarme algunos ejemplos más detallados? Una cosa que me interesa es una página que presente una lista de productos . Estoy en la página de inicio y presiono en la página de productos para poder ver los últimos productos agregados. Y cada vez que visito la página, la lista de productos se creará a partir de las últimas entradas de la base de datos. ¿Cómo puedo manejar esto?

Una forma de resolver esto sería crear un bean administrado con ámbito de sesión en el que colocaría diferentes entidades actualizadas a través de otros beans administrados. Encontré este tipo de enfoque en algunos tutoriales, pero parece bastante difícil y torpe.

¿Cuál sería el mejor enfoque para resolver algo como esto? ¿Cuál es el uso correcto del alcance de la sesión en una interfaz de usuario maestra-detalle de dos páginas?

TGM avatar Dec 11 '11 04:12 TGM
Aceptado

¿Cuál es el uso correcto del alcance de la sesión?

Úselo solo para datos de ámbito de sesión, nada más. Por ejemplo, el usuario que inició sesión, su configuración, el idioma elegido, etcétera.

Ver también:

  • ¿Cómo elegir el alcance del frijol adecuado?

Y cada vez que visito la página, la lista de productos se creará a partir de las últimas entradas de la base de datos. ¿Cómo puedo manejar esto?

Normalmente se utiliza la solicitud o el alcance de visualización para ello. La carga de la lista debe realizarse en un @PostConstructmétodo. Si la página no contiene ninguno <h:form>, entonces el alcance de la solicitud está bien. Un bean con alcance de vista se comportaría como una solicitud con alcance cuando no hay <h:form>ningún modo.

Todos los enlaces/botones "ver producto" y "editar producto" que simplemente recuperan información (es decir, idempotentes) deberían ser simplemente GET <h:link>/ <h:button>en los que se pasa el identificador de entidad como parámetro de solicitud mediante <f:param>.

Todos los enlaces/botones "eliminar producto" y "guardar producto" que manipularán información (es decir, no idempotentes) deben realizar la POST mediante <h:commandLink>/ <h:commandButton>(¡no desea que se puedan marcar como favoritos/indexar por el robot de búsqueda!). Esto a su vez requiere un <h:form>. Para preservar los datos para validaciones y solicitudes ajax (de modo que no necesite recargar/preinicializar la entidad en cada solicitud), es preferible que el bean tenga un alcance de vista.

Tenga en cuenta que básicamente debería tener un bean separado para cada vista y también tener en cuenta que esos beans no necesariamente necesitan hacer referencia entre sí.

Entonces, dada esta entidad de "producto":

@Entity
public class Product {

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

    // ...
}

Y este EJB de "producto servicio":

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

    // ...
}

Puedes tener esta "ver productos" en /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();
    }

    // ...
}

Y puedes tener este "editar producto" en /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";
    }

    // ...
}

Y este convertidor para <f:viewParam>"editar producto":

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

}

Incluso puedes usar un conversor genérico, esto se explica en Implementar conversores para entidades con Java Genéricos .

Ver también:

  • ¿Cómo navegar en JSF? Cómo hacer que la URL refleje la página actual (y no la anterior)
  • Controlador JSF, Servicio y DAO
  • Capa de servicio JSF
  • ¿Cómo inyectar @EJB, @PersistenceContext, @Inject, @Autowired, etc. en @FacesConverter?
  • Comunicación en JSF 2.0: contiene varios ejemplos/sugerencias
BalusC avatar Dec 11 '2011 14:12 BalusC