Computación Web (2023/24)

Práctica 7: Desarrollo de una aplicación Web con Spring MVC (III)

Guardar mensajes en la base de datos

En esta práctica implementarás la funcionalidad de la aplicación de microblogging que se encarga de la gestión de mensajes: publicar nuevos mensajes de usuario y mostrarlos en la aplicación.

En primer lugar, de la misma forma que hiciste con la clase User en el laboratorio anterior, necesitarás anotar la clase Message como una entidad, para que Spring te permita almacenar los mensajes en la base de datos. Reemplaza el código de la clase Message con el siguiente:

package es.uc3m.microblog.model;

import java.util.Date;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Entity
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(nullable = false)
    @NotBlank
    @Size(max = 256)
    private String text;

    @ManyToOne(optional = false)
    private User user;

    @ManyToOne
    private Message responseTo;

    @Column(nullable = false)
    private Date timestamp;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Message getResponseTo() {
        return responseTo;
    }

    public void setResponseTo(Message responseTo) {
        this.responseTo = responseTo;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }
}

Observa en la declaración de esta clase que:

La relación entre usuarios y mensajes es bidireccional. Es decir, una relación muchos a uno de los mensajes a los usuarios corresponde a una relación uno a muchos de los usuarios a los mensajes. Modifica la clase User para que declare, además de las propiedades que ya tiene, esta relación:

// New classes to import:
import java.util.List;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;

// New attribute to add to the User class:
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private List<Message> messages;

// Access methods for the new attribute:
public List<Message> getMessages() {
    return messages;
}

public void setMessages(List<Message> messages) {
    this.messages = messages;
}

Observa que:

Para comprobar que este código se ha aplicado correctamente, inicia el servidor y, una vez iniciado, detenlo de nuevo. El esquema de la base de datos debería haber sido actualizado. Puedes comprobar esto conectándote a tu base de datos y ejecutando las siguientes sentencias SQL:

SHOW TABLES;
DESCRIBE message;
DESCRIBE user;

Comprueba que ambas tablas incluyen las columnas que esperabas de la declaración de las clases User y Message.

Creación de mensajes

Crea una nueva interfaz MessageRepository, análoga a UserRepository pero, de momento, sin métodos.

Añade a MainController un nuevo método que recibirá los datos del formulario de creación de mensajes y los publicará. Asignaremos a este controlador la ruta /post:

// New classes to import:
import java.security.Principal;
import es.uc3m.microblog.model.MessageRepository;

// New attribute:
@Autowired
private MessageRepository messageRepository;

// New method:
@PostMapping(path = "/post")
public String postMessage(@ModelAttribute Message message, Principal principal) {
    User user = userRepository.findByEmail(principal.getName());
    message.setUser(user);
    message.setTimestamp(new Date());
    messageRepository.save(message);
    return "redirect:message/" + message.getId();
}

Observa en este fragmento de código que:

A continuación, necesitas ajustar el formulario de creación de mensajes que deberías tener en la vista principal. En particular, debes asegurarte de que:

Para probar este código, accede a la vista principal y crea un nuevo mensaje. La redirección a la vista de mensajes fallará porque se necesitan algunos ajustes en esa vista, que harás en el próximo ejercicio. Sin embargo, deberías ver el mensaje en la base de datos si listas el contenido de la tabla message.

Vista de mensaje

En una práctica anterior preparaste una versión preliminar del método de controlador para la vista de mensaje. Este simplemente creaba un mensaje de prueba y algunas respuestas al mismo, y los mostraba.

En este ejercicio cambiarás ese método para que, en lugar de mostrar un mensaje de prueba especificado directamente en el código, reciba el identificador del mensaje a mostrar como un parámetro de la petición, recupere ese mensaje de la base de datos y se lo pase a la plantilla para mostrarlo.

Se muestra a continuación un ejemplo de cómo sería este método. Necesitas reemplazar tu método antiguo con este, pero ten en cuenta que es posible que necesites adaptar algunas cosas para integrarlo correctamente. En particular, este ejemplo asume que la ruta de esta vista es /message y que la plantilla de Thymeleaf se llama message_view.html. Cámbialos si en tu código son diferentes.

// New classes to import:
import java.util.Optional;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.bind.annotation.PathVariable;

// New code for the controller method of the message view:
@GetMapping(path = "/message/{messageId}")
public String messageView(@PathVariable("messageId") int messageId, Model model) {
    Optional<Message> messageOpt = messageRepository.findById(messageId);
    if (!messageOpt.isPresent()) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Message not found");
    }
    model.addAttribute("message", messageOpt.get());
    return "message_view";
}

Observa en el código anterior que:

Debido a que ahora añadimos el identificador del mensaje a la ruta de este controlador, las rutas relativas en tu plantilla dejarán de funcionar correctamente (estarás un nivel más bajo que antes). En este ejercicio y en los próximos necesitarás cambiar las rutas relativas que uses en plantillas Thymeleaf tal y como se muestra en los siguientes ejemplos:

<link rel="stylesheet" th:href="@{/example.css}">
<script th:src="@{/public/example.js}"></script>
<a th:href="@{/}">Volver al inicio</a>
<a th:href="@{/user/453}">@mary</a>
<form th:action="@{/delete}" method="post">...</form>

Observa el uso de th:href, th:src y th:action, y la notación @{...} que usamos en los ejemplos anteriores.

Reinicia la aplicación web y crea un nuevo mensaje. Si todo funciona, inmediatamente después de crear el nuevo mensaje se debería presentar la vista del mensaje con los datos de ese nuevo mensaje.

Mostrar mensajes en la vista principal

Dado que de momento no tenemos la funcionalidad para que un usuario siga a otros, simplemente mostraremos en la vista principal los diez mensajes más recientes que se hayan publicado, independientemente de quién sea su creador. En este ejercicio recuperaremos estos mensajes desde la base de datos y los mostraremos en la vista principal, en lugar de los mensajes fijos que están mostrando ahora.

Podemos hacer que la interfaz MessageRepository devuelva los diez mensajes más recientes simplemente declarando el siguiente método en MessageRepository:

// New import statement
import java.util.List;

// New method declaration for MessageRepository
List<Message> findFirst10ByOrderByTimestampDesc();

El nombre findFirst10ByOrderByTimestampDesc le está diciendo a Spring Data JPA que queremos obtener los primeros diez mensajes, ordenados por su propiedad timestamp, que es de tipo Date, en orden descendente (es decir, las fechas más recientes, primero). Como sucedió con los métodos de los párrafos anteriores, Spring nos proporcionará automáticamente una implementación para este método.

Modifica el método del controlador que maneja la vista principal para que, en lugar de crear los mensajes de ejemplo fijos, obtenga, invocando este nuevo método de MessageRepository, los diez mensajes más recientes. Almacénalos en Model para que la plantilla los muestre.

Modifica la plantilla Thymeleaf de la vista principal para que, como consideres oportuno, cada mensaje enlace a su propia vista de mensaje. Es decir, queremos que si los usuarios pinchan en cualquier mensaje (tú decides exactamente dónde hacerlo, ya sea en el texto del mensaje, en un enlace adicional, etc.), se les muestre la vista de dicho mensaje.

Para hacer esto debes construir un hipervínculo cuyo camino incluya el identificador del mensaje (por ejemplo, message/42 cuando el usuario pincha en el mensaje con identificador 42). Thymeleaf proporciona soporte para esto, como se muestra en el siguiente ejemplo:

<a th:href="@{/message/{id}(id=${message.getId()})}">Go to the message</a>

El código del ejemplo le dice a Thymeleaf que inyecte la variable id al final del camino, donde id toma el resultado de evaluar la expresión ${message.getId()}, es decir, el identificador del mensaje que se está mostrando.

Reinicia la aplicación y comprueba que todo funciona como se espera.

Vista de perfil de usuario

El último paso de esta práctica es actualizar la vista del perfil de usuario para que, en vez de mostrar un usuario de prueba fijo como hasta ahora, muestre el perfil del usuario cuyo identificador reciba como parte de la ruta. Deben mostrarse, al menos, el nombre del usuario y todos los mensajes publicados por este, ordenados de más a menos reciente.

Deberías hacer algo similar a lo que hemos hecho con la vista de mensajes en ejercicio anteriores. En particular: