Instalación de SQLAlchemy
Para instalar SQLAlchemy en Python, se utiliza pip. Dependiendo de la base de datos, se necesitan controladores adicionales.
pip install sqlalchemy
# Para PostgreSQL
pip install psycopg2-binary
# Para MySQL
pip install mysql-connector-python
# SQLite está incluido en la biblioteca estándar de Python.
Conceptos Clave
SQLAlchemy se basa en componentes esenciales:
- Motor (Engine): Gestiona la conexión con la base de datos.
- Sesión (Session): Controla las operaciones de persistencia.
- Modelo (Model): Clases que representan tablas en la base de datos.
- Consulta (Query): Objeto para construir y ejecutar consultas.
Conexión a la Base de Datos
Se crea un motor y una sesión para interactuar con la base de datos.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Ejemplo con SQLite
motor = create_engine('sqlite:///ejemplo.db', echo=True)
# Ejemplo con PostgreSQL
# motor = create_engine('postgresql://usuario:contraseña@localhost:5432/mi_base')
# Ejemplo con MySQL
# motor = create_engine('mysql+mysqlconnector://usuario:contraseña@localhost:3306/mi_base')
FabricaSesion = sessionmaker(autocommit=False, autoflush=False, bind=motor)
sesion = FabricaSesion()
Definición de Modelos de Datos
Los modelos se definen como clases que heredan de una base declarativa.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base
Base = declarative_base()
class Usuario(Base):
__tablename__ = 'usuarios'
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String(50), nullable=False)
correo = Column(String(100), unique=True, index=True)
publicaciones = relationship("Publicacion", back_populates="autor")
class Publicacion(Base):
__tablename__ = 'publicaciones'
id = Column(Integer, primary_key=True, index=True)
titulo = Column(String(100), nullable=False)
contenido = Column(String(500))
autor_id = Column(Integer, ForeignKey('usuarios.id'))
autor = relationship("Usuario", back_populates="publicaciones")
etiquetas = relationship("Etiqueta", secondary="publicacion_etiquetas", back_populates="publicaciones")
class Etiqueta(Base):
__tablename__ = 'etiquetas'
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String(30), unique=True, nullable=False)
publicaciones = relationship("Publicacion", secondary="publicacion_etiquetas", back_populates="etiquetas")
class PublicacionEtiqueta(Base):
__tablename__ = 'publicacion_etiquetas'
publicacion_id = Column(Integer, ForeignKey('publicaciones.id'), primary_key=True)
etiqueta_id = Column(Integer, ForeignKey('etiquetas.id'), primary_key=True)
Creación de Tablas
Las tablas se generan en la base de datos usando el metadatos del modelo.
Base.metadata.create_all(bind=motor)
# Para eliminar todas las tablas: Base.metadata.drop_all(bind=motor)
Operaciones CRUD Básicas
Crear Datos
# Crear un nuevo usuario
nuevo_usuario = Usuario(nombre="Ana", correo="ana@ejemplo.com")
sesion.add(nuevo_usuario)
sesion.commit()
# Creación en lote
sesion.add_all([
Usuario(nombre="Carlos", correo="carlos@ejemplo.com"),
Usuario(nombre="María", correo="maria@ejemplo.com")
])
sesion.commit()
Leer Datos
# Obtener todos los usuarios
usuarios = sesion.query(Usuario).all()
# Obtener el primer usuario
primer_usuario = sesion.query(Usuario).first()
# Obtener usuario por ID
usuario = sesion.query(Usuario).get(1)
Actualizar Datos
# Actualizar un usuario
usuario = sesion.query(Usuario).get(1)
usuario.nombre = "Ana García"
sesion.commit()
# Actualización en lote
sesion.query(Usuario).filter(Usuario.nombre.like("Ana%")).update({"nombre": "Ana G."}, synchronize_session=False)
sesion.commit()
Eliminar Datos
# Eliminar un usuario
usuario = sesion.query(Usuario).get(1)
sesion.delete(usuario)
sesion.commit()
# Eliminación en lote
sesion.query(Usuario).filter(Usuario.nombre == "Carlos").delete(synchronize_session=False)
sesion.commit()
Consultas Avanzadas
Consultas Básicas
# Obtener todos los registros
usuarios = sesion.query(Usuario).all()
# Obtener campos específicos
nombres = sesion.query(Usuario.nombre).all()
# Ordenar resultados
usuarios = sesion.query(Usuario).order_by(Usuario.nombre.desc()).all()
# Limitar y desplazar resultados
usuarios = sesion.query(Usuario).limit(10).all()
usuarios = sesion.query(Usuario).offset(5).limit(10).all()
Filtrado de Consultas
from sqlalchemy import or_
# Filtro por igualdad
usuario = sesion.query(Usuario).filter(Usuario.nombre == "Ana").first()
# Filtro con LIKE
usuarios = sesion.query(Usuario).filter(Usuario.nombre.like("Ana%")).all()
# Filtro con IN
usuarios = sesion.query(Usuario).filter(Usuario.nombre.in_(["Ana", "Carlos"])).all()
# Filtros múltiples
usuarios = sesion.query(Usuario).filter(
Usuario.nombre == "Ana",
Usuario.correo.like("%@ejemplo.com")
).all()
# Filtro con OR
usuarios = sesion.query(Usuario).filter(
or_(Usuario.nombre == "Ana", Usuario.nombre == "Carlos")
).all()
Consultas de Agregación
from sqlalchemy import func
# Contar registros
conteo = sesion.query(Usuario).count()
# Conteo agrupado
conteo_publicaciones = sesion.query(
Usuario.nombre,
func.count(Publicacion.id)
).join(Publicacion).group_by(Usuario.nombre).all()
# Promedio
id_promedio = sesion.query(func.avg(Usuario.id)).scalar()
Consultas de Unión
# Unión interna
resultados = sesion.query(Usuario, Publicacion).join(Publicacion).filter(Publicacion.titulo.like("%Python%")).all()
# Unión izquierda externa
resultados = sesion.query(Usuario, Publicacion).outerjoin(Publicacion).all()
Operaciones de Relaciones
# Crear objetos con relaciones
usuario = Usuario(nombre="Pedro", correo="pedro@ejemplo.com")
publicacion = Publicacion(titulo="Mi primer artículo", contenido="Hola Mundo!", autor=usuario)
sesion.add(publicacion)
sesion.commit()
# Acceder a relaciones
print(f"La publicación '{publicacion.titulo}' es de {publicacion.autor.nombre}")
# Operaciones con relaciones muchos a muchos
etiqueta_python = Etiqueta(nombre="Python")
etiqueta_sql = Etiqueta(nombre="SQL")
publicacion.etiquetas.append(etiqueta_python)
publicacion.etiquetas.append(etiqueta_sql)
sesion.commit()
Gestión de Transacciones
# Transacción con manejo de errores
try:
usuario = Usuario(nombre="Lucía", correo="lucia@ejemplo.com")
sesion.add(usuario)
sesion.commit()
except Exception as e:
sesion.rollback()
print(f"Error: {e}")
# Uso de context manager para sesiones
from contextlib import contextmanager
@contextmanager
def obtener_db():
db = FabricaSesion()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
finally:
db.close()
# Ejemplo de uso
with obtener_db() as db:
usuario = Usuario(nombre="Contexto", correo="contexto@ejemplo.com")
db.add(usuario)
Mejores Prácticas
- Gestoinar sesiones por solicitud y cerrarlas al finalizar.
- Manejar excepciones y realizar rollback cuando sea necesario.
- Evitar el problema N+1 usando carga ansiosa (eager loading).
- Configurar el pool de conexiones adecuadamente.
- Validar datos en el modelo o en la capa de aplicación.
Principios de Inversión de Control (IoC) e Inyección de Dependencias (DI) en Spring
En el desarrollo con Spring, IoC y DI son conceptos fundamentales para lograr código desacoplado y testeable.
Inversión de Control (IoC)
IoC es un principio de diseño donde el control de creación y gestión de objetos se transfiere a un contenedor externo. En lugar de que el código cree sus dependencias, un contenedor (como el IoC de Spring) se encarga de insatnciarlas y ensamblarlas.
// Ejemplo sin IoC: el código controla la creación
public class Coche {
private Motor motor;
public Coche() {
this.motor = new Motor(); // Control en el código
}
}
// Ejemplo con IoC: el contenedor controla la creación
public class Coche {
private Motor motor;
public Coche(Motor motor) {
this.motor = motor; // Control en el contenedor
}
}
Inyección de Dependencias (DI)
DI es una técnica para implementar IoC, donde el contenedor inyecta las dependencias en los componentes mediante constructores, métodos setter o campos.
// Inyección por constructor (recomendado)
@Component
public class Coche {
private final Motor motor;
@Autowired
public Coche(Motor motor) {
this.motor = motor;
}
}
// Inyección por setter
@Component
public class Coche {
private Motor motor;
@Autowired
public void setMotor(Motor motor) {
this.motor = motor;
}
}
// Inyección por campo (no recomendado)
@Component
public class Coche {
@Autowired
private Motor motor;
}
Comparación entre IoC y DI
| Característica | IoC | DI |
|---|---|---|
| Esencia | Principio de diseño | Patrón de diseño |
| Relación | Objetivo | Medio para lograr IoC |
| Enfoque | Tranfserencia del control al contenedor | Método para proporcionar dependencias |
| Alcance | Concepto más amplio | Subconjunto de IoC |
Spring utiliza DI como principal mecanismo para implementar IoC, facilitando la creación de aplicaciones modulares y mantenibles.