Uso de SQLAlchemy ORM y Principios de IoC/DI en el Desarrollo de Aplicaciones

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.

Etiquetas: SQLAlchemy ORM Python Spring IOC

Publicado el 6-28 02:19