Hibernate – table par hiérarchie de classe avec un discriminant de type entier
Hibernate permet de faire du mapping d’héritage de classe. Selon la stratégie utilisée, le développeur peut rencontrer quelques difficultés.
Hibernate propose trois stratégies de mapping d’héritage de classe :
- une table par hiérarchie de classe
- une table par classe fille
- une table par classe concrète
L’objectif ici n’est pas d’expliquer la différence entre chaque solution, mais seulement de présenter un problème qu’il est possible de rencontrer lorsque la stratégie une table par hiérarchie de classe est utilisée avec un discriminant de type entier.
En prenant le cas d’une table listant des employés avec une colonne spécifiant le type d’employé, le fichier de mapping peut ressembler à ceci :
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.company.project.dto"> <class name="Employee" table="EMPLOYEE"> <id name="Id" type="int"> <column name="E_ID" sql-type="int(4)" /> <generator class="increment" /> </id> <discriminator column="E_TYPE" type="integer" /> <property name="Name" type="string"> <column name="E_NAME" sql-type="char(20)" not-null="false" /> </property> <subclass name="Manager" discriminator-value="1" /> <subclass name="Salesman" discriminator-value="2" /> <subclass name="Accountant" discriminator-value="3" /> <subclass name="Secretary" discriminator-value="4" /> </class> </hibernate-mapping>
Or, lors de la consultation d’enregistrements présents en base de données, l’exception suivante est levée :
org.hibernate.MappingException: Could not format discriminator value to SQL string at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:284) at org.hibernate.persister.PersisterFactory.createClassPersister(PersisterFactory.java:55) at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:215) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1164) at com.company.project.HibernateUtil.currentSession(HibernateUtil.java:40) at com.company.project.dao.EmployeeDAO.displayEmployee(EmployeeDAO.java:162) at EmployeeTest.testDisplay(EmployeeTest.java:25) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:324) at junit.framework.TestCase.runTest(TestCase.java:154) at junit.framework.TestCase.runBare(TestCase.java:127) at junit.framework.TestResult$1.protect(TestResult.java:106) at junit.framework.TestResult.runProtected(TestResult.java:124) at junit.framework.TestResult.run(TestResult.java:109) at junit.framework.TestCase.run(TestCase.java:118) at junit.framework.TestSuite.runTest(TestSuite.java:208) at junit.framework.TestSuite.run(TestSuite.java:203) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196) Caused by: java.lang.NumberFormatException: For input string: "com.company.project.dto.Employee" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48) at java.lang.Integer.parseInt(Integer.java:468) at java.lang.Integer.<init>(Integer.java:609) at org.hibernate.type.IntegerType.stringToObject(IntegerType.java:55) at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:277) ... 21 more
Afin d’initialiser le discriminant, Hibernate tente de charger un entier qui a pour valeur com.company.project.dto.Employee. Cette valeur est étrange. Afin de palier au problème, il est possible de modifier le type du discriminant et de le passer en chaîne de caractères. Ce qui donne le fichier de mapping suivant :
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.company.project.dto"> <class name="Employee" table="EMPLOYEE"> <id name="Id" type="int"> <column name="E_ID" sql-type="int(4)" /> <generator class="increment" /> </id> <discriminator column="E_TYPE" type="string" /> <property name="Name" type="string"> <column name="E_NAME" sql-type="char(20)" not-null="false" /> </property> <subclass name="Manager" discriminator-value="1" /> <subclass name="Salesman" discriminator-value="2" /> <subclass name="Accountant" discriminator-value="3" /> <subclass name="Secretary" discriminator-value="4" /> </class> </hibernate-mapping>
La consultation des enregistrements se déroule correctement. Par contre, il est impossible de créer un nouvel enregistrement. En effet lors de l’enregistrement d’un nouvel objet, l’exception suivante est levée :
org.hibernate.exception.SQLGrammarException: could not insert: [com.company.project.dto.Salesman] at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:65) at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2078) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2427) at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:51) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:243) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:227) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:140) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:296) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1007) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:354) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106) at com.company.project.dao.EmployeeDAO.createSalesman(EmployeeDAO.java:136) at EmployeeTest.testCreateSalesman(EmployeeTest.java:82) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:324) at junit.framework.TestCase.runTest(TestCase.java:154) at junit.framework.TestCase.runBare(TestCase.java:127) at junit.framework.TestResult$1.protect(TestResult.java:106) at junit.framework.TestResult.runProtected(TestResult.java:124) at junit.framework.TestResult.run(TestResult.java:109) at junit.framework.TestCase.run(TestCase.java:118) at junit.framework.TestSuite.runTest(TestSuite.java:208) at junit.framework.TestSuite.run(TestSuite.java:203) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196) Caused by: com.sybase.jdbc2.jdbc.SybSQLException: Implicit conversion from datatype 'VARCHAR' to 'SMALLINT' is not allowed. Use the CONVERT function to run this query. at com.sybase.jdbc2.tds.Tds.processEed(Tds.java:2636) at com.sybase.jdbc2.tds.Tds.nextResult(Tds.java:1996) at com.sybase.jdbc2.jdbc.ResultGetter.nextResult(ResultGetter.java:69) at com.sybase.jdbc2.jdbc.SybStatement.nextResult(SybStatement.java:204) at com.sybase.jdbc2.jdbc.SybStatement.nextResult(SybStatement.java:187) at com.sybase.jdbc2.jdbc.SybStatement.updateLoop(SybStatement.java:1615) at com.sybase.jdbc2.jdbc.SybStatement.executeUpdate(SybStatement.java:1598) at com.sybase.jdbc2.jdbc.SybPreparedStatement.executeUpdate(SybPreparedStatement.java:89) at craftsman.spy.SpyPreparedStatement.executeUpdate(SpyPreparedStatement.java:219) at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:23) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2062) ... 27 more
La base de données ne veut pas convertir la chaîne de caractères fournie pour la colonne du discriminant en entier. Il est possible de modifier le modèle de la base de données en changant le type du discriminant. Mais ce genre d’opération n’est pas toujours possible.
La solution est la suivante. Il faut conserver le type entier pour le discriminant car la base de données n’acceptera rien d’autre. Avec un discrimimant de type entier, Hibernate charge des objets dont les discriminants ont pour valeur le nom de la classe mère. Pour résoudre ce problème, il suffit de spécifier une valeur de discriminant pour la classe mère. Le fichier de mapping ressemblera donc à :
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.company.project.dto"> <class name="Employee" table="EMPLOYEE" discriminator-value="0"> <id name="Id" type="int"> <column name="E_ID" sql-type="int(4)" /> <generator class="increment" /> </id> <discriminator column="E_TYPE" type="integer" /> <property name="Name" type="string"> <column name="E_NAME" sql-type="char(20)" not-null="false" /> </property> <subclass name="Manager" discriminator-value="1" /> <subclass name="Salesman" discriminator-value="2" /> <subclass name="Accountant" discriminator-value="3" /> <subclass name="Secretary" discriminator-value="4" /> </class> </hibernate-mapping>
Même si si cette valeur de discriminant pour la classe mère n’est jamais utilisé (et donc jamais présent en base de données), il faut procéder de la sorte.
Est-ce la bonne méthode ou est-ce un bug d’Hibernate ? Dans tous les cas ce mapping fonctionne très bientôt !
https://blog.lecacheur.com/2006/02/16/hibernate-table-par-hierarchie-de-classe-avec-un-discriminant-de-type-entier/HibernateHibernate permet de faire du mapping d'héritage de classe. Selon la stratégie utilisée, le développeur peut rencontrer quelques difficultés. Hibernate propose trois stratégies de mapping d'héritage de classe : une table par hiérarchie de classe une table par classe fille une table par classe concrète L'objectif ici n'est pas d'expliquer la différence entre chaque...SeBSébastien LECACHEUR23r0@laposte.netAdministratorLe weblogue de SeB
je veux savoir comment avec hibernate (java) je pourrais afficher les tables de la base de données sur laquelle je travaille??
merci d’avance
Bonjour,
Vous souhaitez afficher tous les tables dynamiquement ou bien une table en particulier ?
Car si c’est une table en particulier, il suffit d’utiliser le mapping de cette table et de faire un simple list() sans ajouter de contrainte.
En espérant avoir bien compris votre problème.
je veux savoir comment en peut faire un consultation (Recherche dans une Table exsiste à une base de donnée appartir d’un numeroc ou bien code donnee)
Les réponses 1 et 2 n’ont rien à voir avec le sujet de cet article.
Si vous saviez chercher, vous auriez trouvé pleins de sites qui parlent de ca :
http://julien.coron.free.fr/languages/Java/Hibernate/
http://www.bourzeix.com/weblog/post/2005/06/09/122-netbeans-une-application-web-hibernate
Ensuite, il faut adapter votre hibernate.cfg.xml à la base de données que vous utilisez (en modifiant le champ xml ).
Tout d’abord, je pense qu’un langage cordial serait plus apprécié que ce genre de formulation : « Si vous saviez chercher ».
Ensuite, je pense que vous faite allusion aux deux premiers commentaires. J’ai répondu à la question. Il faut donc supposer que j’accepte ce genre d’intervention (qui je le concède est plus ou moins hors-sujet).
Enfin, si vous estimez que ces commentaires n’ont rien à voir avec l’article, alors pourquoi relancer un sujet à priori clos depuis près d’un an ?
En espérant que ce n’était qu’une simple erreur de jugement passagère. 😉