Posible excepción en modificación de BD (integridad)
Tengo una duda sobre cómo mantener la integridad de una base de datos. Si yo realizo una actualización (o algo similar) en la que modifico 3 datos, y entre la ejecución de esas 3 sentencias "prepStat.executeUpdate();" se produce una excepción, las cuales ya he puesto una a continuación de la otra con el fin de disminuir esa posibilidad, resulta que la base de datos puede verse modificada en función de las sentencias que se ejecuten (una o dos), pero yo quiero que lo haga con las 3 o con ninguna. Yo de momento lo que he hecho ha sido ejecutar esas tres sentencias seguidas para disminuir la probabilidad de que eso ocurra, pero quizá es algo que sólo "alivia" el problema. También podría ocurrir una excepción después de la ejecución de esas 3 sentencias pero antes de retornar a la página jsp, por lo que el problema seguiría siendo el mismo, aunque en ese caso muestro inmediatamente después un mensaje informando del éxito de la operación para que al menos el usuario sepa que su operación se realizó bien. ¿Cómo debo actuar? ¿Qué debo hacer?
Trabajo con Struts
1 Respuesta
Respuesta
1
1
Anónimo
Lo que debes hacer es: 1. Poner el autoCommit de tus conexiones a false 2. Ejecutar todas las sentencias dentro de una misma transacción. Si todo va bien al final harás un commit, y si algo falla harás un rollback que haga o deshaga todos los cambios y así puedas mantener tu integridad de datos. Ejemplo (no lo he compilado): Connection conn = null; try { conn = ... // obtienes una conexión conn.setAutoCommit( false ); // deshabilitas el autoCommit // aquí haces todos los cambios en la BD conn.commit(); // todo ha ido bien => commit } catch (SQLException sqle) { conn.rollback(); // algo ha fallado => rollback } finally { // liberar recursos (cerrar conexión, statement...) } Espero que te sirva. Suerte.
Gracias por tu pronta respuesta En cuanto lo pruebe, finalizo la pregunta... con nota! :) Un saludo
No me ha funcionado, pero antes de pasarte el código, hay 2 cosas que no entiendo. La primera: ¿A qué llamas una misma transacción?, es decir, ¿cómo la identificas viendo el código? ¿Quizá una transacción equivale a un bloque try?, ¿O al bloque de sentencias que hay entre la obtención de la conexión y su cierre? No lo tengo claro. La segunda: por qué se hace un rollback por si algo falla y deshacer lo hecho, si hasta que no ejecutamos commit precisamente no se hace. Quiero decir que si el commit es la última sentencia del bloque try, cualquier SQLException se producirá antes, por lo que hacer un rollback en el bloque catch que deshaga lo hecho no tiene sentido porque previamente me he asegurado de que no se materialice nada! ¿Me entiendes? :) Vamos con las razones por las que no me ha funcionado. Te paso el código de una función que se encarga de actualizar los datos de un usuario en función de lo que este solicite a través de un formulario. Le doy la posibilidad de modificar su contraseña, su pregunta secreta y su respuesta secreta, y puede modificar 3 datos, 1 dato, ninguno... lo que él desee. // Registra las modificaciones efectuadas por el usuario en sus datos de registro public UsuarioVO actualizarUsuario(String idUsuario, String pwd, String pregSecr, String respSecr) { Connection conn = null; PreparedStatement prepStat = null; ResultSet resSet = null; UsuarioVO usuario = new UsuarioVO(null, null, null, null); // Lo uso para actualizar lo datos en sesión try { conn = MySQLDAOFactory.getConnection(); conn.setAutoCommit(false); // Componemos la sentencia SQL String query = "SELECT contrasena, preg_secreta, resp_secreta FROM usuario WHERE (id_usuario = ?)"; prepStat = conn.prepareStatement(query); // Pasamos los parámetros a la sentencia prepStat.setString(1, idUsuario); // Ejecutamos la sentencia y obtenemos el resultado resSet = prepStat.executeQuery(); resSet.next(); String contrasena = resSet.getString("contrasena"); String preg_secreta = resSet.getString("preg_secreta"); String resp_secreta = resSet.getString("resp_secreta"); if (!(contrasena.equals(pwd))) { // El usuario modificó su contraseña y procedemos a actualizarla // Componemos la sentencia SQL query = "UPDATE usuario SET contrasena = ? WHERE (id_usuario = ?)"; prepStat = conn.prepareStatement(query); // Pasamos los parámetros a la sentencia prepStat.setString(1, pwd); prepStat.setString(2, idUsuario); // Ejecutamos la sentencia, actualizando la contraseña prepStat.executeUpdate(); usuario.setPwd(pwd); // La contraseña quedará actualizada en sesión } if (!(preg_secreta.equals(pregSecr))) { // El usuario modificó su pregunta secreta y procedemos a actualizarla // Componemos la sentencia SQL query = "UPDATE usuario SET preg_secreta = ? WHERE (id_usuario = ?)"; prepStat = conn.prepareStatement(query); // Pasamos los parámetros a la sentencia prepStat.setString(1, pregSecr); prepStat.setString(2, idUsuario); // Ejecutamos la sentencia, actualizando la pregunta secreta prepStat.executeUpdate(); usuario.setPregSecr(pregSecr); // La pregunta secreta quedará actualizada en sesión } if (!(resp_secreta.equals(respSecr))) { // El usuario modificó su respuesta secreta y procedemos a actualizarla // Componemos la sentencia SQL query = "UPDATE usuario SET resp_secreta = ? WHERE (id_usuario = ?)"; prepStat = conn.prepareStatement(query); // Pasamos los parámetros a la sentencia prepStat.setString(1, respSecr); prepStat.setString(2, idUsuario); // Ejecutamos la sentencia, actualizando la respuesta secreta prepStat.executeUpdate(); usuario.setRespSecr(respSecr); // La respuesta secreta quedará actualizada en sesión } throw new SQLException(); // Fuerzo una excepción para comprobar que no sé modifica la BD // Materializamos los cambios en la base de datos //conn.commit(); } catch (SQLException e) { /* Fallo irrecuperable. Lanzo "RuntimeException" Deshago cualquier cambio realizado en la base de datos */ try { conn.rollback(); } catch (SQLException e2) { /* Si lanzo otra RuntimeException no se ejecuta el resto del método y además se pierde la excepción lanzada con anterioridad donde se informa de un error de acceso a la base de datos. Sólo capturo */ } throw new RuntimeException("Se ha producido un error en el acceso a la base de datos", e); } finally { MySQLDAOFactory.closeAll(conn, prepStat); } //return usuario; } El problema está en que forzando la excepción antes del commit, la BD en teoría no debería modificarse pero sin embargo sí que se me modifica. Si hay algo del código que no entiendas pregúntamelo. Un saludo y gracias
Estoy un poco liado ahora mismo, luego te lo miro con más calma. El código en si tiene buena pinta. Estoy seguro de que el setAutoCommit(false) con el commit final y el rollback si falla es lo que hay que hacer. ¿Podrías poner un breakpoint justo antes de forzar la excepción (para que pare la ejecución de tu código), e irte a la BD y hacer una consulta a ver si los datos están cambiados?
Haré lo que me dices y te comentaré
Hola He colocado el breakpoint como me has dicho, pero no me ha aportado nada que no supiera. Incluso he colocado alguno más, y puedo decirte que dado el código que te pasé, en cada "prepStat.executeUpdate()" se actualiza el campo correspondiente de la base de datos. Funciona de igual manera que si no estuviese el "conn.setAutocommit(false)" ¿? Un saludo
Pues espérate, que igual estamos olvidando una cosa que se me acaba de ocurrir :) MySQL en las versiones viejas no soportaba transacciones... ¿Qué versión utilizas tú? En caso de utilizar una versión nueva (4 o 5), asegurate de que estás utilizando tablas de tipo InnoDB en lugar de MyISAM ! Creo que podría ser esto. A ver si hay suerte.
Probaré, porque creo que la tengo establecida en MyISAM aunque la versión es actual (no recuerdo cual, pero creo que la 5). Espero que no me surjan otros problemas por cambiarla!, porque si no me equivoco la puse así por un tema del "autoincrement" Un saludo
Hazte una copia de seguridad antes por si las moscas :). El autoincrement debería funcionar en todas las versiones.
Ya tengo una copia de seguridad, pero gracias por el aviso. Voy a finalizar la pregunta para puntuarte y comenzar otra, ¿vale? Mis temores se han cumplido