Transacciones y multiples AR

Hola Comunidad

Tengo un proceso que involucra varias tablas (varios ActiveRecord)

y quería usar transacciones para hacer el commit al final de todas las modificaciones y validaciones

pero encontré que eso no es posible con AR

http://www.yiiframework.com/forum/index.php?/topic/14082-transaction-on-multiple-ar/

encontré esto http://www.yiiframework.com/wiki/38/how-to-use-nested-db-transactions-mysql-5-pgsql/

pero tengo que modificar el core de yii, lo cual no se si quiero

la opción que si me permite hacer modificaciones en varias tablas esta en

http://www.yiiframework.com/doc/guide/1.1/en/database.dao

pero no usa AR

lo que estoy haciendo ahora es ejecutar el validate de cada modelo

algo así




$valido=true;

$valido = $valido && $model1->validate();

$valido = $valido && $model2->validate();

..


if ($valido) {

    $model1->save(false);

    $model2->save(false);

}



mi consulta es si alguien conoce/usa alguna otra técnica

saludos

Horacio

Algo como un maestro detalle? que tipo de relaciones tienes en tus tablas?

Podes! Si mal no entiendo:







$model1=Some::model();

$transaction=$model1->dbConnection->beginTransaction();

try

{

	$model2 = SomeOther::model();


	// Hace tus cosas


	$model1->save();

	$model2->save();




}

catch(Exception $e)

{

	$transaction->rollBack();

}






Probaste algo asi??

Falta el $transaction->commit(); despues de guardar los modelos. Tambien recuerda que para hacer transacciones debes tener el motor de tu base de datos tipo InnoDB al igual que las tablas.

Ciertisimo!!! Me olvide de esos "detalles".

Pido disculpas!

Te pongo un ejemplo de una transacción que implica 3 save de una aplicación que tengo (no lo hago con el try & catch):




public function actionCreateTarea() {

        /* Primero, creamos la tarea.

         * Segundo, creamos la acción referente al historial de la tarea con la creación realizada

         * y pasándole el id_tarea


        */


        if(isset($_POST['Tareas']))

        {

            if ($_POST['Tareas']['id_usuario']=='') {

                $_POST['Tareas']['id_usuario']=null;

            }

            

            $responce=new FormJson();


            $model=new Tareas();

            $transaction=$model->dbConnection->beginTransaction();

            $model->setScenario('create');

            $model->attributes=$_POST['Tareas'];            


            if($model->save(true)) {

                $id_tarea=$model->getPrimaryKey();

                

                /* 2.- creamos la acción referente al historial de la tarea con el cambio realizado

                 * y pasándole el id_tarea_backup

                 */    

                $model_ht=new HistorialTareas();

                //$transaction_ht=$model_ht->dbConnection->beginTransaction();

                $model_ht->setScenario('create');

                $model_ht->id_tarea=$id_tarea;

                $model_ht->id_usuario=Yii::app()->user->usuario_id_usuario;

                $model_ht->descripcion="Creación de la tarea.";

                

                 if($model_ht->save(true)) {

                    $responce->result="OK";

                    //$transaction_ht->commit();

                    $transaction->commit();

                } else {

                    $responce->result="KO";

                    $arr_errores=$model_ht->getErrors();

                    $transaction->rollBack();

                }               

                

            } else {

                $responce->result="KO";

                $arr_errores=$model->getErrors();

                $transaction->rollBack();

            }


            //Si no ha habido errores

            if ($responce->result=="OK") {

                $responce->rows[0]['descripcion']="Se ha creado el registro correctamente.";

            } else if ($responce->result=="KO") {  

                //Si ha habido errores

                $i=0;

                foreach ($arr_errores as $errores=>$error) {

                    foreach ($error as $key=>$value) {

                        $responce->rows[$i]['campo']=$errores;

                        $responce->rows[$i]['descripcion']=$value;

                    }

                    $i++;

                }

            }//Fin errores


            echo json_encode($responce); 

        }

    }



He dejado las líneas que tengo comentadas aposta para que veas que la transacción es a nivel global y una vez que la inicias sirve para todo lo que hagas en la acción y para todos los modelos que intervengan. Luego haces un commit o rollback y ya está.

Hola!

gracias por las respuestas, lo voy a probar

estaba de vacaciones!

saludos

Horacio

Funciona , creo

me quedan dudas respecto a transacciones en paralelo, creo que no hay problema, pero mejor si alguien me lo confirma

preguntas

[size="5"]A)[/size]

dos usuarios usando el sistema al mismo tiempo tienen dos conexiones a la base diferentes por lo que se inician dos transacciones diferentes

verdadero o falo?

[size="5"]

B )[/size]

el mismo usuario, en la misma sesión, (haciendo uso de pestañas en el navegador)

ejecuta en una pestaña un proceso (con transacciones) que demora XXX tiempo

y antes de que termine el proceso anterior, ejecuta otro proceso que tambien usa transacciones

[b]da error por querer iniciar dos transacciones?

crea una nueva transacción y son independientes?

[/b]

en otras palabras, inicia dos conexiones a la base o solo usa una conexión por sesión?

saludos, disculpas por este tipo de preguntas, y gracias!!!!

Horacio

1 Like

Si usas mysql, siempre podes consultar la documentacion:

Podes empezar por aca.

Si usas otra base de datos, te conviene sacarte esas dudas consultando la documentacion adecuada.

Desde Yii lo unico que haces en iniciar la transaccion y hacer commin o rollback.

En cuanto a lo que preguntas no creo que Yii tome medidas al respecto…

Hola Horacio,

sobre este tema decirte que cuando se hace una conexión a la BD Yii usa el pool (los datos) que se crea en el config.php. Eso significa que la transacción transcurre sobre el mismo "puntero" / la misma conexión a la bd mientras no se cierre la conexión. Si haces todas las operaciones con los diferentes AR en el mismo action (aunque llames a otros del mismo u otro controlador) estarás usando la misma conexión.

Cuando abres una transacción en Yii, se abre sobre un modelo, se abre la conexión. Si hay varios AR implicados se reutiliza la conexión y por tanto la transacción sigue existiendo.

Hice pruebas para comprobar que se realiza el rollback si falla algo de la transacción y lo hacía perfectamente, por eso te comento lo dicho.

Prueba a cambiar algo en la BD para que te de error el último AR y miras si te ha hecho el rollback correctamente.

En mi código llamo a las variables del modelo de forma diferente, no he probado a asígnar el mismo nombre de variable a los diferentes modelos…

Edito para añadir:

==================

al abrir la transacción se marca en el gestor de BD que se abre una transacción para la conexión que está abierta (tendrá un ID interno que le dará la BD al realizar el open()). Todo lo que se haga con esa conexión, se graba y al final se hace rollback o commit sobre todo eso. Si han intervenido múltiples AR no pasa nada porque tiene grabada la actividad desde el Begin al commit o rollback de la transacción. Es decir se tiene que mantener la conexión para realizar las operaciones, por eso te comentaba lo de realizarlo todo en el mismo action aunque llames a diferentes funciones.

Saludos.

Buenas noticias, creo

hice una prueba




public function actionTest1() {


	$model= new Patologia();


	$transaction=$model->dbConnection->beginTransaction();


	$model->nombre='XXXXXXX';

	$model->save();


	sleep(30); 


	$transaction->commit();

	throw new CHttpException(404, Yii::t('app', 'Fin test 1'));


}


public function actionTest2() {


	$model= new Patologia();


	$transaction=$model->dbConnection->beginTransaction();


	$model->nombre='YYYYYYYYY';

	$model->save();




	$transaction->rollBack();

	throw new CHttpException(404, Yii::t('app', 'Fin test 2'));


}



ejecute con la misma sesión (en dos pestañas separadas) , test1 y test2

cuando ejecute la accion test1, la pestaña queda "esperando" los 30 segundos del sleep

antes de que termine, ejecute la accion test2 y tambien quedo esperando

hasta que termino test1 y luego se ejecuto

el resultado: solo agrego XXXXXXX

otra prueba




public function actionTest3() {


	$model= new Patologia();

	$transaction=$model->dbConnection->beginTransaction();

	$model->nombre='WWWWWWW';

	$model->save();

	sleep(30);

	$transaction->rollBack();

	throw new CHttpException(404, Yii::t('app', 'Fin test 3'));


}


public function actionTest4() {


	$model= new Patologia();

	$transaction=$model->dbConnection->beginTransaction();

	$model->nombre='ZZZZZZ';

	$model->save();

	$transaction->commit();

	throw new CHttpException(404, Yii::t('app', 'Fin test 4'));


}



test3 y luego test4

test4 "espera" la terminación de test3 y luego finaliza

resultado inserta ZZZZZZ

mi conclusión

el metodo beginTransaction no se puede ejecutar en paralelo

pero no da error, sino que "espera" a que el activo termine y se ejecuta

otra prueba: una accion con transacion y otra con autocommit




public function actionTest5() {


	$model= new Patologia();

	$transaction=$model->dbConnection->beginTransaction();

	$model->nombre='AAAAAAAAAAA';

	$model->save();

	sleep(30);

	$transaction->rollBack();

	throw new CHttpException(404, Yii::t('app', 'Fin test 5'));


}


public function actionTest6() {


	$model= new Patologia();

	$model->nombre='BBBBBBBBBBB';

	$model->save();

	throw new CHttpException(404, Yii::t('app', 'Fin test 6'));


}



inserto BBBBB (luego de esperar la finalizacion de test5)




public function actionTest7() {


	$model= new Patologia();

	$model->nombre='CCCCCCCCCCCCCCC';

	sleep(30);

	$model->save();

	throw new CHttpException(404, Yii::t('app', 'Fin test 7'));


}


public function actionTest8() {


	$model= new Patologia();

	$transaction=$model->dbConnection->beginTransaction();

	$model->nombre='DDDDDDDDDDDDDDD';

	$model->save();

	$transaction->rollBack();

	throw new CHttpException(404, Yii::t('app', 'Fin test 8'));


}



inserto CCCCCC (la que espero fue test8)

otra conclusión? solo ejecuta de a una acción por vez?

Rta:No

luego probe con dos navegadores distintos y no "espera" ejecuta inmediatamente y los resultados son los mismos

Conclusión final

Si ejecuto en la misma sesión, solo se ejecuta una acción a la vez. por lo tanto. funciona perfecto

Si ejecuto en sesiones distintas, crea conexiones distintas y las transacciones funcionan ok

Espero estar en lo cierto y espero que esto ayude

saludos

Horacio

1 Like