Base de datos
Asociaciones y Relaciones#
Las asociaciones son la forma de Pop de definir una relación entre dos objetos en la base de datos. En este capítulo, aprenderás como definir asociaciones usando tags de estructura; y cómo manipularlos con el modificador Eager.
Ejempl#
type User struct {
ID uuid.UUID
Email string
Password string
Books Books `has_many:"books" order_by:"title asc"`
FavoriteSong Song `has_one:"song" fk_id:"u_id"`
Houses Addresses `many_to_many:"users_addresses"`
}
type Book struct {
ID uuid.UUID
Title string
Isbn string
User User `belongs_to:"user"`
UserID uuid.UUID
}
type Song struct {
ID uuid.UUID
Title string
UserID uuid.UUID `db:"u_id"`
}
type Address struct {
ID uuid.UUID
Street string
HouseNumber int
}
type Books []Book
type Addresses []Address
Tags de estructura disponibles#
Usando el ejemplo anterior, el código a continuación, es una lista de los tags de estructura disponiblesy como usarlos.
-
has_many: Este tag es usado para discribir relaciones uno a muchos en la base de datos. En el ejemplo, El tipoUserdefine una relación con una lista de tipoBooksmediante el uso del taghas_many, lo que significa que unUserpuede tener variosBooks. Cuando consultamos ne la base de datos, Pop cargará todos los registros de la tablabooksque tengan una columna llamadausers_id, o la columna especificada confk_idque coincida con el valor deUser.ID. -
belongs_to: Este tag se usa para describir el propietario en la relación. Un propietario representa una dependencia altamente acoplada entre el modelo y el campo de asociación de destino donde el tagbelongs_tofue definido. Este tag se usa principalmente para indicar que el modelo posee su “existencia” en el campo de asociaciónbelongs_to. En el ejemplo de arriba, el tipoBookusabelongs_topara indicar que su propietario es el tipoUser. cuando consultamos en la base de datos, Pop cargará un registro de la tablauserscon unidque coincida con el valor del campoBook.UserID. -
has_one: Este tag se usa para describir las relaciones uno a uno en la base de datos. En el ejemplo de arriba, hay unaFavoriteSongdentro de todas los registros de canciones que el tipoUserle gusta más. Cuando consultamos en la base de datos, Pop cargará un registro de la tablasongsque tiene una columna llamadauser_id, una columna especificada confk_idque coincide con el valor del campoUser.ID. -
many_to_many: Este tag se usa para describir relaciones muchos a muchos en la base de datos. En el ejemplo de arriba, la relacion entreUsery la lista de tipoAddressesexiste para indicar que unUSerpuede ser propietario de muchasHousesy unaHouseser propiedad de muchosUsers. Es importante notar que el valor para el tagmany_to_manyes la tabla que conecta ambos lados en la relación; en el ejemplo de arriba, este valor se define comousers_addresses. Cuando consultamos en la base de datos, Pop cargará todos los registros de la tablaaddressesmediante la tabla asociativausers_addresses. La tablausers_addressesDEBE tener definidas las columnasaddress_idyuser_idpara coincidir con los valores de los camposAddress.IDyUser.ID. Tambien puedes definir un tagfk_idque se usará en la asociacion de destino, es decir, la tablaaddresses. -
fk_id: Este tag se puede usar para definir el nombre de la columna en la asociación de destino que coincida con elIDdel modelo. En el ejemplo de arriba,Songtiene una columna llamadau_idque hace referencia al id de la tablausers. Cuando cargamosFavoriteSong,u_idserá usado en lugar deuser_id. -
order_by: Este tag se puede usar en combinacion con los tagshas_manyymany_to_manypara indicar el orden de la asociación cuando se cargan. La forma de uso es:order_by:"<column_name> <asc | desc>"
Cargando asociaciones#
Pop actualmente proporciona dos modos para cargar asociaciones; cada modo afectará la forma en que Pop carga las asociaciones y consultas a la base de datos.
Eager. El modo por defecto. Al habilitar este modo, pop realizará “n” consultas para cada asociación definida en el modelo. Esto significa más llamadas a la base de datos para no afectar el uso de la memoria.
EagerPreload. Modo opcional. Al habilitar este modo, Pop ejecutará una consulta para cada asociación definida en el modelo. Este modo llamará a la base de datos con una frecuencia reducida sacrificando más espacio en memoria.
-
pop.SetEagerMode: Pop permite habilitar alguno de estos modos globalmente, lo que afectará el rendimiento del manejo de TODAS las consultas. UsaEagerDefaultoEagerPreloadcomo parametro para activar alguno de estos modos. -
tx.EagerPreload | q.EagerPreload: Pop permite a los desarrolladores controlar en cuales situaciones quienen que Pop ejecute cualquiera de estos modos cuando sea necesario. Este método activará el modoEagerPreloadsolo para la consulta en acción. -
tx.Eager | q.Eager: Pop permite a los desarrolladores controlar en cuales situaciones quienen que Pop ejecute cualquiera de estos modos cuando sea necesario. Este metodo activará el modoEagersolo para la consulta en acción.
Modo Eager#
El método pop.Connection.Eager() le indica a Pop que cargue las asociaciones para un modelo una vez que ese modelo se carge desde la base de datos.
Este modo ejecutará “n” consultas para cada asociacion definida en el modelo.
for i := 0; i < 3; i++ {
user := User{ID: i + 1}
tx.Create(&user)
}
for i := 0; i < 3; i++ {
book := Book{UserID: i +1}
tx.Create(&book)
}
u := Users{}
// Carga todas las asociaciones para cada usuario registrado. Por ejemplo `Books`, `Houses` y `FavoriteSong`
err := tx.Eager().All(&u)
El modo Eager va a:
- Cargar todos los users.
SELECT * FROM users;
- Recorrer cada usuario y cargar sus asociaciones
SELECT * FROM books WHERE user_id = 1
SELECT * FROM books WHERE user_id = 2
SELECT * FROM books WHERE user_id = 3
Modo EagerPreload#
El método pop.Connection.EagerPreload() le indica a pop que cargue las asociaciones para un modelo una vez que el modelo es cargado desde la base de datos. Este modo llamará a la base de datos con una frecuencia reducida sacrificando más espacio en memoria.
for i := 0; i < 3; i++ {
user := User{ID: i + 1}
tx.Create(&user)
}
for i := 0; i < 3; i++ {
book := Book{UserID: i +1}
tx.Create(&book)
}
u := Users{}
// Carga todas las asociaciones para cada usuario registrado. Por ejemplo `Books`, `Houses` y `FavoriteSong`
err := tx.EagerPreload().All(&u)
El modo EagerPreload va a:
- Cargará todos los usuarios.
SELECT * FROM users;
- Carga las asociaciones para todos los usuarios en una sola consulta.
SELECT * FROM books WHERE user_id IN (1, 2, 3)
Cargar asociaciones específicas#
Por defecto, Eager y EagerPreload cargarán todas las asociaciones asignadas para el modelo. PAra especificar cuales asociaciones deben ser cargadas, puedes pasar los nombres de esos campos a los metodos Eager o EagerPreload y solo se cargarán esas asociaciones.
// Cargar solo las asociaciones `Books` para los usuarios con nombre `Mark`
u := []User{}
// Usando modo `Eager`
err := tx.Eager("Books").Where("name = 'Mark'").All(&u)
// Usando modo `EagerPreload`
err := tx.EagerPreload("Books").Where("name = 'Mark'").All(&u)
Pop también te permite cargar asociaciones anidadas usando el caracter . para concatenarlos. Echale un vistazo al siguiente ejemplo.
// Cargará todos los `Books` para `u` y para cada `Book`, cargará el usuario que será el mismo que `u`
u := User{}
// Usando modo `Eager`
tx.Eager("Books.User").First(&u)
// Usando modo `EagerPreload`
tx.EagerPreload("Books.User").First(&u)
// Cargará todos los `Books` para `u` y para cada `Book` cargará todos los `Writers` y para cada `Writer` cargará la asociación `Book`.
u := User{}
// Usando modo `Eager`
tx.Eager("Books.Writers.Book").First(&u)
// Usando modo `EagerPreload`
tx.EagerPreload("Books.Writers.Book").First(&u)
// Cargará todos los `Books` para `u` y para cada `Book` cargará todos los `Writers`. Y también cargará `FavoriteSong` de `u`.
u := User{}
// Usando modo `Eager`
tx.Eager("Books.Writers").Eager("FavoriteSong").First(&u)
// Usando modo `EagerPreload`
tx.EagerPreload("Books.Writers").EagerPreload("FavoriteSong").First(&u)
Cargar asociaciones para un modelo existente#
El método pop.Connection.Load() toma una estructura de modelo, que ha sido llenado desde la base de datos, y una lista opcional de asociaciones para cargar.
u := User{}
// Carga todas las asociaciones para `u`. Por ejemplo `Books`, `Houses` y `FavoriteSong`
tx.Load(&u)
// Carga solo las asociaciones `Books` para `u`
tx.Load(&u, "Books")
El método Load no recuperará el User de la base de datos, solo sus asociaciones.
Creación anidada plana#
Pop te permite crear los modelos y sus asociaciones con otros modelos en un solo paso de manera predeterminada. Ya no necesitas crear cada asociación por separado. Pop incluso creará registros de las tablas asociativas para las asociaciones many_to_many.
Suponiendo las siguientes piezas de pseudocódigo:
book := Book{Title: "Pop Book", Description: "Pop Book", Isbn: "PB1"}
tx.Create(&book)
song := Song{Title: "Don't know the title"}
tx.Create(&song)
addr := Address{HouseNumber: 1, Street: "Golang"}
tx.Create(&addr)
user := User{
Name: "Mark Bates",
Books: Books{Book{ID: book.ID}},
FavoriteSong: song,
Houses: Addresses{
addr,
},
}
err := tx.Create(&user)
-
Notarás que
Bookses una asociacionhas_manyy se dará cuenta que para actualizar realmente cadaBook, primero necesitará tener elUser ID. Por lo tanto, procede a guardar los datos deUserpara que pueda recuperar el ID y lueo usar ese ID para llenar el campoUserIDen todos losBooks. Actualiza todos losBooksen la base de datos usando susIDs para orientarlos. -
FavoriteSonges una asociacionhas_oney usa la misma logica descrita en la asociaciónhas_many. Dado que los datos deUserse guardaron previamente antes de actuializar todos losBooksafectados, Se sabe que elUsertiene unID, por lo que llena su campoUserIDcon ese valor yFavoriteSongse actualiza en la base de datos. -
Housesen este ejemplo es una relacionmany_to_manyy se tendrá que tratar con dos tablas en este caso:usersyaddresses. Debido a queUserya estaba almacenado, ya se tiene suID. A continación, se usarán losID’s pasados con losAddressespara crear las entradas correcpondientes en la tabla asociativa.
Para una asociación belongs_to como se muestra en el siguiente ejemplo, llena su campo UserID antes de guardarse en la base de datos.
book := Book{
Title: "Pop Book",
Description: "Pop Book",
Isbn: "PB1",
User: user,
}
tx.Create(&book)
Creacion con Eager#
Pop tambien te permite crear modelos e integrar la creación de sus asociaciones en un solo paso
Suponiendo las siguientes piezas de pseudocódigo:
user := User{
Name: "Mark Bates",
Books: Books{{Title: "Pop Book", Description: "Pop Book", Isbn: "PB1"}},
FavoriteSong: Song{Title: "Don't know the title"},
Houses: Addresses{
Address{HouseNumber: 1, Street: "Golang"},
},
}
err := tx.Eager().Create(&user)
-
Notará que
Bookses una asociaciónhas_manyy se dará cuenta de que para almacenar cadaBook, primero necesitará obtener elUserID. Por lo tanto, procede primero a guardar/crear los datos deUserpara que pueda recuperar un ID y luego usar esa ID para rellenar el campoUserIDen cadaBookenBooks. Después guarda todos losBooksen la base de datos. -
FavoriteSonges una asociaciónhas_oney usa la misma lógica descrita en la asociaciónhas_many. Dado que los datos delUserse guardaron previamente antes de crear todos losBooks, se sabe que elUsertiene unID, por lo que llena su campoUserIDcon ese valor y luegoFavoriteSongse guarda en la base de datos. -
Housesen este ejemplo es una relaciónmany_to_manyy tendrá que tratar con dos tablas, en este caso:usersyaddresses. Primero deberá guardar todas lasAddressesen la tablaaddressesantes de guardarlas en la tabla asociativa. Debido a queUserya está guardado, ya tiene unID.- Este es un caso especial a tratar, ya que este comportamiento es diferente de todas las demás asociaciones, se soluciona implementando la interfaz
AssociationCreatableStatement, todas las demás asociaciones implementan por defecto la interfazAssociationCreatable.
- Este es un caso especial a tratar, ya que este comportamiento es diferente de todas las demás asociaciones, se soluciona implementando la interfaz
Para una asociación belongs_to como la que se muestra en el siguiente ejemplo, primero deberá crear User para recuperar su valor ID y luego rellenar su campo UserID antes de guardarlo en la base de datos.
book := Book{
Title: "Pop Book",
Description: "Pop Book",
Isbn: "PB1",
User: User{
Name: nulls.NewString("Larry"),
},
}
tx.Eager().Create(&book)
En caso de que alimentes la creación con Eager con modelos asociados que ya existen, en lugar de crear duplicados o actualizar su contenido, simplemente creará/actualizará las asociaciones con ellos.