Chapter 9. Incremental Database Reverse Engineering (DBRE) Add-On

The incremental database reverse engineering (DBRE) add-on allows you to create an application tier of JPA 2.0 entities based on the tables in your database. DBRE will also incrementally maintain your application tier if you add or remove tables and columns.

9.1. Introduction

9.1.1. What are the benefits of Roo's incremental reverse engineering?

Traditional JPA reverse engineering tools are designed to introspect a database schema and produce a Java application tier once. Roo's incremental database reverse engineering feature differs because it has been designed to enable developers to repeatedly re-introspect a database schema and update their Java application. For example, consider if a column or table has been dropped from the database (or renamed). With Roo the re-introspection process would discover this and helpfully report errors in the Java tier wherever the now-missing field or entity was referenced. In simple terms, incremental database reverse engineering ensures Java type safety and easy application maintenance even if the database schema is constantly evolving. Just as importantly, Roo's incremental reverse engineering is implemented using the same unique design philosophy as the rest of Roo. This means very fast application delivery, clutter-free .java source files, extensive usability features in the shell (such as tab completion and hinting) and so on.

9.1.2. How does DBRE work?

9.1.2.1. Obtaining database metadata

The DBRE commands (see Section 9.3, “DBRE Add-On commands” below) make live connections to the database configured in your Roo project and obtain database metadata from the JDBC driver's implementation of the standard java.sql.DatabaseMetadata interface. When the database is reverse engineered, the metadata information is converted to XML and is stored and maintained in the dbre.xml file in the src/main/resources directory of your project. DBRE creates JPA entities based on the table names in your database and fields based on the column names in the tables. Simple and composite primary keys are supported (see Section 9.5.2, “Composite primary keys” for more details) and relationships between entities are also created using the imported and exported key information obtained from the metadata.

9.1.2.2. Class and field name creation

DBRE creates entity classes with names that are derived from the associated table name using a simple algorithm. If a table's name contains an underscore, hyphen, forward or back slash character, an upper case letter is substituted for each of these characters. This is also similar for column and field names. The following tables contain some examples.

Table nameDBRE-produced entity class name
orderOrder.java
line_itemLineItem.java
EAM_MEASUREMENT_DATA_1HEamMeasurementData1h.java
COM-FOO\BARComFooBar.java
Column nameDBRE-produced field name
orderorder
EMPLOYEE_NUMBERemployeeNumber
USR_CNTusrCnt

9.2. Installation

DBRE supports most of the relational databases that can be configured for Roo-managed projects such as MySQL, MS SQL, and PostgreSQL. These drivers are auto-detected by Roo and you will be prompted by the Roo shell to download your configured database's JDBC driver when you first issue the database introspect or database reverse engineer commands (see Section 9.3, “DBRE Add-On commands” below). For example, if you have configured your Roo project to use a MySQL database, when the database introspect command is first issued, you will see the following console output:

roo> database introspect --schema no-schema-required 
Located add-on that may offer this JDBC driver
1 found, sorted by rank; T = trusted developer; R = Roo 1.1 compatible
ID T R DESCRIPTION -------------------------------------------------------------
01 Y Y 5.1.13.0001 #jdbcdriver driverclass:com.mysql.jdbc.Driver. This...
--------------------------------------------------------------------------------
[HINT] use 'addon info id --searchResultId ..' to see details about a search result
[HINT] use 'addon install id --searchResultId ..' to install a specific search result, or
[HINT] use 'addon install bundle --bundleSymbolicName TAB' to install a specific add-on version
JDBC driver not available for 'com.mysql.jdbc.Driver'

You can get further information about the search result with the following command:

roo> addon info id --searchResultId 01

This may list several versions of a driver if available.

You can then install the latest MySQL JDBC driver by entering the following Roo command:

roo> addon install id --searchResultId 01

Alternatively, to install a different version (if available) of the driver you can use the following command:

roo> addon install bundle --bundleSymbolicName org.springframework.roo.wrapping.mysql-connector-java;<version>

The JDBC driver for MySQL is immediately available for you to use. You can now enter the database introspect and database reverse engineer commands (see Section 9.3, “DBRE Add-On commands” below).

Note: currently there are no open-source JDBC drivers for Oracle or DB2 and Roo does not provide OSGi drivers for these databases. If you are an Oracle or DB2 user, you will need to obtain an OSGi-enabled driver from Oracle or IBM respectively or wrap your own Oracle or DB2 driver jars using Roo's wrapping facility. Use the addon create wrapper to turn an existing Oracle JDBC driver into an OSGi bundle you can install into Roo. Roo does provide a wrapping pom.xml for the DB2 Express-C edition that can be used to convert your db2jcc4.jar into an OSGi-compliant driver. You can then use the osgi start command to install the jar, for example:

roo> osgi start --url file:///tmp/org.springframework.roo.wrapping.db2jcc4-9.7.2.0001.jar

9.3. DBRE Add-On commands

After you have configured your persistence layer with the jpa setup command and installed all the JDBC drivers, you can introspect and reverse engineer the database configured for your project. DBRE contains two commands:

  1. roo> database introspect --schema --file --enableViews

    This command displays the database structure, or schema, in XML format. The --schema is mandatory and for databases which support schemas, you can press tab to display a list of schemas from your database. You can use the --file option to save the information to the specified file.

    The --enableViews option when specified will also retrieve database views and display them with the table information.

    Notes:

    • The term "schema" is not used by all databases, such as MySQL and Firebird, and for these databases the target database name is contained in the JDBC URL connection string. However the --schema option is still required but Roo's tab assist feature will display "no-schema-required".

    • PostgreSQL upper case schema names are not supported.

  2. roo> database reverse engineer --schema --package --activeRecord --repository 
                                   --service --testAutomatically --enableViews 
                                   --includeTables --excludeTables 
                                   --includeNonPortableAttributes 
                                   --disableVersionFields --disableGeneratedIdentifiers 

    This command creates JPA entities in your project representing the tables and columns in your database. As for the database introspect command, the --schema option is required and tab assistance is available. You can use the --package option to specify a Java package where your entities will be created. If you do not specify the --package option on second and subsequent executions of the database reverse engineer command, new entities will be created in the same package as they were previously created in.

    Use the --activeRecord option to create 'Active Record' entities (default if not specified).

    Use the --repository option to create Spring Data JPA Repositories for each entity. If specified as true, the --activeRecord option is ignored.

    Use the --service option to create a service layer for each entity.

    Use the --testAutomatically option to create integration tests automatically for each new entity created by reverse engineering.

    The --enableViews option when specified will also retrieve database views and reverse engineer them into entities. Note that this option should only be used in specialised use cases only, such as those with database triggers.

    You can generate non-portable JPA @Column attributes, such as 'columnDefinition' by specifying the --includeNonPortableAttributes option.

    Use the --disableVersionFields option to disable the generation of 'version' fields.

    Use the --disableGeneratedIdentifiers option to disable auto generated identifiers.

    Since the DBRE Add-on provides incremental database reverse engineering, you can execute the command as many times as you want and your JPA entities will be maintained by Roo, that is, new fields will be added if new columns are added to a table, or fields will be removed if columns are deleted. Entities are also deleted in certain circumstances if their corresponding tables are dropped.

    Examples of the database reverse engineer command:

    • roo> database reverse engineer --schema order --package ~.domain --excludeTables "foo* bar?"

      This will reverse engineer all tables except any table whose name starts with 'foo' and any table called bar with one extra character, such as 'bar1' or 'bars'.

      You can use the --includeTables and --excludeTables option to specify tables that you want or do not want reverse engineered respectively. The options can take one or more table names. If more than one table is required, the tables must be enclosed in double quotes and each separated by a space. Wild-card searching is also permitted using the asterisk (*) character to match one or more characters or the '?' character to match exactly one character. For example:

      Note: excluding tables not only prevent entities from being created but associations are also not created in other entities. This is done to prevent compile errors in the source code.

    • roo> database reverse engineer --schema order --package ~.domain --includeTables "foo* bar?"

      This will reverse engineer all tables who table whose name starts with 'foo' and any table called bar with one extra character, such as 'bar1' or 'bars'.

    • You can also reverse engineer more than one schema by specifying a doubled-quoted space-separated list of schemas. Reverse engineering of foreign-key releationships between tables in different schemas is supported. For example:

      roo> database reverse engineer --schema "schema1 schema2 schema3" --package ~.domain

      This will reverse engineer all tables from schemas "schema1", "schema2", and "schema3".

9.4. The @RooDbManaged annotation

The @RooDbManaged annotation is added to all new entities created by executing the database reverse engineer command. Other Roo annotations, @RooJpaActiveRecord, @RooJavaBean, and @RooToString are also added to the entity class. The attribute "automaticallyDelete" is added to the @RooDbManaged annotation and is set to "true" so that Roo can delete the entity if the associated table has been dropped. However, if "automaticallyDelete" is set to "false", or if any annotations, fields, constructors, or methods have been added to the entity (i.e in the .java file), or if any of the Roo annotations are removed, the entity will not be deleted.

The presence of the @RooDbmanaged annotation on an entity class triggers the creation of an AspectJ inter-type declaration (ITD) ".aj" file where fields and their getters and setters are stored matching the columns in the table. For example, if an entity called Employee.java is created by the database reverse engineer command, a file called Employee_Roo_DbManaged.aj is also created and maintained by Roo. All the columns of the matching employee table will cause fields to be created in the entity's DbManaged ITD. An example of a DBRE-created entity is as follows:

@RooJavaBean
@RooToString
@RooDbManaged(automaticallyDelete = true)
@RooJpaActiveRecord(table = "employee", schema = "expenses")
public class Employee {
}

Along with the standard entity, toString, configurable ITDs, a DbManaged ITD is created if there are more columns in the employee table apart from a primary key column. For example, if the employee table has mandatory employee name and employee number columns, and a nullable age column the ITD could look like this:

privileged aspect Employee_Roo_DbManaged {
    
    @Column(name = "employee_number")
    @NotNull
    private String Employee.employeeNumber;
    
    public String Employee.getEmployeeNumber() {
        return this.employeeNumber;
    }
    
    public void Employee.setEmployeeNumber(String employeeNumber) {
        this.employeeNumber = employeeNumber;
    }

    @Column(name = "employee_name", length = "100")
    @NotNull
    private String Employee.employeeName;
    
    public String Employee.getEmployeeName() {
        return this.employeeName;
    }
    
    public void Employee.setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }

    @Column(name = "age")
    private Integer Employee.age;
    
    public Integer Employee.getAge() {
        return this.age;
    }
    
    public void Employee.setAge(Integer age) {
        this.age = age;
    }

    ...
}

If you do not want DBRE to manage your entity any more, you can "push-in" refactor the fields and methods in the DbManaged ITD and remove the @RooDbManaged annotation from the .java file.

9.5. Supported JPA 2.0 features

DBRE will produce and maintain primary key fields, including composite keys, entity relationships such as many-valued and single-valued associations, and other fields annotated with the JPA @Column annotation.

The following sections describe the features currently supported.

9.5.1. Simple primary keys

For a table with a single primary key column, DBRE causes an identifier field to be created in the entity ITD annotated with @Id and @Column. This is similar to executing the entity jpa command by itself.

9.5.2. Composite primary keys

For tables with two or more primary key columns, DBRE will create a primary key class annotated with @RooIdentifier(dbManaged = true) and add the "identifierType" attribute with the identifier class name to the @RooJpaActiveRecord annotation in the entity class. For example, a line_item table has two primary keys, line_item_id and order_id. DBRE will create the LineItem entity class and LineItemPK identifier class as follows:

@RooJavaBean
@RooToString
@RooDbManaged(automaticallyDelete = true)
@RooJpaActiveRecord(identifierType = LineItemPK.class, table = "line_item", schema = "order")
public class LineItem {
}
@RooIdentifier(dbManaged = true)
public class LineItemPK {
}

Roo will automatically create the JPA entity ITD containing a field annotated with @EmbeddedId with type LineItemPK as follows:

privileged aspect LineItem_Roo_JpaEntity {
    
    declare @type: LineItem: @Entity;
    
    declare @type: LineItem: @Table(name = "line_item", schema = "order");
    
    @EmbeddedId
    private LineItemPK LineItem.id;
    
    public LineItemPK LineItem.getId() {
        return this.id;
    }
    
    public void LineItem.setId(LineItemPK id) {
        this.id = id;
    }

    ...
}

and an identifier ITD for the LineItemPK class containing the primary key fields and the type annotation for @Embeddable, as follows:

privileged aspect LineItemPK_Roo_Identifier {
    
    declare @type: LineItemPK: @Embeddable;
    
    @Column(name = "line_item_id", nullable = false)
    private BigDecimal LineItemPK.lineItemId;
    
    @Column(name = "order_id", nullable = false)
    private BigDecimal LineItemPK.orderId;
    
    public LineItemPK.new(BigDecimal lineItemId, BigDecimal orderId) {
        super();
        this.lineItemId = lineItemId;
        this.orderId = orderId;
    }

    private LineItemPK.new() {
        super();
    }

    ...
}

If you decide that your table does not require a composite primary key anymore, the next time you execute the database reverse engineer command, Roo will automatically change the entity to use a single primary key and remove the identifier class if it is permitted.

9.5.3. Entity relationships

One of the powerful features of DBRE is its ability to create relationships between entities automatically based on the foreign key information in the dbre.xml file. The following sections describe the associations that can be created.

9.5.3.1. Many-valued associations with many-to-many multiplicity

Many-to-many associations are created if a join table is detected by DBRE. To be identified as a many-to-many join table, the table must have exactly two primary keys and have exactly two foreign-keys pointing to other entity tables and have no other columns.

For example, the database contains a product table and a supplier table. The database has been modelled such that a product can have many suppliers and a supplier can have many products. A join table called product_supplier also exists and links the two tables together by having a composite primary key made up of the product id and supplier id and foreign keys pointing to each of the primary keys of the product and supplier tables. DBRE will create a bi-directional many-to-many association. DBRE will designate which entities are the owning and inverse sides of the association respectively and annotate the fields accordingly as shown in the following code snippets:

privileged aspect Product_Roo_DbManaged {
    
    @ManyToMany
    @JoinTable(name = "product_supplier", 
        joinColumns = { 
            @JoinColumn(name = "prod_id") }, 
        inverseJoinColumns = { 
            @JoinColumn(name = "supp_id") })
    private Set<Supplier> Product.suppliers;

    ...
}

privileged aspect Supplier_Roo_DbManaged {
    
    @ManyToMany(mappedBy = "suppliers")
    private Set<Product> Supplier.products;

    ...
}

DBRE will also create many-to-many associations where the two tables each have composite primary keys. For example:

privileged aspect Foo_Roo_DbManaged {

    @ManyToMany
    @JoinTable(name = "foo_bar", 
        joinColumns = { 
            @JoinColumn(name = "foo_bar_id1", referencedColumnName = "foo_id1"), 
            @JoinColumn(name = "foo_bar_id2", referencedColumnName = "foo_id2") }, 
        inverseJoinColumns = { 
            @JoinColumn(name = "foo_bar_id1", referencedColumnName = "bar_id1"), 
            @JoinColumn(name = "foo_bar_id2", referencedColumnName = "bar_id2") })
    private Set<Bar> Foo.bars;

    ...
}

9.5.3.2. Single-valued associations to other entities that have one-to-one multiplicity

If the foreign key column represents the entire primary key (or the entire index) then the relationship between the tables will be one to one and a bi-directional one-to-one association is created.

For example, the database contains a customer table and an address table and a customer can only have one address. The following code snippets show the one-to-one mappings:

privileged aspect Address_Roo_DbManaged {
    
    @OneToOne
    @JoinColumn(name = "address_id")
    private Party Address.customer;

    ...
}

privileged aspect Customer_Roo_DbManaged {
    
    @OneToOne(mappedBy = "customer") 
    private Address Party.address;

    ...
}

9.5.3.3. Many-valued associations with one-to-many multiplicity

If the foreign key column is part of the primary key (or part of an index) then the relationship between the tables will be one to many. An example is shown below:

privileged aspect Order_Roo_DbManaged {
    
    @OneToMany(mappedBy = "order")
    private Set<LineItem> Order.lineItems;

    ...
}

9.5.3.4. Single-valued associations to other entities that have many-to-one multiplicity

When a one-to-many association is created, for example a set of LineItem entities in the Order entity in the example above, DBRE will also create a corresponding many-to-one association in the LineItem entity, as follows:

privileged aspect LineItem_Roo_DbManaged {
    
    @ManyToOne
    @JoinColumn(name = "order_id", referencedColumnName = "order_id")
    private Order LineItem.order;
    
    ...
}

9.5.3.5. Multiple associations in the same entity

DBRE will ensure field names are not duplicated. For example, if an entity has more than one association to another entity, the field names will be created with unique names. The following code snippet illustrates this:

privileged aspect Foo_Roo_DbManaged {

    @ManyToMany
    @JoinTable(name = "foo_bar", 
        joinColumns = { 
            @JoinColumn(name = "foo_bar_id1", referencedColumnName = "foo_id1"), 
            @JoinColumn(name = "foo_bar_id2", referencedColumnName = "foo_id2") }, 
        inverseJoinColumns = { 
            @JoinColumn(name = "foo_bar_id1", referencedColumnName = "bar_id1"), 
            @JoinColumn(name = "foo_bar_id2", referencedColumnName = "bar_id2") })
    private Set<Bar> Foo.bars;

    @ManyToMany
    @JoinTable(name = "foo_com", 
        joinColumns = { 
            @JoinColumn(name = "foo_com_id1", referencedColumnName = "foo_id1"), 
            @JoinColumn(name = "foo_com_id2", referencedColumnName = "foo_id2") }, 
        inverseJoinColumns = { 
            @JoinColumn(name = "foo_com_id1", referencedColumnName = "bar_id1"), 
            @JoinColumn(name = "foo_com_id2", referencedColumnName = "bar_id2") })
    private Set<Bar> Foo.bars1;

    ...
}

9.5.4. Other fields

DBRE will detect column types from the database metadata and create and maintain fields and field annotations appropriately. Strings, dates, booleans, numeric fields, CLOBs and BLOBs are all supported by DBRE, as well as the JSR 303 @NotNull validation constraint.

9.5.5. Existing fields

Roo checks the .java file for a field before it creates it in the ITD. If you code a field in the entity's .java file, Roo will not create the field in the DbManaged ITD if detected in the database metadata. For example, if your table has a column called 'name' and you have added a field called 'name' to the .java file, Roo will not create this field in the ITD when reverse engineered.

Roo also ensures the entity's identity field is unique. For example if the @Id field is called 'id' but you also add a field with the same name to the .java file, DBRE will automatically rename the @Id field by prefixing it with an underscore character.

9.6. Troubleshooting

This section explains scenarios that may be encountered when using the DBRE feature.

  • Executing the database introspect or database reverse engineer commands causes the message 'JDBC driver not available for oracle.jdbc.OracleDriver' to be displayed

    This is due to the Oracle JDBC driver not having been installed. The driver must be installed if you have installed Roo for the first time. See Section 9.2, “Installation”. This also applies to other databases, for example, HSQL and H2.

  • Executing the database introspect or database reverse engineer commands with the Firebird database configured causes the message 'Exception in thread "JLine Shell" java.lang.NoClassDefFoundError: javax/resource/ResourceException' to be displayed

    This is due to the javax.resource connector jar not installed. Remove the cache directory under your Roo installation directory, start the Roo shell, and run the command:

    osgi start --url
            http://spring-roo-repository.springsource.org/release/org/springframework/roo/wrapping/org.springframework.roo.wrapping.connector/1.0.0010/org.springframework.roo.wrapping.connector-1.0.0010.jar
            

    Re-install the Firebird driver. See Section 9.2, “Installation”.

  • The error message 'Caused by: org.hibernate.HibernateException: Missing sequence or table: hibernate_sequence' appears when starting Tomcat

    When the database reverse engineer command is first run, the property determining whether tables are created and dropped which is defined in the persistence.xml file is modified to a value that prevents new database artifacts from being created. This is done to avoid deleting the data in your tables when unit tests are run or a web application is started. For example, if you use Hibernate as your JPA 2.0 provider the property is called 'hibernate.hbm2ddl.auto' and is initially set to 'create' when the project is first created. This value causes Hibernate to create tables and sequences and allows you to run unit tests and start a web application. However, the property's value is changed to 'validate' when the database reverse engineer command is executed. Other JPA providers such as EclipseLink and OpenJPA have a similar property which are also changed when the command is run. If you see this issue when running unit tests or when starting your web application after reverse engineering, you may need to change the property back to 'create' or 'update'. Check your persistence.xml for the property values for other JPA providers.

  • The message 'Unable to maintain database-managed entity <entity name> because its associated table name could not be found' appears in the Roo console during reverse engineering

    When DBRE first creates an entity it puts in the table name in the 'table' attribute of the @RooJpaActiveRecord annotation. This is the only mechanism DBRE has for associating an entity with a table. If you remove the 'table' attribute, DBRE has no way of determining what the entity's corresponding table is and as a result cannot maintain the entity's fields and associations.