Conoce las magias de Hibernate antes de usarlo
01-02-2024
Desde que ví las ventajas de usar un ORM me encantó la idea, pero el tiempo y las malas experiencias, poco a poco me hacen cambiar de opinión. Veamos por qué 🤣
Los ORM como todos sabemos, hacen muchas magias negras sin que nos demos cuenta, y curiosamente esas magias negras que antes me gustaban porque no tenía que hacerlas yo, cada vez me gustan menos. Últimamente, hemos estado trabajando con Hibernate, y claro, cuanto más trabajas con la librería, más cosas descubres.
💡 Para trabajar correctamente con la herramienta, es necesario conocerla en profundidad y no confiar en los automatismos y convenciones de la misma - Adrián Ferrera
En Hibernate, una instancia de una entidad de base de datos puede tener 3 estados distintos:
- Transient: Nueva instancia de una entidad, que no está asociada a una sesión y todavía no tiene representación en base de datos, por ejemplo, un usuario que está a punto de crearse.
- Persistent: Instancia de una entidad que tiene representación en base de datos y está asociada a una sesión, por ejemplo, un usuario que hemos buscado por identificador.
- Detached: Instancia de una entidad que tiene representación en base de datos pero no está asociada a una sesión. Esto último puede ser porque se ha cerrado la sesión, o porque se ha decidido deliberadamente desvincular dicha instancia de la sesión.
Una vez descrito esto, vamos a lo que puede ser un problema. Imaginémonos que en un momento dado, tenemos como entrada dos entidades que queremos modificar. Como estamos haciendo uso del mismo modelo de base de datos, al hacer un get
de estos objetos e instanciarlos, tendríamos dichas instancias en el estado Persistent. Una vez modificadas las instancias, podremos hacer efectivo el cambio en base de datos por medio del método entity.save()
.
Hasta ahora no debería haber ningún problema, sin embargo, si en el método especificamos la opción de flush
a true
, pasará un comportamiento que bien es el que describe Hibernate, pero a ojos de la persona que desarrolla, no es precisamente algo esperado. Veamos que dice hibernate sobre el flush
, y lo que implica.
Flushing is the process of synchronizing the state of the persistence context with the underlying database.
Esto viene a decir que, todo aquello que esté en el estado de persistence
, se insertará a la base de datos en el momento en el que se haga flush
, en vez de esperar a que el ORM decida cuando se debe hacer dicho cambio.
Esto suena bien, decidir en qué momento se hacen efectivos los cambios en base de datos, salvo que el save
no solo afectará a la entidad a la que hayamos hecho el save()
. Veamos un ejemplo:
User user1 = User.get(id1) user1.name = "modificado1" user2.name = "modificado2" User user2 = User.get(id2) user1.save(flush: true)
Cabría esperar que tras haber hecho el save
del user1, el flush
sólo afectara al user1. Pues curiosamente no es así, como hemos hecho el flush
, éste hace efectivos todos los cambios que se hayan hecho a todas las entidades.
Obviamente, esto no pasaría si no se usaran las entidades de base de datos en otros sitios, ni se usara el flush
como opción en el save()
. Entonces, como formas de evitar este comportamiento poco intuitivo (aunque técnicamente esperado según la documentación), podemos separar las entidades de dominio (aquellas que van a tener los cambios que realicemos a base de operaciones), de las de base de datos, para que sólo aquellas que realmente queramos que se modifiquen, lo hagan, y no por culpa de efectos colaterales poco intuitivos.