-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 
Author Message
 Post subject: Generating abstract domain class from jdbc
PostPosted: Sat Nov 22, 2008 8:00 pm 
Newbie

Joined: Tue Nov 11, 2008 12:36 am
Posts: 14
The folks in my project have decided to generate domain classes from the database. I would like to use Hibernate Tools ant task to generate an abstract domain class with protected settings. I'll then extend this to make a concrete implemented with certain setters public and added business logic. That way, if new fields are added to the database, I can regenerate the abstract domain class and not affect my hand-code business logic. See examples below. I'd like generate a mapping file to use the concrete class.

How would I do this without writing a reveng.xml section for each class? Can I do this with a custom reverse engineering strategy? Would I need to use custom templates. Or possibly a combination? Can I do this without building the Hibernate Tools source? Thanks for your help.

Code:
//
// abstract domain class generated from database schema by Hibernate Tools
//
package org.Domain

public abstract Class AbstractDomain
{
   private int value;

   public AbstractDomain( int value )
   {
      this.value = value;
   }

   protected void setValue( int value )
   {
      this.value = value;
   }

   public int getValue()
   {
      return this.value;
   }
}



Code:
//
// concrete domain class implemented by hand
//
package org.Domain

public Class Domain extends AbstractDomain
{
   public Domain( int Value )
   {
      super( value );
   }

   //
   // business code
   //
   public void setValue( int value )
   {
      if( 13 == value )
      {
         throw IllegalArgumentException("13 is bad luck!");
      }
      super.setValue( value );
   }
}

_________________
This signature left intentionally blank.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 24, 2008 1:57 am 
Newbie

Joined: Tue Nov 11, 2008 12:36 am
Posts: 14
I've continued to work on this issue. After some googling, I found an example from this forum on how to apply the scope-class attribute in the reveng file. See below.

Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering PUBLIC "-//Hibernate/Hibernate Reverse Engineering DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd">
<hibernate-reverse-engineering>
  <schema-selection match-catalog="mmerch"/>
  <table-filter match-name="a_p_i_keys">
   <!-- force abstract scope for generated classes -->
   <meta attribute="scope-class">public abstract</meta>
  </table-filter>
</hibernate-reverse-engineering>


That allowed me to create an abstract class. However, I wanted to prefix the classname with "Abstract", so I could use the base classname as my implementation class. So I wrote a reverse strategy to attempt to do this.


Code:
package tribal.tool;

import org.hibernate.cfg.reveng.DelegatingReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.ReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.TableIdentifier;

/**
*
* @author Ezward
*/
public class TribalDomainStrategy extends DelegatingReverseEngineeringStrategy
{
   public TribalDomainStrategy(ReverseEngineeringStrategy delegate)
   {
      //
      // remember our delegate.  We will override some methods and
      // allow delegate to handle the rest.
      //
      super(delegate);
   }

   /**
    * return the class name given the table name
    *
    * Our strategy produces abstract classes, so prefix the standard
    * name with 'Abstract'
    *
    * @param tableIdentifier name of the table
    * @return name of the corresponding domain class
    */
   @Override
   public String tableToClassName(TableIdentifier tableIdentifier) {
      String theClassName = super.tableToClassName(tableIdentifier);
      
      //
      // theClassName has form package.ClassName, so make it package.AbstractClassName
      int index = theClassName.lastIndexOf('.');
      return theClassName.substring(0, index) + "Abstract" + theClassName.substring(index + 1);
   }

   /*@Override
   public Map tableToMetaAttributes(TableIdentifier tableIdentifier) {
      Map theMetaAttributes = super.tableToMetaAttributes( tableIdentifier );
      if( theMetaAttributes == null ) theMetaAttributes = new HashMap<String, MetaAttribute>();

      //
      // force abstract meta attribute unless there is already a class scope
      //
      if( ! theMetaAttributes.containsKey("scope-class"))
      {
         theMetaAttributes.put("scope-class", new MetaAttribute("public abstract"));
      }

      return theMetaAttributes;
   }*/
}


Note that the tableToMetaAttributes() method is commented out; more on that later.

When I changed my build file to use this, it did not seem to get called. here is the relavent build section;

Code:
   <!-- let ant know about the hibernatetool task -->
   <taskdef name="hibernatetool"
             classname="org.hibernate.tool.ant.HibernateToolTask" />

    <!-- pre-compile task to regenrate domain classes and mapping files -->
   <target name="-pre-compile">
   </target>

    <!-- generate the domain classes and mapping files -->
    <target name="generate-domain-from-db"
            description="run the hibernate task to generate domain classes and mapping files" >
        <!-- run the hibernate task to generate domain classes and mapping files -->
        <hibernatetool destdir="src">
            <!-- classpath to hibernate mappings and reveng files -->
            <classpath>
                <path location="src/tribal/domain"/>
            </classpath>

            <!-- we are using jdbc to read configuration from database -->
            <jdbcconfiguration
                propertyfile="src/tribal/domain/hibernate.properties"
                revengfile="src/tribal/domain/hibernate.reveng.xml"
                packagename="tribal.domain"
                detectmanytomany="true"
            reversestrategy="tribal.tool.TribalDomainStrategy"
            />

         <!-- order of generators is important -->
            <!-- we are generating the hbm.xml files -->
            <hbm2hbmxml destdir="src" />

            <!-- we are generating the java files -->
            <hbm2java jdk5="true" />

            <!-- we are generating the hibernate config file -->
            <hbm2cfgxml destdir="src/tribal/domain" />
        </hibernatetool>
    </target>


When I took out the meta tag from the table-filter in the reveng file, then the strategy seemed to get called, but it created an error. here is the updated reveng file, followed by the resulting error when the build is attempted:

Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering PUBLIC "-//Hibernate/Hibernate Reverse Engineering DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd">
<hibernate-reverse-engineering>
  <schema-selection match-catalog="mmerch"/>
  <table-filter match-name="a_p_i_keys">
  </table-filter>
</hibernate-reverse-engineering>


The errors:

Code:
generate-domain-from-db:
Executing Hibernate Tool with a JDBC Configuration (for reverse engineering)
1. task: hbm2hbmxml (Generates a set of hbm.xml files)
An exception occurred while running exporter #2:hbm2hbmxml (Generates a set of hbm.xml files)
To get the full stack trace run ant with -verbose
org.hibernate.tool.hbm2x.ExporterException: Error while processing Entity: Abstracttribal.domain.APIKeys with template hbm/hibernate-mapping.hbm.ftl
freemarker.core.InvalidReferenceException: Expression metaattributable.getMetaAttributes().get(key).values is undefined on line 3, column 9 in hbm/meta.hbm.ftl.
org.hibernate.tool.hbm2x.ExporterException: Error while processing Entity: Abstracttribal.domain.APIKeys with template hbm/hibernate-mapping.hbm.ftl
        at org.hibernate.tool.hbm2x.TemplateHelper.processTemplate(TemplateHelper.java:261)
        at org.hibernate.tool.hbm2x.TemplateProducer.produceToString(TemplateProducer.java:67)
        at org.hibernate.tool.hbm2x.TemplateProducer.produce(TemplateProducer.java:28)
        at org.hibernate.tool.hbm2x.TemplateProducer.produce(TemplateProducer.java:103)
        at org.hibernate.tool.hbm2x.GenericExporter.exportPOJO(GenericExporter.java:148)
        at org.hibernate.tool.hbm2x.GenericExporter.exportPersistentClass(GenericExporter.java:137)
        at org.hibernate.tool.hbm2x.GenericExporter$2.process(GenericExporter.java:43)

... snip ...


So, if I have the meta tag in the reveng file, it generates without error and I get an abstract class, but the name is not changed. If I remove the meta tag, I get the error. Any clues?

Note that the error refers to the entity "Abstracttribal.domain.APIKeys". I'm not sure what is up there. It seems to have prefixed the class with "Abstract", so that is why it can't process it.

Back to the tableToMetaAttributes() that is commented out. I thought this was the problem, so i commented it out. I'm not sure if I am allocating the correct kind of map. It would be great if someone could point me to a good example of implementing this method. I could use this rather than add the meta mapping to every file in the reveng file. Also, it seems like I may have to add this if I don't have it in the reveng file (is that the error?)

Thank you for your help.


[/code]

_________________
This signature left intentionally blank.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 24, 2008 2:15 am 
Newbie

Joined: Tue Nov 11, 2008 12:36 am
Posts: 14
You always find the issue just after you hit the return key!

I had a project to build a jar file with the TribalDomainStrategy class in it and I had added it to the ant path. it was an older version that did the wrong thing with name - it just prefixed the classname with "Abstract" without regard for the package name. When I removed that jar and added the updated one, it seemed to work. So now I have a project that builds the TribalDomainStrategy class and puts it in a jar file. I added that jar file to my ant class path and the error goes way and everything seems to work.

Now I still need help with the meta attribute so I can set the class-scope without putting it in the reveng file.. Thanks very much.

_________________
This signature left intentionally blank.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 24, 2008 6:56 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
No need to set scope explicitly just set <meta attribute="generated-class">AbstractDomainClassName</meta> then it will be done automatically for you.

About your tableToMetaAttributes that looks correct - is it not being picked up ?

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 24, 2008 7:12 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
btw. if you want to generate the specific classes in one go you would need to run a custom set of templates which just generate the 3-4 lines of class declaration.

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 24, 2008 4:19 pm 
Newbie

Joined: Tue Nov 11, 2008 12:36 am
Posts: 14
The meta attribute stuff as written below does not work.

Code:
   @Override
   public Map tableToMetaAttributes(TableIdentifier tableIdentifier) {
      Map theMetaAttributes = super.tableToMetaAttributes( tableIdentifier );
      if( theMetaAttributes == null ) theMetaAttributes = new HashMap<String, MetaAttribute>();

      //
      // force abstract meta attribute unless there is already a class scope
      //
      if( ! theMetaAttributes.containsKey("scope-class"))
      {
         theMetaAttributes.put("scope-class", new MetaAttribute("public abstract"));
      }

      return theMetaAttributes;
   }


I also wrote a version where I commented out the if, thinking that I may need to overwrite any pre-existing scope-class attribute, but that did not work either. Any ideas?

_________________
This signature left intentionally blank.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 24, 2008 7:59 pm 
Newbie

Joined: Tue Nov 11, 2008 12:36 am
Posts: 14
max wrote:
No need to set scope explicitly just set <meta attribute="generated-class">AbstractDomainClassName</meta> then it will be done automatically for you.

About your tableToMetaAttributes that looks correct - is it not being picked up ?


I went back to the meta attribute stuff in the reverse strategy class with your suggestion in mind. I finally figured out the correct MetaAttribute collection idiom to make them work. Below I have copied the full reverse strategy class. This class generates an abstract domain class and maps a concrete class. I am not generating the concrete class, but I believe I could using your suggestion of using a custom template. I think I could also spin up another reverse strategy class that chose which setters to override in an intelligent way. Specifically, I would want to override setters that take a value by reference type and have the setter throw an IllegalArgumentException if it is null. I'll work on that later.

Here is the full reverse strategy:

Code:
/*
* A class to provide custom reverse engineering strategy for the
* Hibernate Tools.  This is used in the ant file within the
* Hibernate Tools' configuration tab.  For example (only the
* relevent bits are shown);
*
* <hibernatetool destdir="src">
*     <jdbcconfiguration
*         propertyfile="src/tribal/domain/hibernate.properties"
*         revengfile="src/tribal/domain/hibernate.reveng.xml"
*         packagename="tribal.domain"
*         detectmanytomany="true"
*         reversestrategy="tribal.tool.TribalDomainStrategy"
*     />
* </hibernatetool>
*/

package tribal.tool;

import java.util.HashMap;
import java.util.Map;
import org.hibernate.cfg.reveng.DelegatingReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.ReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.TableIdentifier;
import org.hibernate.mapping.MetaAttribute;

/**
*
* @author Ezward
*/
public class TribalDomainStrategy extends DelegatingReverseEngineeringStrategy
{
   public TribalDomainStrategy(ReverseEngineeringStrategy delegate)
   {
      //
      // remember our delegate.  We will override some methods and
      // allow delegate to handle the rest.
      //
      super(delegate);
   }

   private String tableToAbstractClassName(TableIdentifier tableIdentifier)
   {
      String theClassName = super.tableToClassName(tableIdentifier);

      //
      // the default classname has form package.ClassName, so make it package.AbstractClassName
      //
      int index = theClassName.lastIndexOf('.');
      return theClassName.substring(0, index + 1) + "Abstract" + theClassName.substring(index + 1);
   }

   @Override
   public Map tableToMetaAttributes(TableIdentifier tableIdentifier) {
      Map theMetaAttributes = super.tableToMetaAttributes( tableIdentifier );
      if( theMetaAttributes == null ) theMetaAttributes = new HashMap<String, MetaAttribute>();

      //
      // force abstract meta attribute unless there is already a class scope
      //
      if( ! theMetaAttributes.containsKey("scope-class") )
      {
         //
         // make the generated java use an abstract class
         //
         MetaAttribute theMetaAttribute = new MetaAttribute("scope-class");
         theMetaAttribute.addValue("public abstract");

         theMetaAttributes.put(theMetaAttribute.getName(), theMetaAttribute);
      }
      
      //
      // add meta data to tell mapping file to generate to our abstract class.
      // Note we will still map the concrete class, since we don't override
      // the name that get's put in the mapping file by tableToClassName().
      //
      if( ! theMetaAttributes.containsKey("generated-class") )
      {
         //
         // make the generated java have Abstract name.
         //
         MetaAttribute theMetaAttribute = new MetaAttribute("generated-class");
         theMetaAttribute.addValue(tableToAbstractClassName(tableIdentifier));

         theMetaAttributes.put(theMetaAttribute.getName(), theMetaAttribute);
      }


      return theMetaAttributes;
   }
}


I modified the reverse engineering file so that it excludes a few tables and includes all the rest, so it is shorter and I don't have to keep adding entries if I add a table (since I want most tables to have automatic generation).

Code:
<!DOCTYPE hibernate-reverse-engineering PUBLIC "-//Hibernate/Hibernate Reverse Engineering DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd">
<hibernate-reverse-engineering>
  <schema-selection match-catalog="mmerch"/>

  <!-- these tables or procedures should not be generated -->
  <table-filter match-name="messageprocessing_list" exclude="true" />
  <table-filter match-name="messageprocessing_priority" exclude="true" />
  <table-filter match-name="message_processing_priority" exclude="true" />
  <table-filter match-name="add_message_to_conversation" exclude="true" />
  <table-filter match-name="get_customer_id_from_merchant_and_ref" exclude="true" />
  <table-filter match-name="process_next_message_begin" exclude="true" />
  <table-filter match-name="process_next_message_finalize" exclude="true" />
  <table-filter match-name="reenque_messages_not_finalized" exclude="true" />

  <!-- generate everything else classes -->
  <table-filter match-name=".*">
   <!-- add any global meta attributes for tables here -->
  </table-filter>
 
</hibernate-reverse-engineering>


The generated class looks like this:

Code:
package tribal.domain;
// Generated Nov 24, 2008 3:31:56 PM by Hibernate Tools 3.2.1.GA

/**
* AbstractAPIKeys generated by hbm2java
*/
public abstract class AbstractAPIKeys  implements java.io.Serializable {

... private fields, public accessors ...

}


The generated mapping file looks like this:

Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Nov 24, 2008 3:31:54 PM by Hibernate Tools 3.2.1.GA -->
<hibernate-mapping>
    <class name="tribal.domain.APIKeys" table="a_p_i_keys" catalog="mmerch">
        <meta attribute="generated-class" inherit="false">tribal.domain.AbstractAPIKeys</meta>
        <meta attribute="scope-class" inherit="false">public abstract</meta>

--- field mappings ---

</hibernate-mapping>



So, I think this looks the way I want. Now for the JUnit tests.

Thank you for your help Max.

_________________
This signature left intentionally blank.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Nov 25, 2008 10:20 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
If you get it all working let me know and if you got some working templates for it we could integrate this as a part of the tooling ;)

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Sat Nov 29, 2008 5:56 pm 
Newbie

Joined: Tue Nov 11, 2008 12:36 am
Posts: 14
max wrote:
If you get it all working let me know and if you got some working templates for it we could integrate this as a part of the tooling ;)


I got it all working the way I want with the reverse strategy in my previous post. I already had some concrete domain classes, so I started with them. So I did not make any template changes.

Now I think I might actually do some template changes to add some convenience methods for one-to-many and many-to-many relationships. What I have been doing (by hand) in my concrete classes is to implement methods of the form:

Code:
public void addRelated( Related theRelatedObject )
{
    // add the related object to our collection
    this.getRelateds().add( theRelatedObject );

    // complete the reciprocal relationship
    theObject.getInverseRelated().add(this);
}


In some cases where the join table has it's own data, the convenience method news up a join object and sets it into both collections. I also add removeRelated() and hasRelated(). I think I can get these into the templates so they are generated in the abstract base class.

The idea is to complete the reciprocal relationships so that my business logic does not have to be aware of which end of the relationship is marked as inverse in the mapping file.

The other thing I am doing over and over is to override the setters for the relationship collections and throw if they are called. In effect, these collections should be final. Rather than do that, I can make the setters protected.

By putting this stuff either in the reverse strategy or in the templates, I can get this stuff to happen for all my tables without having to tweak each mapping file (which I'm generating anyway, so that is out of the question).

I have not messed with the templates before. Do you know of any good examples?

_________________
This signature left intentionally blank.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Dec 01, 2008 6:29 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
Quote:
emurmur wrote:
max wrote:
If you get it all working let me know and if you got some working templates for it we could integrate this as a part of the tooling ;)


I got it all working the way I want with the reverse strategy in my previous post. I already had some concrete domain classes, so I started with them. So I did not make any template changes.


cool.

Quote:
Now I think I might actually do some template changes to add some convenience methods for one-to-many and many-to-many relationships. What I have been doing (by hand) in my concrete classes is to implement methods of the form:

Code:
public void addRelated( Related theRelatedObject )
{
    // add the related object to our collection
    this.getRelateds().add( theRelatedObject );

    // complete the reciprocal relationship
    theObject.getInverseRelated().add(this);
}



Something i've wanted to add for a while ;)

Quote:
In some cases where the join table has it's own data, the convenience method news up a join object and sets it into both collections.


Could you show and example of it ?

Quote:
I also add removeRelated() and hasRelated(). I think I can get these into the templates so they are generated in the abstract base class.


Yes, just need to do them dependent on which side is inverse and if it should actually be bidirecitonally to know how the sequence of updates should be done.

Quote:
The other thing I am doing over and over is to override the setters for the relationship collections and throw if they are called.
[/qoute]

You mean for field based properties you dont want setters called or ?

Quote:
I have not messed with the templates before. Do you know of any good examples?


There is an example in the hibernate tools documentation, see http://docs.jboss.org/tools/3.0.0.Beta1 ... ml#d0e5042

Let me know if it does not answer your question.

_________________
Max
Don't forget to rate


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.