Hace tiempo que manejo hibernate, pero no consigo dar con la tecla para arreglar el problema que se me presenta. Tengo una base de datos MySQL (impuesta por el cliente), que contienen 3 clases, una genérica de las otras dos. Como en MySQL no hay herencia, he hecho que hibernate lleve la cuenta de dicha unión. El problema se me presenta cuando intento borrar una de ellas. La inserción y la modificación van estupendas, no producen ningún problema. Os pongo el código a ver que os parece:
Tablas:
Código:
La tabla abstractaction es el padre del que heredarán action y delay en hibernate.CREATE TABLE `abstractaction` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `id_scene` bigint(20) NOT NULL, `new_value` float NOT NULL, `id_actionType` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `Action_FKIndex1` (`id_scene`), KEY `fk_AbstractAction_ActionType1` (`id_actionType`), CONSTRAINT `fk_AbstractAction_ActionType1` FOREIGN KEY (`id_actionType`) REFERENCES `actiontype` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `fk_B3B03319-9424-426C-B74C-BB07BAEE4B3D` FOREIGN KEY (`id_scene`) REFERENCES `scene` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8 COLLATE=utf8_bin PACK_KEYS=0; CREATE TABLE `action` ( `id` bigint(20) NOT NULL, `id_attributeVariable` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `fk_Action_AbstractAction1` (`id`), KEY `fk_Action_AttributeVariable1` (`id_attributeVariable`), CONSTRAINT `fk_Action_AbstractAction1` FOREIGN KEY (`id`) REFERENCES `abstractaction` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `fk_Action_AttributeVariable1` FOREIGN KEY (`id_attributeVariable`) REFERENCES `attributevariable` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; CREATE TABLE `delay` ( `id` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `fk_Delay_AbstractAction1` (`id`), CONSTRAINT `fk_Delay_AbstractAction1` FOREIGN KEY (`id`) REFERENCES `abstractaction` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
Por otra parte, la declaración de la configuración de hibernate es la siguiente:
Código:
Como veis, la clase Action y la clase Delay hace un joined-subclass para hacer lo antes mencionado.<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="xxx.hibernate.model.AbstractAction" table="abstractaction" catalog="atbuilding"> <id name="id" type="java.lang.Long"> <column name="id" /> <generator class="identity" /> </id> <property name="newValue" type="java.lang.Float"> <column name="new_value" not-null="true" /> </property> <many-to-one name="scene" class="xxx.hibernate.model.Scene" fetch="select"> <column name="id_scene" not-null="true" /> </many-to-one> <many-to-one name="actionType" class="xxx.hibernate.model.ActionType" fetch="select"> <column name="id_actionType" not-null="true" /> </many-to-one> <!-- Especificación de la clase Action --> <joined-subclass name="xxx.hibernate.model.Action" table="action"> <key column="id" /> <many-to-one name="attributeVariable" class="xxx.hibernate.model.AttributeVariable" fetch="select"> <column name="id_attributeVariable" not-null="true" /> </many-to-one> </joined-subclass> <!-- Especificación de la clase Delay --> <joined-subclass name="xxx.hibernate.model.Delay" table="delay"> <key column="id" /> </joined-subclass> </class> </hibernate-mapping>
La implementación de las clases no tiene ningún misterio y por tanto las voy a obviar.
Y en el caso de los DAO, tengo la siguiente estructura:
Código:
En este caso, he generado un HibernateDAO que, de manera genérica, genera todas las acciones principales (no las he copiado todas porque es bastante extensa). AbstractActionDAO hereda de ésta, y es abstracta para que sea necesario que las hijas la hereden, indicando la clase a la que pertenecen. public class ActionDAO extends AbstractActionDAO<Action> { public static final String ATTRIBUTE_VARIABLE = "attributeVariable"; public ActionDAO() { super(Action.class); } } public class DelayDAO extends AbstractActionDAO<Delay> { public static final String NEW_VALUE = "newValue"; public static final String SCENE = "scene"; public static final String ACTION_TYPE = "actionType"; public DelayDAO() { super(Delay.class); } } public abstract class AbstractActionDAO<T extends AbstractAction> extends HibernateDAO<T> { public static final String NEW_VALUE = "newValue"; public static final String SCENE = "scene"; public static final String RULE = "rule"; public static final String ACTION_TYPE = "actionType"; public AbstractActionDAO(Class<T> persistentClass) { super(persistentClass); } } public abstract class HibernateDAO<T extends ProjectModel>{ public final static String ID = "id"; protected final Class<T> persistentClass; public final String modelName; public HibernateDAO(Class<T> persistentClass) { if(persistentClass == null){ throw new RuntimeException("No persistent class specified."); } this.persistentClass = persistentClass; this.modelName = this.persistentClass.getSimpleName(); // Necesitamos abrir la sesión en caso de que no esté abierta. try { HibernateUtil.getSession(); } catch (ATBHibernateException e) { e.printStackTrace(); } } /** deletes a model */ public void delete(T model){ if (DEBUG) System.out.println("Borrar " + this.modelName + " #" + model.getId()); this.getSession().delete(model); } public void deleteAll(List<T> models) { Session session = this.getSession(); for (T model : models) { session.delete(model); } } public void insert(T model){ if (DEBUG) System.out.println("Insertar nuevo " + modelName + " (" + model + ")"); this.getSession().save(model); } public void update(T model){ try { this.getSession().evict(model); } catch (NullPointerException npe) { // When model is new instance } if (DEBUG) System.out.println("Modificar " + modelName + " #" + model.getId() ); this.getSession().update(model); } public void insertOrUpdate(T model){ try { this.getSession().evict(model); } catch (NullPointerException npe) { // When model is new instance } if (DEBUG) if(model.getId() == null){ System.out.println("Insertar (o modificar) nuevo " + modelName); }else{ System.out.println("Modificar (o insertar) " + modelName + " #" + model.getId()); } this.getSession().saveOrUpdate(model); } ....... }
Espero que no sea demasiado compliado de entender. Este esquema lo he probado en varios proyectos y no he tenido ningún problema con él, incluso con herencia (solo que la base de datos era Postgres que si soporta la herencia en un SGDB).
Bueno, solo me queda poner el error que se produce en el borrado:
Código:
Como veis, el error se produce cuando le indico a hibernate que persista los cambiso que he hecho, es decir, el borrado de la acción. El método de ejemplo que aparece en este caso es:org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:61) at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:46) at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:68) at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48) at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:237) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:146) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106) at xxx.hibernate.util.HibernateUtil.commitTransaction(HibernateUtil.java:173) at xxx.hibernate.Prueba.main(Prueba.java:28)
Código:
Espero que me ayudeis si podeis.public static void main(String[] args) { Delay delay = new DelayDAO().findById(35l); HibernateUtil.beginTransaction(); new DelayDAO().delete(delay); HibernateUtil.commitTransaction(); }
Un saludo, Squar.
PD: No tened miedo a poner cualquier chorrada que se os ocurra... quien sabe por donde anda la solución ;)