138. Spring Boot¶
De la familia Spring
138.1 SPRING DATA JPA¶
En el contexto de Spring Boot, el uso de Hibernate se maneja a través de Spring Data JPA, que proporciona una capa de abstracción sobre JPA (Java Persistence API) para facilitar el acceso y la manipulación de bases de datos. Cuando usas Spring Data JPA, defines repositorios extendiendo interfaces proporcionadas por Spring, como JpaRepository
o CrudRepository. Estas interfaces vienen con una serie de métodos útiles generados automáticamente por Spring, que te permiten realizar operaciones de base de datos sin necesidad de escribir implementaciones específicas.
El interface JpaRepository :
-
save(S entity): Guarda la entidad dada. Si la entidad ya existe, la actualiza.
-
findById(ID id): Recupera una entidad por su ID.
-
existsById(ID id): Devuelve true si una entidad con el ID dado existe.
-
findAll(): Encuentra todas las entidades de un tipo dado.
-
findAllById(Iterable
ids): Encuentra todas las entidades por los IDs dados. -
count(): Cuenta cuántas entidades existen.
-
delete(T entity): Elimina la entidad dada.
-
deleteAll(Iterable<? extends T> entities): Elimina todas las entidades dadas.
-
deleteById(ID id): Elimina la entidad con el ID dado.
-
deleteAll(): Elimina todas las entidades.
Además de estos métodos, Spring Data JPA permite la creación de consultas de manera automática a partir de los nombres de los métodos en tus interfaces de repositorio. Por ejemplo, si necesitas encontrar usuarios por su apellido, puedes simplemente declarar un método en tu repositorio como
List<User> findByLastName(String lastName)
138.1.1 Consultas más especificas¶
Esto es particularmente útil cuando las consultas que necesitas no se pueden cubrir con el mecanismo de generación de consultas a partir de nombres de métodos que ofrece Spring Data. Con @Query, puedes especificar una consulta en JPA Query Language (JPQL) o incluso en SQL nativo, dándote mucha flexibilidad para realizar operaciones de base de datos complejas.
En el siguiente ejemplo ?1 se refiere al primer parámetro de la función "email"
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmailAddress(String emailAddress);
}
@Query("SELECT u FROM User u WHERE u.lastName = :lastName")
List<User> findByLastName(@Param("lastName") String lastName);
Para usar con UPDATE:
@Modifying
@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :cutoffDate")
int deactivateUsersNotLoggedInSince(@Param("cutoffDate") LocalDateTime cutoffDate);
Con SQL NATIVO en lugar de JSQL
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailAddressNative(String emailAddress);
138.2 Busquedas Lazy y Eager¶
138.2.1 Carga Perezosa (Lazy Loading)¶
La carga perezosa es el enfoque predeterminado en JPA para cargar relaciones. Cuando accedes a una entidad, las entidades relacionadas no se cargan inmediatamente de la base de datos. En su lugar, se cargan "bajo demanda", es decir, solo cuando explícitamente accedes a ellas. Este enfoque ayuda a mejorar el rendimiento, especialmente cuando tienes relaciones con muchas entidades que no siempre son necesarias.
Para configurar una relación como perezosa, puedes usar la anotación @ManyToOne o @OneToMany con el atributo fetch establecido en FetchType.LAZY
en la definición en la @Entity
:
@OneToMany(fetch = FetchType.LAZY)
private Set<Item> items;
138.2.2 Carga Ansiosa (Eager Loading)¶
La carga ansiosa significa que cuando recuperas una entidad, JPA también recupera inmediatamente sus entidades relacionadas. Este enfoque es útil cuando sabes que vas a necesitar las entidades relacionadas junto con la entidad principal. Sin embargo, usar la carga ansiosa sin cuidado puede llevar a un rendimiento deficiente, especialmente si se cargan grandes cantidades de datos que no se necesitan.
Para configurar una relación como ansiosa, puedes usar la anotación @ManyToOne o @OneToMany con el atributo fetch establecido en FetchType.EAGER
:
@ManyToOne(fetch = FetchType.EAGER)
private User user;
138.2.3 @EnitityGraph¶
**@EntityGraph **es una poderosa característica de JPA y Spring Data JPA que te permite definir cómo se deben cargar las entidades y sus relaciones asociadas. Proporciona un control más granular sobre la carga ansiosa (eager) y perezosa (lazy) de las relaciones, permitiéndote optimizar el rendimiento de las consultas al especificar dinámicamente qué relaciones se deben cargar junto con la entidad principal.
Uso Básico Puedes usar @EntityGraph en dos contextos principales: directamente en la entidad o en el repositorio, sobre un método de consulta.
En la Entidad Cuando se utiliza en la entidad, @EntityGraph define un gráfico de entidad que puedes referenciar en tus consultas:
import javax.persistence.*
@Entity
@NamedEntityGraph(
name = "User.books",
attributeNodes = [NamedAttributeNode("books")]
)
data class User(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@ManyToMany(fetch = FetchType.LAZY) // Configuración predeterminada de lazy loading
val books: Set<Book> = HashSet()
)
@Entity
data class Book(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null
// Otros campos y relaciones
)
En el Repositorio
@EntityGraph
es particularmente útil cuando se usa en los métodos de repositorio:
Este gráfico indica que cuando se use, los books asociados con User se deben cargar de manera ansiosa, independientemente de la configuración de carga perezosa predeterminada.
interface UserRepository : JpaRepository<User, Long> {
@EntityGraph(attributePaths = ["books"])
fun findAllBy(): List<User>
}
Especificando el Tipo de Carga
Puedes especificar el tipo de carga en @EntityGraph usando el atributo type. Los valores pueden ser
Los valores pueden ser LOAD (para cargar las relaciones especificadas de manera ansiosa, además de las configuraciones de carga predeterminadas) o FETCH (para cargar solo las relaciones especificadas de manera ansiosa, ignorando las configuraciones de carga predeterminadas).
- EntityGraph.EntityGraphType.LOAD o
- EntityGraph.EntityGraphType.FETCH.
interface UserRepository : JpaRepository<User, Long> {
@EntityGraph(attributePaths = ["books"], type = EntityGraph.EntityGraphType.FETCH)
fun findByLastName(lastName: String): List<User>
}
138.2.4 Consideraciones¶
-
Selección Cuidadosa: Es crucial seleccionar cuidadosamente entre carga ansiosa y perezosa según tus necesidades de acceso a datos específicas para evitar sobre-carga o sub-carga de datos.
-
@EntityGraph
: Spring Data JPA ofrece la anotación @EntityGraph que te permite definir de manera dinámica cómo se deben cargar las entidades y sus relaciones, proporcionando un control más fino que puede ser útil para optimizar el rendimiento en consultas específicas. -
Problemas con Lazy Loading: Ten en cuenta que la carga perezosa puede causar la excepción
LazyInitializationException
si intentas acceder a las entidades relacionadas fuera del contexto de una transacción abierta, ya que el contexto necesario para cargar esas entidades ya no está disponible.
138.3 Observaciones¶
En algunos casos , por ejemplo al insertar un nuevo objeto se devuelve un Long con el identifiador. En este caso retrofit en la parte cliente no lo reconoce como una respuesta JSON.
La solución es usar un data class:
data class RespuestaId(
val id: Long
)
138.4 Apendice¶
ENLACES:
- Descarga de Spring Tool Suit 4
- Tutorial bastante completo en Java
- Uso de Thymeleaf en Spring
- Proyecto de ejemplo Servidor Recetario y cliente Android recetas