Transacciones en Laravel (Insertar modelos relacionados)

Spread the love

Las transacciones en Laravel son muy útiles cuando queremos que las operaciones de actualización o de inserción de datos se realicen como una sola unidad.

Es decir que si algo falla durante esas operaciones se deshacen todos los cambios para evitar que se pierdan datos.

veamos esto con un ejemplo:

En donde debemos insertar datos en tablas relacionadas con Eloquent de Laravel

class EmployeePositionController extends Controller
{

    public function __invoke(Request $request)
    {
        try {
            $employee = Employee::create([
                'name' => $request->employee_name,
                'email' => $request->employee_email
            ]);

            $position = Position::create([
                'name' => $request->position,
            ]);

            $position->employees()->save($employee);

        } catch (\Exception $e) {
            return response()->json(['message' => 'Error']);
        }

        return response()->json(['message' => 'Success']);

    }
}

En nuestro ejemplo tenemos dos operaciones de inserción que corresponden al modelo Employee y Position después de crearse los dos modelos, se relaciona el empleado con la posición en la ultima operación.

En la mayoría de los casos los datos se guardaran de forma correcta, pero si existe un fallo aunque tenemos un try/catch es probable que la base de datos pierda información, ya que no se realizaran todas las operaciones para el modelo Employee y Position.

Para evitar esto, podemos usar las transacciones y guardar datos en dos tablas diferentes(Employees y Positions) y en Laravel es muy sencillo emplearlas.

Pero antes demos un repasos breve sobre las transacciones de base de datos.

¿No quieres leer?

Para estos casos tenemos la versión en video:

¿Qué son las transacciones?

Las transacciones en SQL son unidades o secuencias de trabajo realizadas de forma ordenada y separada en una base de datos. Normalmente representan cualquier cambio en la base de datos, y tienen dos objetivos principales:

  • Proporcionar secuencias de trabajo fiables que permitan poder recuperarse fácilmente ante errorres y mantener una base de datos consistente incluso frente a fallos del sistema.
  • Proporcionar aislamiento entre programas accediendo a la vez a la base de datos.

Una transacción es una propagación de uno o más cambios en la base de datos, ya sea cuando se crea, se modifica o se elimina un registro. En la práctica suele consistir en la agrupación de consultas SQL y su ejecución como parte de una transacción.

Propiedades de las transacciones

Las transacciones siguen cuatro propiedades básicas, bajo el acrónimo ACID (Atomicity, Consistency, Isolation, Durability):

  • Atomicidad: aseguran que todas las operaciones dentro de la secuencia de trabajo se completen satisfactoriamente. Si no es así, la transacción se abandona en el punto del error y las operaciones previas retroceden a su estado inicial.
  • Consistencia: aseguran que la base de datos cambie estados en una transacción exitosa.
  • Aislamiento: permiten que las operaciones sean aisladas y transparentes unas de otras.
  • Durabilidad: aseguran que el resultado o efecto de una transacción completada permanezca en caso de error del sistema.

Control de las transacciones

Existen tres comandos básicos de control en las transacciones SQL:

  • COMMIT. Para guardar los cambios.
  • ROLLBACK. Para abandonar la transacción y deshacer los cambios que se hubieran hecho en la transacción.
  • SAVEPOINT. Crea checkpoints, puntos concretos en la transacción donde poder deshacer la transacción hasta esos puntos.

Los comandos de control de transacciones se usan sólo con INSERTDELETE y UPDATENo pueden utilizarse creando tablas o vaciándolas porque las operaciones se guardan automáticamente en la base de datos.

Cómo implementar las transacciones en laravel.

En este articulo veremos las formas mas comunes de implementar transacciones en Laravel.

Transacciones usando Transaction

La forma mas sencilla de aplicar transacciones en nuestro código, es usando el Facade DB::transaction() que agrega una transacción y hace el rollback por nosotros. Pero al final envía una excepción que tenemos que manejar de alguna forma.

Para nuestro ejemplo lo hare en el controlador y vamos a guardar datos en dos tablas diferentes usando Laravel Eloquent.

class EmployeePositionController extends Controller
{

    public function __invoke(Request $request)
    {
        try {
            DB::transaction(function() use ($request) {
                
            $employee = Employee::create([
                'name' => $request->employee_name,
                'email' => $request->employee_email
            ]);

            $position = Position::create([
                'name' => $request->position,
            ]);

            $position->employees()->save($employee);
            });

        } catch (\Exception $e) {
            return response()->json(['message' => 'Error']);
        }

        return response()->json(['message' => 'Success']);

    }
}

Observa que el método transaction maneja el commit y el rollback de forma automática por nosotros, de esta forma lo único que tenemos que hacer es manejar la excepción.

Manejo de bloqueos

Es común que en el uso de transacciones tengas casos en las que mas de una transacción necesite afectar los mismo datos, esto genera que los registros relacionados a esas operaciones queden bloqueados.

Para resolver esto, el método transaction acepta un segundo argumento que te permite pasar un numero de intentos antes de que la transacción falle sin realizar los cambios.

DB::transaction(function() {
  //Código
}, 2);

En el ejemplo la transacción intentara completarse en dos intentos, si falla entonces lanzara una excepción.

El método transaction tiene una debilidad y es que maneja el rollback por nosotros, dejando fuera la opción de realizar operaciones adicionales antes de aplicar el rollback.

Así que para esos casos podemos agregar las transacciones de forma explicita

Transacciones de forma manual

Para indicar de forma manual una transacción usamos igual el Facade DB pero ahora indicamos el inicio y fin de nuestra transacción y tenemos que manejar de forma manual al rollback:

class EmployeePositionController extends Controller
{

    public function __invoke(Request $request)
    {
        try {
            DB::beginTransaction();

            $employee = Employee::create([
                'name' => $request->employee_name,
                'email' => $request->employee_email
            ]);

            $position = Position::create([
                'name' => $request->position,
            ]);

            $position->employees()->save($employee);

            DB::commit();


        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['message' => 'Error']);
        }

        return response()->json(['message' => 'Success']);

    }
}

Transacción usando after commit

Para finalizar existen algunos casos en los que requerimos que se realice una tarea solo después de completar el commit.

Para estos casos se agrego recientemente en Laravel, el método afterCommit en el Facade DB

try {
            DB::beginTransaction();

            $employee = Employee::create([
                'name' => $request->employee_name,
                'email' => $request->employee_email
            ]);

            DB::afterCommit(function() use($employee){
                Mail::to($employee)->send(new EmployeeHired);
            });

            $position = Position::create([
                'name' => $request->position,
            ]);

            $position->employees()->save($employee);

            DB::commit();


        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['message' => 'Error']);
        }

En nuestro ejemplo solo se enviara el correo si se realiza el commit con éxito.

Puedes ver un ejemplo detallado de este caso, en este video.

Conclusión

Como puedes observar agregar transacciones en Laravel es muy sencillo y tenemos dos opciones que puedes usar dependiendo de las necesidades que tengas.

¿Te gusto el articulo?

Entonces no olvides compartirlo en tus redes sociales!