Is there a way to reverse engineer and existing database schema?

My data model is done and has been in production for years, I want to evaluate using Cuba-platform for CRUD on control tables.

Hi James,

indeed there is. The facility is there since the studio 2.1 release. You can read more about it in the official announcement and in my blog.

Bye,
Mario

Thanks for the quick reply. I will be evaluating this product this month. After half an hour it seems to hold much promise.

Hmmm.
The sequences used to populate the primary key do not follow my naming conventions and I can’t find a way to control the generation of the database object or the entity name for the sequences as I can in hibernate reveng.

For example:
CREATE TABLE UT_QUERY
( UT_QUERY_ID NUMBER(18,0) NOT NULL ENABLE,
QUERY_NM VARCHAR2(32) NOT NULL ENABLE,
QUERY_DESCR VARCHAR2(128),
SCHEMA VARCHAR2(30) NOT NULL ENABLE,
TABLE_NAME VARCHAR2(30) NOT NULL ENABLE,
QUERY_TEXT CLOB,
APEX_RPT_HEADING VARCHAR2(4000),
TABLE_ID VARCHAR2(8),
CONSTRAINT UT_QUERY_PK PRIMARY KEY (UT_QUERY_NBR),
CONSTRAINT UT_QUERY_UK UNIQUE (SCHEMA, TABLE_NAME, QUERY_NM),
CONSTRAINT UQ_UDL_FK FOREIGN KEY (SCHEMA, TABLE_NAME)
REFERENCES UT_DATA_LOCATION (SCHEMA, TABLE_NAME) ENABLE
);

create sequence ut_query_id_seq;

In CUBA, entity identifiers are assigned in very different way than in Hibernate or in JPA in general. But you can employ your sequence naming convention if you override the cuba_NumberIdWorker bean.

First, create a subclass in your core module:


package com.company.sample.core;

import com.haulmont.cuba.core.app.NumberIdWorker;
import com.haulmont.cuba.core.global.Metadata;
import javax.inject.Inject;

public class SampleNumberIdWorker extends NumberIdWorker {

    @Inject
    private Metadata metadata;

    @Override
    protected String getSequenceName(String entityName) {
        String table = metadata.getTools().getDatabaseTable(metadata.getClass(entityName));
        return table + "_id_seq";
    }
}

Then register your bean in spring.xml:


<beans ...>

    <bean id="cuba_NumberIdWorker" class="com.company.sample.core.SampleNumberIdWorker"/>

</beans>

I created the code


jjs@localhost.localdomain$ pwd
/common/home/jjs/studio-projects/ConditionIdentificationGUI/modules/core/src/org/javautil/conditionidentificationgui
jjs@localhost.localdomain$ cat SequenceIdWorker.java 

package org.javautil.conditionidentificationgui;

import com.haulmont.cuba.core.app.NumberIdWorker;
import com.haulmont.cuba.core.global.Metadata;
import javax.inject.Inject;

public class SequenceIdWorker extends NumberIdWorker {

    @Inject
    private Metadata metadata;

    @Override
    protected String getSequenceName(String entityName) {
        String table = metadata.getTools().getDatabaseTable(metadata.getClass(entityName));
        return table + "_id_seq";
    }
}

Modified the spring file


jjs@localhost.localdomain$ pwd
/common/home/jjs/studio-projects/ConditionIdentificationGUI/modules/core/src/org/javautil/conditionidentificationgui
jjs@localhost.localdomain$ cat spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <!-- Annotation-based beans -->
<context:component-scan base-package="org.javautil.conditionidentificationgui"></context:component>

    <bean id="cuba_NumberIdWorker" class="org.javautil.conditionidentificationgui.SequenceIdWorker"></bean>
</beans>

I have restarted studio,

did a build->clean build->assemble

I still get


jjs@localhost.localdomain$ pwd
/common/home/jjs/studio-projects/ConditionIdentificationGUI
jjs@localhost.localdomain$ cat modules/core/db/import/ConditionIdentificationGUI/UT_QUERY.sql 
-- Create a sequence for id column
create sequence seq_id_conditionidentificationgui_UtQuery increment by 100 start with 1 nocache minvalue 0;

Also there is no reason to increment by 100 and caching is ok.

The generated screen will only accept numbers in the query_nm field even though it is a text field (file is attached)

I can see no reason for the field being defined as numeric


/common/home/jjs/studio-projects/ConditionIdentificationGUI/modules/web/src/org/javautil/conditionidentificationgui/web/utquery
jjs@localhost.localdomain$ cat ut-query-edit.xml 
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
        caption="msg://editCaption"
        class="org.javautil.conditionidentificationgui.web.utquery.UtQueryEdit"
        datasource="utQueryDs"
        focusComponent="fieldGroup"
        messagesPack="org.javautil.conditionidentificationgui.web.utquery">
    <dsContext>
        <datasource id="utQueryDs"
                    class="org.javautil.conditionidentificationgui.entity.UtQuery"
                    view="_local"></datasource>
    </dsContext>
    <dialogMode height="600"
                width="800"></dialogMode>
    <layout expand="windowActions"
            spacing="true">
        <fieldGroup id="fieldGroup"
                    datasource="utQueryDs">
            <column width="250px">
                <field id="queryNm"></field>
                <field id="queryDescr"></field>
                <field id="tableName"></field>
                <field id="queryText"
                       rows="5"></field>
                <field id="table"></field>
                <field id="apexRptHeading"
                       rows="5"></field>
            </column>
        </fieldGroup>
        <frame id="windowActions"
               screen="editWindowActions"></frame>
    </layout>
</window>

UtQueryEditScreen

After some digging around I found UtQuery.java


package org.javautil.conditionidentificationgui.entity;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.AttributeOverrides;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import com.haulmont.cuba.core.global.DesignSupport;
import java.util.UUID;
import javax.persistence.Lob;
import com.haulmont.cuba.core.entity.BaseLongIdEntity;

@DesignSupport("{'imported':true}")
@AttributeOverrides({
        @AttributeOverride(name = "id", column = @Column(name = "UT_QUERY_NBR"))
})
@Table(name = "UT_QUERY")
@Entity(name = "conditionidentificationgui$UtQuery")
public class UtQuery extends BaseLongIdEntity {
    private static final long serialVersionUID = -2980351115666180398L;

    @Column(name = "QUERY_NM", nullable = false)
    protected UUID queryNm;

    @Column(name = "QUERY_DESCR", length = 128)
    protected String queryDescr;

    @Column(name = "TABLE_NAME", nullable = false, length = 30)
    protected String tableName;

    @Lob
    @Column(name = "QUERY_TEXT")
    protected String queryText;

    @Column(name = "TABLE_ID", length = 8)
    protected String table;

    @Lob
    @Column(name = "APEX_RPT_HEADING")
    protected String apexRptHeading;

    public void setQueryNm(UUID queryNm) {
        this.queryNm = queryNm;
    }

    public UUID getQueryNm() {
        return queryNm;
    }

    public void setQueryDescr(String queryDescr) {
        this.queryDescr = queryDescr;
    }

    public String getQueryDescr() {
        return queryDescr;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public String getTableName() {
        return tableName;
    }

    public void setQueryText(String queryText) {
        this.queryText = queryText;
    }

    public String getQueryText() {
        return queryText;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public String getTable() {
        return table;
    }

    public void setApexRptHeading(String apexRptHeading) {
        this.apexRptHeading = apexRptHeading;
    }

    public String getApexRptHeading() {
        return apexRptHeading;
    }
}

The table is defined as follows:


  CREATE TABLE UT_QUERY
   (	UT_QUERY_NBR NUMBER(18,0) NOT NULL ENABLE,
	QUERY_NM VARCHAR2(32) NOT NULL ENABLE,
	QUERY_DESCR VARCHAR2(128),
	SCHEMA VARCHAR2(30) NOT NULL ENABLE,
	TABLE_NAME VARCHAR2(30) NOT NULL ENABLE,
	QUERY_TEXT CLOB,
	APEX_RPT_HEADING VARCHAR2(4000),
	TABLE_ID VARCHAR2(8),
	 CONSTRAINT UT_QUERY_PK PRIMARY KEY (UT_QUERY_NBR), 
	 CONSTRAINT UT_QUERY_UK UNIQUE (SCHEMA, TABLE_NAME, QUERY_NM),
	 CONSTRAINT UQ_UDL_FK FOREIGN KEY (SCHEMA, TABLE_NAME)
	  REFERENCES UT_DATA_LOCATION (SCHEMA, TABLE_NAME) ENABLE
   ); 

I changed this Entity class to


jjs@localhost.localdomain$ cat ./modules/global/src/org/javautil/conditionidentificationgui/entity/UtQuery.java

package org.javautil.conditionidentificationgui.entity;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.AttributeOverrides;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import com.haulmont.cuba.core.global.DesignSupport;
import java.util.UUID;
import javax.persistence.Lob;
import com.haulmont.cuba.core.entity.BaseLongIdEntity;

@DesignSupport("{'imported':true}")
@AttributeOverrides({
        @AttributeOverride(name = "id", column = @Column(name = "UT_QUERY_NBR"))
})
@Table(name = "UT_QUERY")
@Entity(name = "conditionidentificationgui$UtQuery")
public class UtQuery extends BaseLongIdEntity {
    private static final long serialVersionUID = -2980351115666180398L;

	/* jjs hand patch */
    @Column(name = "QUERY_NM", nullable = false, unique = true, length = 32)
    protected String queryNm;

    @Column(name = "QUERY_DESCR", length = 128)
    protected String queryDescr;

    @Column(name = "TABLE_NAME", nullable = false, length = 30)
    protected String tableName;

    @Lob
    @Column(name = "QUERY_TEXT")
    protected String queryText;

    @Column(name = "TABLE_ID", length = 8)
    protected String table;

    @Lob
    @Column(name = "APEX_RPT_HEADING")
    protected String apexRptHeading;

    public void setQueryNm(String queryNm) {
        this.queryNm = queryNm;
    }

    public String getQueryNm() {
        return queryNm;
    }

    public void setQueryDescr(String queryDescr) {
        this.queryDescr = queryDescr;
    }

    public String getQueryDescr() {
        return queryDescr;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public String getTableName() {
        return tableName;
    }

    public void setQueryText(String queryText) {
        this.queryText = queryText;
    }

    public String getQueryText() {
        return queryText;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public String getTable() {
        return table;
    }

    public void setApexRptHeading(String apexRptHeading) {
        this.apexRptHeading = apexRptHeading;
    }

    public String getApexRptHeading() {
        return apexRptHeading;
    }


}

And the code now works.
What caused the natural key to be defined as a UUID?

What caused the natural key to be defined as a UUID?

It’s a coincidence - we store UUID keys in Oracle as VARCHAR2(32), so the model generator thinks it should be a UUID. We’ll fix it, see https://youtrack.cuba-platform.com/issue/STUDIO-3099

Any documentation on how to tune the reverse engineering would be appreciated, it may exist and I missed it.

The example I received in this thread was helpful, but there are still massive holes in my understanding of the process.

Thanks for the quick reply.

There is no standalone documentation on this feature at the moment, but you can take a look at the context help in Studio - it’s actually a minimal step-by-step guide to the process.

:ticket: See the following issue in our bug tracker:

https://youtrack.cuba-platform.com/issue/STUDIO-3099