Tutorial 2: Adding Join Relationships

Abstract

Learn how to add join relationships to an Ad Hoc report and data service in order to export columns from an entire data model in Pull Reports™.


This tutorial expands on the Pull Reports™ catalog configuration concepts introduced in Tutorial 1 by enlarging the report data model with <relationship> configuration. Relationships join additional database tables to the report's base table in a infinitely deep join tree.

Relationships in Pull Reports™ are of three types:

  • to many, <join_column>

  • to one, <join_column>

  • to many, <join_table>

Each type is a directional inner or left outer relational join from the relationship parent table to the relationship child table. The three types are differentiated by the cardinality of the relationship, one or many, and whether the foreign key column(s) are present on the relationship tables or on a many-to-many join table.

This tutorial contains an example of each type of relationship plus an advanced example in which a child table joins to an ancestor table. The tutorial gives example configuration as Pull Reports™ XML Catalog files but also includes a comprehensive example in the Catalog Configuration Java API.

For additional relationship configuration examples, see the <relationship> documentation.

to many, <join_column>

The catalog configuration from Tutorial 1 allows a user to query and export the columns of the base, student table. To expand our example data model, will add a table called assignment containing student assignments records. The assignment table has an assignment.student_id column which is a foreign key to student.id. In this way, one student record may join to many assignment records and the relationship is "to-many" from student to assignment.

Here is the new UML with the additional assignment table:

You may wonder why the student to assignment relationship is described as "to-many" instead of the more common "one-to-many". This is because Pull Reports™ relational joins are always uni-directional from parent to child within the report join tree, so the parent cardinality is unimportant.

To add the assignment table to our report data model, add a child <relationship> element to the base table which itself contains the child, assignment <table>. Because this is a to-many relationship in which the foreign key column is on the child table, a <join_column> element specifies the foreign key column, assignment.student_id, and referenced column, student.id, which participate in the join.

When defining the relationship, we choose not to specify the <relationship> cardinality and join attributes but accept their default values of many and left respectively. The cardinality of many is appropriate because the relationship is to-many from student to assignment. The join value of left ensures that all rows from the student table will be included in the Export Report REST API export result regardless of whether a student row has any referencing assignment records.

Here is our new Pull Reports™ XML Catalog file which includes the assignment relationship table:

<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://www.pullreports.com/catalog-1.6.1" id="class" name="Class Reports"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.pullreports.com/catalog-1.6.1
    https://www.pullreports.com/docs/xsd/pullreports-catalog-1.6.1.xsd">
    <report id="student-information" name="Student Information">
        <export_config defaultColumns='firstn,lastn'/>
        <table id="student" name="class_info.student" displayName="Student" primaryKeyColumns="id">
            <column id="id" name="id" displayName="Student ID" paramType="java.lang.Integer" export="false"/>
            <column id="firstn" name="first_name" displayName="First Name"/>
            <column id="lastn" name="last_name" displayName="Last Name"/>
            <column id="height_in" name="height_inches" displayName="Height (in)" paramType="java.lang.Double"/>
            <column id="bdate" name="birth_date" displayName="Birth Date" paramType="java.sql.Date"/>
            <column id="active" name="is_active" displayName="Active?" 
                paramType="java.lang.Boolean" export="false"/>
            <pre_filter>@active=true</pre_filter>
            <relationship>
                <join_column columnName="student_id" referencedColumnName="id" />
                <table id="assignment" displayName="Assignment" name="class_info.assignment">
                    <column id="id" name="id" displayName="Assignment ID" paramType="java.lang.Integer"/>
                    <column id="grade" name="grade" displayName="Grade"/> 
                    <column id="name" name="name" displayName="Name"/> 
                </table>
            </relationship>
        </table>
    </report>
</catalog>

When adding the assignment table relationship, note we also specified the base <table>'s primaryKeyColumns attribute. This attribute is required for each table with at least one descendant relationship table of cardinality many in order to activate the jsontree and htmltree export formats.

Now that the assignment table is part of the report join tree, we can include the columns of the assignment table into the Export Report REST API export result by adding the /student/assignment table resource path to the columns parameter like so:

/[context]/pullreports/catalog/class/report/student-information/export?columns=/student/assignment

to-one, <join_column>

When we added the assignment table to the report data model, you might have noticed the assignment.class_id column. This column represents a to-one relationship from assignment to the referenced class in which the assignment was given (e.g. "Algebra II", "An introduction to Computer Science", or "Cities in History").

Here is the new UML which includes the class table:

To add the class table to our report data model, add a child <relationship> element to the assignment table which includes the child, class table. Because this is a to-one relationship (e.g. cardinality="one") in which the foreign key column is on the parent table, a <join_column> element specifies the foreign key column, assignment.class_id and referenced column, class.id, which participate in the join.

Unlike the assignment table relationship, the class table relationship is defined as join="inner". A relational inner join is typically desired if the data model requires the assignment.class_id column to be not null such that each assignment always has one class. If the assignment.class_id column were nullable, a left join would be typically appropriate because a left join between assignment and class would export assignments without a referenced class if the class table columns were included in the Export Report REST API export result.

Here is our new Pull Reports™ XML Catalog file which includes the class relationship table:

<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://www.pullreports.com/catalog-1.6.1" id="class" name="Class Reports"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.pullreports.com/catalog-1.6.1
    https://www.pullreports.com/docs/xsd/pullreports-catalog-1.6.1.xsd">
    <report id="student-information" name="Student Information">
        <export_config defaultColumns="firstn,lastn"/>
        <table id="student" name="class_info.student" displayName="Student" primaryKeyColumns="id">
            <column id="id" name="id" displayName="Student ID" paramType="java.lang.Integer" export="false"/>
            <column id="firstn" name="first_name" displayName="First Name"/>
            <column id="lastn" name="last_name" displayName="Last Name"/>
            <column id="height_in" name="height_inches" displayName="Height (in)" paramType="java.lang.Double"/>
            <column id="bdate" name="birth_date" displayName="Birth Date" paramType="java.sql.Date"/>
            <column id="active" name="is_active" displayName="Active?" 
                paramType="java.lang.Boolean" export="false"/>
            <pre_filter>@active=true</pre_filter>
            <relationship>
                <join_column columnName="student_id" referencedColumnName="id" />
                <table id="assignment" displayName="Assignment" name="class_info.assignment">
                    <column id="id" name="id" displayName="Assignment ID" paramType="java.lang.Integer"/>
                    <column id="grade" name="grade" displayName="Grade"/> 
                    <column id="name" name="name" displayName="Name"/> 
                    <relationship join="inner" cardinality="one">
                        <join_column columnName="class_id" referencedColumnName="id" />
                        <table id="class" displayName="Class" name="class_info.class">
                            <column id="id" name="id" displayName="Class ID" 
                                 paramType="java.lang.Integer" export="false"/>
                            <column id="name" name="name" displayName="Class Name"/> 
                        </table>
                    </relationship>
                </table>
            </relationship>
        </table>
    </report>
</catalog>

Now that the class table is part of the report join tree, we can include the columns of the class table into the Export Report REST API export result by adding the /student/assignment/class table resource path to the columns parameter like so:

/[context]/pullreports/catalog/class/report/student-information/export?columns=/student/assignment;/student/assignment/class

to-many, <join_table>

The final relationship type to add to our example data model is a to-many in which the relationship between two tables is captured within an indirection or join table. Join tables have foreign key columns to each table in the relationship and are used to relate tables which are modeled as bi-direction, many-to-many relationships. Of course, in Pull Reports™, joins are always uni-directional from a parent to child table, but the join table must still be configured.

To demonstrate to-many, <join_table> joins, we will add two relationships to our example data model. The first is between a student record and the student's parents recorded within a parent table. In this relationship, the student_parent table is the join table. The second relationship is between a parent and the parent's employers represented by the company table. In this relationship, the parent_company table is the join table.

Here is the new UML which includes the parent, company, and join tables:

The new Pull Reports™ XML Catalog file uses two <join_table>s to implement the many-to-many relationships. The student_parent join table is a second <relationship> child of the base, student table, and parent_company is within a nested <relationship> of the parent table.

Since it has a child, descendant table of cardinality many, we also specify the primaryKeyColumns attribute for the parent table.

<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://www.pullreports.com/catalog-1.6.1" id="class" name="Class Reports"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.pullreports.com/catalog-1.6.1
    https://www.pullreports.com/docs/xsd/pullreports-catalog-1.6.1.xsd">
    <report id="student-information" name="Student Information">
        <export_config defaultColumns="firstn,lastn"/>
        <table id="student" name="class_info.student" displayName="Student" primaryKeyColumns="id">
            <column id="id" name="id" displayName="Student ID" paramType="java.lang.Integer" export="false"/>
            <column id="firstn" name="first_name" displayName="First Name"/>
            <column id="lastn" name="last_name" displayName="Last Name"/>
            <column id="height_in" name="height_inches" displayName="Height (in)" paramType="java.lang.Double"/>
            <column id="bdate" name="birth_date" displayName="Birth Date" paramType="java.sql.Date"/>
            <column id="active" name="is_active" displayName="Active?" 
                paramType="java.lang.Boolean" export="false"/>
            <pre_filter>@active=true</pre_filter>
            <relationship>
                <join_column columnName="student_id" referencedColumnName="id" />
                <table id="assignment" displayName="Assignment" name="class_info.assignment">
                    <column id="id" name="id" displayName="Assignment ID" paramType="java.lang.Integer"/>
                    <column id="grade" name="grade" displayName="Grade"/> 
                    <column id="name" name="name" displayName="Name"/> 
                    <relationship join="inner" cardinality="one">
                        <join_column columnName="class_id" referencedColumnName="id" />
                        <table id="class" displayName="Class" name="class_info.class">
                            <column id="id" name="id" displayName="Class ID" 
                                 paramType="java.lang.Integer" export="false"/>
                            <column id="name" name="name" displayName="Class Name"/> 
                        </table>
                    </relationship>
                </table>
            </relationship>
            <relationship>
                <join_table name="class_info.student_parent">
                    <join_columns>
                        <join_column columnName="student_id" referencedColumnName="id" />
                    </join_columns>
                    <inverse_join_columns>
                        <join_column columnName="parent_id" referencedColumnName="id" />
                   </inverse_join_columns>
                </join_table>
                <table id="parent" displayName="Parent" name="class_info.parent" primaryKeyColumns="id">
                    <column id="id" name="id" displayName="Parent ID" paramType="java.lang.Integer"/>
                    <column id="firstn" name="first_name" displayName="Parent First Name"/> 
                    <column id="lastn" name="last_name" displayName="Parent Last Name"/> 
                    <column id="phone" name="phone" displayName="Phone"/> 
                    <relationship>
                        <join_table name="class_info.parent_company">
                            <join_columns>
                                <join_column columnName="parent_id" referencedColumnName="id" />
                            </join_columns>
                            <inverse_join_columns>
                                <join_column columnName="company_id" referencedColumnName="id" />
                           </inverse_join_columns>
                        </join_table>
                        <table id="company" displayName="Company" name="class_info.company">
                            <column id="id" name="id" displayName="Company ID" 
                                 paramType="java.lang.Integer" export="false"/>
                            <column id="name" name="name" displayName="Company"/> 
                        </table>
                    </relationship>
                </table>
            </relationship>
        </table>
    </report>
</catalog>

Now that the parent and company tables are part of the report's join tree, it is possible to expand our previous Export Report REST API URL and include the new tables within the export results with this columns parameter:

/[context]/pullreports/catalog/class/report/student-information/export?columns=/student/assignment;/student/assignment/class;/student/parent;/student/parent/company

However, adding multiple to-many relationships to the same export result returns the cross product of the joins. In this case, the export results would contain one row for each combination of the assignment, parent, and company tables. A more useful export would be to export each to-many relationship separately. For example:

/[context]/pullreports/catalog/class/report/student-information/export?columns=/student/parent;/student/parent/company

and

/[context]/pullreports/catalog/class/report/student-information/export?columns=/student/assignment;/student/assignment/class

Joins to Ancestor Tables

So far in this tutorial, each <relationship> join is between a parent and child table. However, it is also possible to create join relationships between a child table and an ancestor table farther away in the join tree via the <join_column> referencedTablePath attribute. To demonstrate a relationship join to a grandparent, ancestor table, we will once again expand the data model to include a new table, student_assessment.

The student_assessment table contains records of each student's assessment, or opinion, of the classes in which they enroll. The student_assessment table has a foreign key to both student and class and could be added to the report join tree as a relationship child of either table. For this example, we will add student_assessment as a relationship child of the class table and configure a join to the grandparent student table.

Here is the new UML which includes the student_assessment table:

To add the student_assessment table as a child relationship of class, use the to-many, <join_column> relationship type we used to join assignment to student. In this case however, we will use two <join_column> elements. The first will join student_assessment.class_id to class.id, and the second will join student_assessment.student_id to student.id. The later <join_column> must use the referencedTablePath attribute to specify the table resource path of the table which has the referencedColumnName, /student.

The new Pull Reports™ XML Catalog file is now:

<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://www.pullreports.com/catalog-1.6.1" id="class" name="Class Reports"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.pullreports.com/catalog-1.6.1
    https://www.pullreports.com/docs/xsd/pullreports-catalog-1.6.1.xsd">
    <report id="student-information" name="Student Information">
        <export_config defaultColumns="firstn,lastn"/>
        <table id="student" name="class_info.student" displayName="Student" primaryKeyColumns="id">
            <column id="id" name="id" displayName="Student ID" paramType="java.lang.Integer" export="false"/>
            <column id="firstn" name="first_name" displayName="First Name"/>
            <column id="lastn" name="last_name" displayName="Last Name"/>
            <column id="height_in" name="height_inches" displayName="Height (in)" paramType="java.lang.Double"/>
            <column id="bdate" name="birth_date" displayName="Birth Date" paramType="java.sql.Date"/>
            <column id="active" name="is_active" displayName="Active?" 
                paramType="java.lang.Boolean" export="false"/>
            <pre_filter>@active=true</pre_filter>
            <relationship>
                <join_column columnName="student_id" referencedColumnName="id" />
                <table id="assignment" displayName="Assignment" name="class_info.assignment">
                    <column id="id" name="id" displayName="Assignment ID" paramType="java.lang.Integer"/>
                    <column id="grade" name="grade" displayName="Grade"/> 
                    <column id="name" name="name" displayName="Name"/> 
                    <relationship join="inner" cardinality="one">
                        <join_column columnName="class_id" referencedColumnName="id" />
                        <table id="class" displayName="Class" name="class_info.class">
                            <column id="id" name="id" displayName="Class ID" 
                                 paramType="java.lang.Integer" export="false"/>
                            <column id="name" name="name" displayName="Class Name"/> 
                            <relationship>
                                <join_column columnName="class_id" referencedColumnName="id"/>
                                <join_column columnName="student_id" referencedColumnName="id" referencedTablePath="/student"/>
                                <table id="student_assessment" displayName="Student Assessment" name="class_info.student_assessment">
                                    <column id="rating" name="rating" displayName="Rating" paramType="java.lang.Integer"/> 
                                    <column id="assessment" name="assessment" displayName="Assessment"/> 
                                    <column id="assessment_date" name="assessment_date" displayName="Assessment Date" paramType="java.sql.Date"/> 
                                </table>
                            </relationship>
                        </table>
                    </relationship>
                </table>
            </relationship>
            <relationship>
                <join_table name="class_info.student_parent">
                    <join_columns>
                        <join_column columnName="student_id" referencedColumnName="id" />
                    </join_columns>
                    <inverse_join_columns>
                        <join_column columnName="parent_id" referencedColumnName="id" />
                   </inverse_join_columns>
                </join_table>
                <table id="parent" displayName="Parent" name="class_info.parent" primaryKeyColumns="id">
                    <column id="id" name="id" displayName="Parent ID" paramType="java.lang.Integer"/>
                    <column id="firstn" name="first_name" displayName="Parent First Name"/> 
                    <column id="lastn" name="last_name" displayName="Parent Last Name"/> 
                    <column id="phone" name="phone" displayName="Phone"/> 
                    <relationship>
                        <join_table name="class_info.parent_company">
                            <join_columns>
                                <join_column columnName="parent_id" referencedColumnName="id" />
                            </join_columns>
                            <inverse_join_columns>
                                <join_column columnName="company_id" referencedColumnName="id" />
                           </inverse_join_columns>
                        </join_table>
                        <table id="company" displayName="Company" name="class_info.company">
                            <column id="id" name="id" displayName="Company ID" 
                                 paramType="java.lang.Integer" export="false"/>
                            <column id="name" name="name" displayName="Company"/> 
                        </table>
                    </relationship>
                </table>
            </relationship>
        </table>
    </report>
</catalog>

Relationships in the Catalog Configuration Java API

So far in this tutorial, all examples have been XML Catalog files. Here is a Catalog Configuration Java API CatalogConfigurationFactory which is identical to the final XML Catalog file.

package com.pullreports.examples.tutorial;

import com.pullreports.export.request.FilterValue;
import com.pullreports.export.request.Operator;
import com.pullreports.model.Cardinality;
import com.pullreports.model.CatalogId;
import com.pullreports.model.ColumnId;
import com.pullreports.model.Join;
import com.pullreports.model.JoinColumn;
import com.pullreports.model.JoinTable;
import com.pullreports.model.ParamType;
import com.pullreports.model.ReportId;
import com.pullreports.model.TableId;
import com.pullreports.model.TablePath;
import com.pullreports.model.config.CatalogConfiguration;
import com.pullreports.model.config.CatalogConfigurationFactory;
import com.pullreports.model.config.ColumnConfiguration;
import com.pullreports.model.config.JoinColumnsRelationshipConfiguration;
import com.pullreports.model.config.JoinTableRelationshipConfiguration;
import com.pullreports.model.config.PreFilter;
import com.pullreports.model.config.RelationshipConfiguration;
import com.pullreports.model.config.ReportConfiguration;
import com.pullreports.model.config.TableConfiguration;
import com.pullreports.model.exportconfig.ExportConfiguration;

import javax.servlet.ServletContext;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class StudentTutorial2CatalogConfigurationFactory implements CatalogConfigurationFactory {

    @Override
    public CatalogConfiguration makeCatalog(ServletContext servletContext) {

        ReportConfiguration studentReportConfiguration = new StudentReportConfigurationBuilder().build();
        
        List<ReportConfiguration> reportConfigurations = Collections.singletonList(studentReportConfiguration);

        return new CatalogConfiguration(new CatalogId("class"),"Class Reports",reportConfigurations);
    }

    private static class StudentReportConfigurationBuilder extends ReportConfiguration.Builder {
        StudentReportConfigurationBuilder(){
            super(new ReportId("student-information"),"Student Information"
                ,new StudentTableConfigurationBuilder().build());
            setExportConfiguration(
                new ExportConfiguration.Builder().setDefaultColumns(Arrays.asList(
                    StudentTableConfigurationBuilder.FIRSTN_COLUMN_CONFIGURATION.getId()
                        ,StudentTableConfigurationBuilder.LASTN_COLUMN_CONFIGURATION.getId()))
                .build()).build();
        }
    }

    private final static class StudentTableConfigurationBuilder extends TableConfiguration.Builder {

        static final ColumnConfiguration ID_COLUMN_CONFIGURATION,FIRSTN_COLUMN_CONFIGURATION
            ,LASTN_COLUMN_CONFIGURATION,ACTIVE_COLUMN_CONFIGURATION;

        private static final List<ColumnConfiguration> COLUMN_CONFIGURATIONS;

        static {
            ID_COLUMN_CONFIGURATION = new ColumnConfiguration.Builder(
                new ColumnId("id"),"id").setDisplayName("Student ID")
                .setParamType(ParamType.INTEGER).setExport(false).build();

            FIRSTN_COLUMN_CONFIGURATION = new ColumnConfiguration.Builder(
                new ColumnId("firstn"),"first_name").setDisplayName("First Name").build();

            LASTN_COLUMN_CONFIGURATION = new ColumnConfiguration.Builder(
                new ColumnId("lastn"),"last_name").setDisplayName("Last Name").build();

            ColumnConfiguration heightInColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("height_in"),"height_inches").setParamType(ParamType.DOUBLE)
                .setDisplayName("Height (in)").build();

            ColumnConfiguration bdateColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("bdate"),"birth_date").setParamType(ParamType.DATE)
                .setDisplayName("Birth Date").build();
            
            ACTIVE_COLUMN_CONFIGURATION = new ColumnConfiguration.Builder(
                new ColumnId("active"),"active").setDisplayName("Active?")
                .setParamType(ParamType.BOOLEAN).setExport(false).build();

            COLUMN_CONFIGURATIONS = Arrays.asList(
                    ID_COLUMN_CONFIGURATION,FIRSTN_COLUMN_CONFIGURATION,LASTN_COLUMN_CONFIGURATION
                    ,heightInColumnConfiguration,ACTIVE_COLUMN_CONFIGURATION
                    ,bdateColumnConfiguration);
        }

        StudentTableConfigurationBuilder(){
            super(new TableId("student"),"Student",COLUMN_CONFIGURATIONS);
            setName("class_info.student");
            PreFilter activePreFilter = new PreFilter(ACTIVE_COLUMN_CONFIGURATION.getId()
                ,Operator.EQUALS, FilterValue.asList(Boolean.TRUE));
            setPreFilters(Collections.singletonList(activePreFilter));

            RelationshipConfiguration assignmentRelationship = 
                new JoinColumnsRelationshipConfiguration.Builder(new AssignmentTableConfigurationBuilder().build()
                ,Collections.singletonList(new JoinColumn("student_id","id"))).build();

            RelationshipConfiguration parentRelationship = 
                new JoinTableRelationshipConfiguration.Builder(new ParentTableConfigurationBuilder().build()
                ,new JoinTable("class_info.student_parent"
                    ,Collections.singletonList(new JoinColumn("student_id","id"))
                    ,Collections.singletonList(new JoinColumn("parent_id","id")))).build();

            setRelationshipConfigurations(Arrays.asList(assignmentRelationship,parentRelationship));
            setPrimaryKeyColumnIds(Collections.singleton(ID_COLUMN_CONFIGURATION.getId()));
        }
    }

    private final static class AssignmentTableConfigurationBuilder extends TableConfiguration.Builder {

        private static final List<ColumnConfiguration> COLUMN_CONFIGURATIONS;

        static {
            ColumnConfiguration idColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("id"),"id").setDisplayName("Assignment ID")
                .setParamType(ParamType.INTEGER).build();

            ColumnConfiguration gradeColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("grade"),"grade").setDisplayName("Grade").build();

            ColumnConfiguration nameColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("name"),"name").setDisplayName("Name").build();

            COLUMN_CONFIGURATIONS = Arrays.asList(
                    idColumnConfiguration,gradeColumnConfiguration,nameColumnConfiguration);
        }

        AssignmentTableConfigurationBuilder(){
            super(new TableId("assignment"),"Assignment",COLUMN_CONFIGURATIONS);
            setName("class_info.assignment");

            RelationshipConfiguration classRelationship = 
                new JoinColumnsRelationshipConfiguration.Builder(new ClassTableConfigurationBuilder().build()
                ,Collections.singletonList(new JoinColumn("class_id","id")))
                    .setJoin(Join.INNER).setCardinality(Cardinality.ONE).build();

            setRelationshipConfigurations(Collections.singletonList(classRelationship));
        }

    }

    private final static class ClassTableConfigurationBuilder extends TableConfiguration.Builder {

        private static final List<ColumnConfiguration> COLUMN_CONFIGURATIONS;

        static {
            ColumnConfiguration idColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("id"),"id").setDisplayName("Class ID")
                .setParamType(ParamType.INTEGER).setExport(false).build();

            ColumnConfiguration nameColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("name"),"name").setDisplayName("Class Name").build();

            COLUMN_CONFIGURATIONS = Arrays.asList(
                    idColumnConfiguration,nameColumnConfiguration);
        }

        ClassTableConfigurationBuilder(){
            super(new TableId("class"),"Class",COLUMN_CONFIGURATIONS);
            setName("class_info.class");

            RelationshipConfiguration studentAssessmentRelationship = 
                new JoinColumnsRelationshipConfiguration.Builder(new StudentAssessmentTableConfigurationBuilder().build()
                ,Arrays.asList(new JoinColumn("class_id","id")
                    ,new JoinColumn("student_id","id",new TablePath("/student")))).build();

            setRelationshipConfigurations(Collections.singletonList(studentAssessmentRelationship));
        }
    }

    private final static class StudentAssessmentTableConfigurationBuilder extends TableConfiguration.Builder {

        private static final List<ColumnConfiguration> COLUMN_CONFIGURATIONS;

        static {
            ColumnConfiguration ratingColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("rating"),"rating").setDisplayName("Rating")
                .setParamType(ParamType.INTEGER).build();

            ColumnConfiguration assessmentColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("assessment"),"assessment").setDisplayName("Assessment").build();

            ColumnConfiguration assessmentDateColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("assessment_date"),"assessment_date").setParamType(ParamType.DATE)
                .setDisplayName("Assessment Date").build();

            COLUMN_CONFIGURATIONS = Arrays.asList(
                    ratingColumnConfiguration,assessmentColumnConfiguration
                    ,assessmentDateColumnConfiguration);
        }

        StudentAssessmentTableConfigurationBuilder(){
            super(new TableId("student_assessment"),"Student Assessment",COLUMN_CONFIGURATIONS);
            setName("class_info.student_assessment");
        }
    }

    private final static class ParentTableConfigurationBuilder extends TableConfiguration.Builder {

        private static final ColumnConfiguration ID_COLUMN_CONFIGURATION;
        private static final List<ColumnConfiguration> COLUMN_CONFIGURATIONS;

        static {

            ID_COLUMN_CONFIGURATION = new ColumnConfiguration.Builder(
                new ColumnId("id"),"id").setDisplayName("Parent ID")
                .setParamType(ParamType.INTEGER).build();

            ColumnConfiguration firstnColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("firstn"),"first_name").setDisplayName("First Name").build();

            ColumnConfiguration lastnColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("lastn"),"last_name").setDisplayName("Last Name").build();

            ColumnConfiguration phoneColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("phone"),"phone").setDisplayName("Phone").build();

            COLUMN_CONFIGURATIONS = Arrays.asList(
                    ID_COLUMN_CONFIGURATION,firstnColumnConfiguration
                    ,lastnColumnConfiguration,phoneColumnConfiguration);
        }

        ParentTableConfigurationBuilder(){
            super(new TableId("parent"),"Parent",COLUMN_CONFIGURATIONS);
            setName("class_info.parent");

            RelationshipConfiguration companyRelationship = 
                new JoinTableRelationshipConfiguration.Builder(new CompanyTableConfigurationBuilder().build()
                ,new JoinTable("class_info.parent_company"
                    ,Collections.singletonList(new JoinColumn("parent_id","id"))
                    ,Collections.singletonList(new JoinColumn("company_id","id")))).build();

            setRelationshipConfigurations(Collections.singletonList(companyRelationship));
            setPrimaryKeyColumnIds(Collections.singleton(ID_COLUMN_CONFIGURATION.getId()));
        }
    }

    private final static class CompanyTableConfigurationBuilder extends TableConfiguration.Builder {

        private static final List<ColumnConfiguration> COLUMN_CONFIGURATIONS;

        static {
            ColumnConfiguration idColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("id"),"id").setDisplayName("Company ID")
                .setParamType(ParamType.INTEGER).setExport(false).build();

            ColumnConfiguration nameColumnConfiguration = new ColumnConfiguration.Builder(
                new ColumnId("name"),"name").setDisplayName("Company").build();

            COLUMN_CONFIGURATIONS = Arrays.asList(
                    idColumnConfiguration,nameColumnConfiguration);
        }

        CompanyTableConfigurationBuilder(){
            super(new TableId("company"),"Company",COLUMN_CONFIGURATIONS);
            setName("class_info.company");
        }
    }
}

This concludes the tutorial regarding <relationship> configuration. Continue to Tutorial 3 learn about additional <column> configuration capabilities.