1. Rich client

This artifact pulls in all required framework dependencies, except the ones related to database connectivity and logging, see below.

Client Artifact

Swing

is.codion:codion-swing-framework.ui:0.17.36

2. Database connectivity

A Codion client has three ways of connecting to a database, directly via a local JDBC connection or remotely via RMI or HTTP using the Codion remote server.

DB Connection Artifact

Local

is.codion:codion-framework-db-local:0.17.36

RMI

is.codion:codion-framework-db-rmi:0.17.36

HTTP

is.codion:codion-framework-db-http:0.17.36

2.1. DBMS

When connecting to the database with a local JDBC connection the DBMS module for the underlying database must be on the classpath. Note that these artifacts do not depend on the JDBC drivers, so those must be added separately.

The most used and tested DBMS modules are:

  1. Oracle

  2. PostgreSQL

  3. H2 Database

DBMS Artifact

Db2

is.codion:codion-dbms-db2:0.17.36

Derby

is.codion:codion-dbms-derby:0.17.36

H2

is.codion:codion-dbms-h2:0.17.36

HSQL

is.codion:codion-dbms-hsql:0.17.36

MariaDB

is.codion:codion-dbms-mariadb:0.17.36

MySQL

is.codion:codion-dbms-mysql:0.17.36

Oracle

is.codion:codion-dbms-oracle:0.17.36

PostgreSQL

is.codion:codion-dbms-postgresql:0.17.36

SQLite

is.codion:codion-dbms-sqlite:0.17.36

SQL Server

is.codion:codion-dbms-sqlserver:0.17.36

3. Logging

Codion uses SLF4J throughout so all you need to do is add a SLF4J bridge for your logging framework of choice to the classpath. If you use Logback, Log4J or Java Util Logging you can use one of the logging-proxy plugins below which will pull in the required dependencies and provide a main-menu action in the client for setting the logging level.

Logging Artifact

Logback

is.codion:codion-plugin-logback-proxy:0.17.36

Log4j

is.codion:codion-plugin-log4j-proxy:0.17.36

Java Util Logging

is.codion:codion-plugin-jul-proxy:0.17.36

4. Gradle

A minimal Gradle dependencies configuration for a Swing based client application, using the H2 database.

dependencies {
    implementation "is.codion:codion-swing-framework-ui:0.17.36"

    runtimeOnly "is.codion:codion-framework-db-local:0.17.36"
    runtimeOnly "is.codion:codion-dbms-h2:0.17.36"
    runtimeOnly "com.h2database:h2:2.2.220"
    runtimeOnly "is.codion:codion-plugin-logback-proxy:0.17.36"

    testImplementation "is.codion:codion-framework-domain-test:0.17.36"
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.3"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.9.3"
}

5. Code examples

There are at minimum three steps involved in creating a Codion application:

  • Defining a domain model based on the underlying tables.

  • Creating edit panels for tables requiring CRUD functionality.

  • Assembling a client application from these building blocks.

5.1. Domain

Module

Artifact

is.codion.framework.domain

is.codion:codion-framework-domain:0.17.36

5.1.1. Defining entities

The below examples are somewhat simplified, but functionally correct.

View example schema SQL
Note
Not all columns are used in the example code below.
create user if not exists scott password 'tiger';
alter user scott admin true;

create schema store;

create table store.customer (
  id varchar(36) not null,
  first_name varchar(40) not null,
  last_name varchar(40) not null,
  email varchar(100),
  active boolean default true not null,
  constraint customer_pk primary key (id),
  constraint customer_email_uk unique (email)
);

create table store.address (
  id identity not null,
  street varchar(120) not null,
  city varchar(50) not null,
  valid boolean default true not null,
  constraint address_pk primary key (id),
  constraint address_uk unique (street, city)
);

create table store.customer_address (
  id identity not null,
  customer_id varchar(36) not null,
  address_id integer not null,
  constraint customer_address_pk primary key (id),
  constraint customer_address_uk unique (customer_id, address_id)
);

insert into store.customer(id, first_name, last_name, email)
values ('ff60ebc3-bae0-4c0f-b094-4129edd3665a', 'John', 'Doe', 'doe@doe.net');

insert into store.address(street, city)
values ('Elm Street 123', 'Syracuse');

insert into store.customer_address(customer_id, address_id)
values ('ff60ebc3-bae0-4c0f-b094-4129edd3665a', 1);

commit;
View full domain source
public static class Store extends DefaultDomain {

  public static final DomainType DOMAIN = domainType(Store.class);

  public Store() {
    super(DOMAIN);
    customer();
    address();
    customerAddress();
  }

  public interface Customer {
    EntityType TYPE = DOMAIN.entityType("store.customer");

    Column<String> ID = TYPE.stringColumn("id");
    Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
    Column<String> LAST_NAME = TYPE.stringColumn("last_name");
  }

  void customer() {
    add(Customer.TYPE.define(
                    Customer.ID.define()
                            .primaryKey(),
                    Customer.FIRST_NAME.define()
                            .column()
                            .caption("First name")
                            .nullable(false)
                            .maximumLength(40),
                    Customer.LAST_NAME.define()
                            .column()
                            .caption("Last name")
                            .nullable(false)
                            .maximumLength(40))
            .keyGenerator(new CustomerKeyGenerator())
            .stringFactory(StringFactory.builder()
                    .value(Customer.LAST_NAME)
                    .text(", ")
                    .value(Customer.FIRST_NAME)
                    .build()));
  }

  private static final class CustomerKeyGenerator implements KeyGenerator {
    @Override
    public void beforeInsert(Entity entity, DatabaseConnection connection) {
      entity.put(Customer.ID, randomUUID().toString());
    }
  }

  public interface Address {
    EntityType TYPE = DOMAIN.entityType("store.address");

    Column<Integer> ID = TYPE.integerColumn("id");
    Column<String> STREET = TYPE.stringColumn("street");
    Column<String> CITY = TYPE.stringColumn("city");
  }

  void address() {
    add(Address.TYPE.define(
                    Address.ID.define()
                            .primaryKey(),
                    Address.STREET.define()
                            .column()
                            .caption("Street")
                            .nullable(false)
                            .maximumLength(120),
                    Address.CITY.define()
                            .column()
                            .caption("City")
                            .nullable(false)
                            .maximumLength(50))
            .keyGenerator(automatic("store.address"))
            .stringFactory(StringFactory.builder()
                    .value(Address.STREET)
                    .text(", ")
                    .value(Address.CITY)
                    .build()));
  }

  public interface CustomerAddress {
    EntityType TYPE = DOMAIN.entityType("store.customer_address");

    Column<Integer> ID = TYPE.integerColumn("id");
    Column<String> CUSTOMER_ID = TYPE.stringColumn("customer_id");
    Column<Integer> ADDRESS_ID = TYPE.integerColumn("address_id");

    ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
    ForeignKey ADDRESS_FK = TYPE.foreignKey("address_fk", ADDRESS_ID, Address.ID);
  }

  void customerAddress() {
    add(CustomerAddress.TYPE.define(
                    CustomerAddress.ID.define()
                            .primaryKey(),
                    CustomerAddress.CUSTOMER_ID.define()
                            .column()
                            .nullable(false),
                    CustomerAddress.CUSTOMER_FK.define()
                            .foreignKey()
                            .caption("Customer"),
                    CustomerAddress.ADDRESS_ID.define()
                            .column()
                            .nullable(false),
                    CustomerAddress.ADDRESS_FK.define()
                            .foreignKey()
                            .caption("Address"))
            .keyGenerator(automatic("store.customer_address"))
            .caption("Customer address"));
  }
}

To create a domain model we extend the DefaultDomain class and define a DomainType constant to identify the domain model.

In the constructor we call methods adding entity definitions to this domain model.

This class is continued below.

public static class Store extends DefaultDomain {

  public static final DomainType DOMAIN = domainType(Store.class);

  public Store() {
    super(DOMAIN);
    customer();
    address();
    customerAddress();
  }

We create a namespace interface and define a EntityType constant based on the table and typed Column constants for each column, these constants specify the entity API, used when referring to the entity type or its columns.

In the associated method we create ColumnDefinition.Builder instances based on each Column, providing further configuration options, such as specifying a caption and maximum length, using methods in the Column class. We supply these ColumnDefinition.Builder instances to the EntityType.define() method, which returns a EntityDefinition.Builder instance, providing further configuration options for the entity definition, such as the key generation strategy. Finally, we add the definition builder to the domain model.

public interface Customer {
  EntityType TYPE = DOMAIN.entityType("store.customer");

  Column<String> ID = TYPE.stringColumn("id");
  Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
  Column<String> LAST_NAME = TYPE.stringColumn("last_name");
}

void customer() {
  add(Customer.TYPE.define(
                  Customer.ID.define()
                          .primaryKey(),
                  Customer.FIRST_NAME.define()
                          .column()
                          .caption("First name")
                          .nullable(false)
                          .maximumLength(40),
                  Customer.LAST_NAME.define()
                          .column()
                          .caption("Last name")
                          .nullable(false)
                          .maximumLength(40))
          .keyGenerator(new CustomerKeyGenerator())
          .stringFactory(StringFactory.builder()
                  .value(Customer.LAST_NAME)
                  .text(", ")
                  .value(Customer.FIRST_NAME)
                  .build()));
}

private static final class CustomerKeyGenerator implements KeyGenerator {
  @Override
  public void beforeInsert(Entity entity, DatabaseConnection connection) {
    entity.put(Customer.ID, randomUUID().toString());
  }
}

Next we define an entity based on the table store.address.

public interface Address {
  EntityType TYPE = DOMAIN.entityType("store.address");

  Column<Integer> ID = TYPE.integerColumn("id");
  Column<String> STREET = TYPE.stringColumn("street");
  Column<String> CITY = TYPE.stringColumn("city");
}

void address() {
  add(Address.TYPE.define(
                  Address.ID.define()
                          .primaryKey(),
                  Address.STREET.define()
                          .column()
                          .caption("Street")
                          .nullable(false)
                          .maximumLength(120),
                  Address.CITY.define()
                          .column()
                          .caption("City")
                          .nullable(false)
                          .maximumLength(50))
          .keyGenerator(automatic("store.address"))
          .stringFactory(StringFactory.builder()
                  .value(Address.STREET)
                  .text(", ")
                  .value(Address.CITY)
                  .build()));
}

And finally we define an entity based on the many-to-many relationship table store.customer_address, note the foreign keys.

public interface CustomerAddress {
  EntityType TYPE = DOMAIN.entityType("store.customer_address");

  Column<Integer> ID = TYPE.integerColumn("id");
  Column<String> CUSTOMER_ID = TYPE.stringColumn("customer_id");
  Column<Integer> ADDRESS_ID = TYPE.integerColumn("address_id");

  ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
  ForeignKey ADDRESS_FK = TYPE.foreignKey("address_fk", ADDRESS_ID, Address.ID);
}

void customerAddress() {
  add(CustomerAddress.TYPE.define(
                  CustomerAddress.ID.define()
                          .primaryKey(),
                  CustomerAddress.CUSTOMER_ID.define()
                          .column()
                          .nullable(false),
                  CustomerAddress.CUSTOMER_FK.define()
                          .foreignKey()
                          .caption("Customer"),
                  CustomerAddress.ADDRESS_ID.define()
                          .column()
                          .nullable(false),
                  CustomerAddress.ADDRESS_FK.define()
                          .foreignKey()
                          .caption("Address"))
          .keyGenerator(automatic("store.customer_address"))
          .caption("Customer address"));
}

5.2. UI

The EntityPanel class provides a Swing UI for viewing and editing entities. It is composed of a EntityEditPanel and a EntityTablePanel. For each of these panel classes there is a corresponding model class; SwingEntityModel, which is composed of a SwingEntityEditModel and a SwingEntityTableModel. The only class you are required to extend is the EntityEditPanel, which provides the input controls for editing an entity. Below we demonstrate how to set up a simple master/detail panel.

Module

Artifact

is.codion.swing.framework.ui

is.codion:codion-swing-framework-ui:0.17.36

5.2.1. Master

Here we extend a EntityEditPanel to provide the UI for editing a customer and use that edit panel class when we assemble the EntityPanel. We use a default SwingEntityModel implementation, which internally, creates a default SwingEntityEditModel and SwingEntityTableModel.

class CustomerEditPanel extends EntityEditPanel {

  public CustomerEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(Customer.FIRST_NAME);
    createTextField(Customer.FIRST_NAME);
    createTextField(Customer.LAST_NAME);
    addInputPanel(Customer.FIRST_NAME);
    addInputPanel(Customer.LAST_NAME);
  }
}

EntityConnectionProvider connectionProvider =
        LocalEntityConnectionProvider.builder()
                .domain(new Store())
                .user(User.parse("scott:tiger"))
                .build();

SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);

EntityPanel customerPanel = new EntityPanel(customerModel,
        new CustomerEditPanel(customerModel.editModel()));

5.2.2. Detail

Here we create a panel for viewing and editing customer addresses, much like the one above. We start by creating a default SwingEntityModel instance, which we add as a detail model on the customer model. Finally, we create a EntityPanel for the customer address and add that as a detail panel on the customer panel.

class CustomerAddressEditPanel extends EntityEditPanel {

  public CustomerAddressEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    initialFocusAttribute().set(CustomerAddress.CUSTOMER_FK);
    createForeignKeyComboBox(CustomerAddress.CUSTOMER_FK);
    createForeignKeyComboBox(CustomerAddress.ADDRESS_FK);
    addInputPanel(CustomerAddress.CUSTOMER_FK);
    addInputPanel(CustomerAddress.ADDRESS_FK);
  }
}

SwingEntityModel customerAddressModel = new SwingEntityModel(CustomerAddress.TYPE, connectionProvider);

customerModel.addDetailModel(customerAddressModel);

EntityPanel customerAddressPanel = new EntityPanel(customerAddressModel,
        new CustomerAddressEditPanel(customerAddressModel.editModel()));

customerPanel.addDetailPanel(customerAddressPanel);

//lazy initialization of UI components
customerPanel.initialize();

//populate the model with data from the database
customerModel.tableModel().refresh();

Dialogs.componentDialog(customerPanel)
        .title("Customers")
        .show();

5.3. Domain unit test

The EntityTestUnit class provides a way to unit test the domain model. The test method performs insert, update, select and delete on a randomly generated entity instance and verifies the results.

Module

Artifact

is.codion.framework.domain.test

is.codion:codion-framework-domain-test:0.17.36

class StoreTest extends EntityTestUnit {

  public StoreTest() {
    super(new Store(), User.parse("scott:tiger"));
  }

  @Test
  void customer() throws DatabaseException {
    test(Customer.TYPE);
  }

  @Test
  void address() throws DatabaseException {
    test(Address.TYPE);
  }

  @Test
  void customerAddress() throws DatabaseException {
    test(CustomerAddress.TYPE);
  }
}

5.4. Persistance

The EntityConnection interface provides select, insert, update, and delete methods. It has three available implementations, one of which is based on a local JDBC connection, used below.

Module

Artifact

Description

is.codion.framework.db.core

is.codion:codion-framework-db-core:0.17.36

Core database API

is.codion.framework.db.local

is.codion:codion-framework-db-local:0.17.36

Local JDBC implementation

5.4.1. Selecting

Store domain = new Store();

EntityConnection connection =
        LocalEntityConnection.localEntityConnection(
                Database.instance(), domain, User.parse("scott:tiger"));

//select customer where last name = Doe
Entity johnDoe = connection.selectSingle(Customer.LAST_NAME.equalTo("Doe"));

//select all customer addresses
List<Entity> customerAddresses = //where customer = john doe
        connection.select(CustomerAddress.CUSTOMER_FK.equalTo(johnDoe));

Entity customerAddress = customerAddresses.get(0);

Entity address = customerAddress.referencedEntity(CustomerAddress.ADDRESS_FK);

String lastName = johnDoe.get(Customer.LAST_NAME);
String street = address.get(Address.STREET);
String city = address.get(Address.CITY);

5.4.2. Persisting

Store domain = new Store();

EntityConnection connection =
        LocalEntityConnection.localEntityConnection(
                Database.instance(), domain, User.parse("scott:tiger"));

Entities entities = domain.entities();

Entity customer = entities.builder(Customer.TYPE)
        .with(Customer.FIRST_NAME, "John")
        .with(Customer.LAST_NAME, "Doe")
        .build();

customer = connection.insertSelect(customer);

Entity address = entities.builder(Address.TYPE)
        .with(Address.STREET, "Elm Street 321")
        .with(Address.CITY, "Syracuse")
        .build();

address = connection.insertSelect(address);

Entity customerAddress = entities.builder(CustomerAddress.TYPE)
        .with(CustomerAddress.CUSTOMER_FK, customer)
        .with(CustomerAddress.ADDRESS_FK, address)
        .build();

customerAddress = connection.insertSelect(customerAddress);

customer.put(Customer.FIRST_NAME, "Jonathan");

connection.update(customer);

connection.delete(customerAddress.primaryKey());
Full code
/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.demos.manual.quickstart;

import is.codion.common.db.connection.DatabaseConnection;
import is.codion.common.db.database.Database;
import is.codion.common.db.exception.DatabaseException;
import is.codion.common.user.User;
import is.codion.framework.db.EntityConnection;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.db.local.LocalEntityConnection;
import is.codion.framework.db.local.LocalEntityConnectionProvider;
import is.codion.framework.domain.DefaultDomain;
import is.codion.framework.domain.DomainType;
import is.codion.framework.domain.entity.Entities;
import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.KeyGenerator;
import is.codion.framework.domain.entity.StringFactory;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import is.codion.framework.domain.entity.test.EntityTestUnit;
import is.codion.swing.common.ui.dialog.Dialogs;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import is.codion.swing.framework.ui.EntityPanel;

import org.junit.jupiter.api.Test;

import java.util.List;

import static is.codion.framework.demos.manual.quickstart.Example.Store.*;
import static is.codion.framework.domain.DomainType.domainType;
import static is.codion.framework.domain.entity.KeyGenerator.automatic;
import static java.util.UUID.randomUUID;

public final class Example {

  public static class Store extends DefaultDomain {

    public static final DomainType DOMAIN = domainType(Store.class);

    public Store() {
      super(DOMAIN);
      customer();
      address();
      customerAddress();
    }

    public interface Customer {
      EntityType TYPE = DOMAIN.entityType("store.customer");

      Column<String> ID = TYPE.stringColumn("id");
      Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
      Column<String> LAST_NAME = TYPE.stringColumn("last_name");
    }

    void customer() {
      add(Customer.TYPE.define(
                      Customer.ID.define()
                              .primaryKey(),
                      Customer.FIRST_NAME.define()
                              .column()
                              .caption("First name")
                              .nullable(false)
                              .maximumLength(40),
                      Customer.LAST_NAME.define()
                              .column()
                              .caption("Last name")
                              .nullable(false)
                              .maximumLength(40))
              .keyGenerator(new CustomerKeyGenerator())
              .stringFactory(StringFactory.builder()
                      .value(Customer.LAST_NAME)
                      .text(", ")
                      .value(Customer.FIRST_NAME)
                      .build()));
    }

    private static final class CustomerKeyGenerator implements KeyGenerator {
      @Override
      public void beforeInsert(Entity entity, DatabaseConnection connection) {
        entity.put(Customer.ID, randomUUID().toString());
      }
    }

    public interface Address {
      EntityType TYPE = DOMAIN.entityType("store.address");

      Column<Integer> ID = TYPE.integerColumn("id");
      Column<String> STREET = TYPE.stringColumn("street");
      Column<String> CITY = TYPE.stringColumn("city");
    }

    void address() {
      add(Address.TYPE.define(
                      Address.ID.define()
                              .primaryKey(),
                      Address.STREET.define()
                              .column()
                              .caption("Street")
                              .nullable(false)
                              .maximumLength(120),
                      Address.CITY.define()
                              .column()
                              .caption("City")
                              .nullable(false)
                              .maximumLength(50))
              .keyGenerator(automatic("store.address"))
              .stringFactory(StringFactory.builder()
                      .value(Address.STREET)
                      .text(", ")
                      .value(Address.CITY)
                      .build()));
    }

    public interface CustomerAddress {
      EntityType TYPE = DOMAIN.entityType("store.customer_address");

      Column<Integer> ID = TYPE.integerColumn("id");
      Column<String> CUSTOMER_ID = TYPE.stringColumn("customer_id");
      Column<Integer> ADDRESS_ID = TYPE.integerColumn("address_id");

      ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
      ForeignKey ADDRESS_FK = TYPE.foreignKey("address_fk", ADDRESS_ID, Address.ID);
    }

    void customerAddress() {
      add(CustomerAddress.TYPE.define(
                      CustomerAddress.ID.define()
                              .primaryKey(),
                      CustomerAddress.CUSTOMER_ID.define()
                              .column()
                              .nullable(false),
                      CustomerAddress.CUSTOMER_FK.define()
                              .foreignKey()
                              .caption("Customer"),
                      CustomerAddress.ADDRESS_ID.define()
                              .column()
                              .nullable(false),
                      CustomerAddress.ADDRESS_FK.define()
                              .foreignKey()
                              .caption("Address"))
              .keyGenerator(automatic("store.customer_address"))
              .caption("Customer address"));
    }
  }

  static void customerPanel() {
    class CustomerEditPanel extends EntityEditPanel {

      public CustomerEditPanel(SwingEntityEditModel editModel) {
        super(editModel);
      }

      @Override
      protected void initializeUI() {
        initialFocusAttribute().set(Customer.FIRST_NAME);
        createTextField(Customer.FIRST_NAME);
        createTextField(Customer.LAST_NAME);
        addInputPanel(Customer.FIRST_NAME);
        addInputPanel(Customer.LAST_NAME);
      }
    }

    EntityConnectionProvider connectionProvider =
            LocalEntityConnectionProvider.builder()
                    .domain(new Store())
                    .user(User.parse("scott:tiger"))
                    .build();

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);

    EntityPanel customerPanel = new EntityPanel(customerModel,
            new CustomerEditPanel(customerModel.editModel()));

    class CustomerAddressEditPanel extends EntityEditPanel {

      public CustomerAddressEditPanel(SwingEntityEditModel editModel) {
        super(editModel);
      }

      @Override
      protected void initializeUI() {
        initialFocusAttribute().set(CustomerAddress.CUSTOMER_FK);
        createForeignKeyComboBox(CustomerAddress.CUSTOMER_FK);
        createForeignKeyComboBox(CustomerAddress.ADDRESS_FK);
        addInputPanel(CustomerAddress.CUSTOMER_FK);
        addInputPanel(CustomerAddress.ADDRESS_FK);
      }
    }

    SwingEntityModel customerAddressModel = new SwingEntityModel(CustomerAddress.TYPE, connectionProvider);

    customerModel.addDetailModel(customerAddressModel);

    EntityPanel customerAddressPanel = new EntityPanel(customerAddressModel,
            new CustomerAddressEditPanel(customerAddressModel.editModel()));

    customerPanel.addDetailPanel(customerAddressPanel);

    //lazy initialization of UI components
    customerPanel.initialize();

    //populate the model with data from the database
    customerModel.tableModel().refresh();

    Dialogs.componentDialog(customerPanel)
            .title("Customers")
            .show();
  }

  static void domainModelTest() {
    class StoreTest extends EntityTestUnit {

      public StoreTest() {
        super(new Store(), User.parse("scott:tiger"));
      }

      @Test
      void customer() throws DatabaseException {
        test(Customer.TYPE);
      }

      @Test
      void address() throws DatabaseException {
        test(Address.TYPE);
      }

      @Test
      void customerAddress() throws DatabaseException {
        test(CustomerAddress.TYPE);
      }
    }
  }

  static void selectEntities() throws DatabaseException {
    Store domain = new Store();

    EntityConnection connection =
            LocalEntityConnection.localEntityConnection(
                    Database.instance(), domain, User.parse("scott:tiger"));

    //select customer where last name = Doe
    Entity johnDoe = connection.selectSingle(Customer.LAST_NAME.equalTo("Doe"));

    //select all customer addresses
    List<Entity> customerAddresses = //where customer = john doe
            connection.select(CustomerAddress.CUSTOMER_FK.equalTo(johnDoe));

    Entity customerAddress = customerAddresses.get(0);

    Entity address = customerAddress.referencedEntity(CustomerAddress.ADDRESS_FK);

    String lastName = johnDoe.get(Customer.LAST_NAME);
    String street = address.get(Address.STREET);
    String city = address.get(Address.CITY);
  }

  static void persistEntities() throws DatabaseException {
    Store domain = new Store();

    EntityConnection connection =
            LocalEntityConnection.localEntityConnection(
                    Database.instance(), domain, User.parse("scott:tiger"));

    Entities entities = domain.entities();

    Entity customer = entities.builder(Customer.TYPE)
            .with(Customer.FIRST_NAME, "John")
            .with(Customer.LAST_NAME, "Doe")
            .build();

    customer = connection.insertSelect(customer);

    Entity address = entities.builder(Address.TYPE)
            .with(Address.STREET, "Elm Street 321")
            .with(Address.CITY, "Syracuse")
            .build();

    address = connection.insertSelect(address);

    Entity customerAddress = entities.builder(CustomerAddress.TYPE)
            .with(CustomerAddress.CUSTOMER_FK, customer)
            .with(CustomerAddress.ADDRESS_FK, address)
            .build();

    customerAddress = connection.insertSelect(customerAddress);

    customer.put(Customer.FIRST_NAME, "Jonathan");

    connection.update(customer);

    connection.delete(customerAddress.primaryKey());
  }
}