This is a common problem with databases. The resolution is called migration. In the relational database world the DBA will write a migration script. It's then a manual process to ensure that the migration script is run on production data at the same time as the new code is promoted. In the shrink-wrap software world it's the responsibility of the install program to recognise an upgrade and migrate the client data.
The Adept Library has a Migration class that does most of the work for you once you have completed some manual steps:
// File DAOTest.java
public class DAOTest extends TestCase
{
static class TestDAO extends DAO
{
/***/ public String string;
/***/ public int integer;
/***/ public float real;
/***/ public static class PrimaryIndex
extends Index
{ /***/ public String string; }
/***/ public static class SecondaryIndex
extends Index
{ /***/ public int integer; }
/***/ public static class TertiaryIndex
extends Index
{
/***/ public int integer;
/***/ public String string;
}
}
}
.......................................
// File Migrations.java
public class Migrations
{
/**
* Migration for DAOTest DAOs - note that these
* are inner class DAOs.
*/
public static class DAOTest
{
/** Test migration path */
public static class TestDAO extends DAO
{
/** First on migration path */
public static class V050511a extends DAO
implements Migration.Interface
{
/***/ public String string;
/***/ public int integer;
/***/ public float real;
/***/ public static class PrimaryIndex
extends Index
{ /***/ public String string; }
/***/ public static class SecondaryIndex
extends Index
{ /***/ public int integer; }
/***/ public static class TertiaryIndex
extends Index
{
/***/ public int integer;
/***/ public String string;
}
/**
* @see Migration.Interface#migrate()
*/
public DAO migrate()
{
/*
* Originally created DAOTest -
* changed when new migration added
*/
V050511b testDAO = new V050511b();
/*
* Use DTO deep copy since objects
* will be so similar.
*/
if (dto == null)
dto = new DTO( this, testDAO);
dto.copy( testDAO, this);
/*
* This is where we would do the
* processing that is the essence
* of migration. If we added fields
* we may need to fill them here.
*/
testDAO.integer++;
/*
* Lucky last - we return the newly
* created replacement DAO.
*/
return testDAO;
}
private static DTO dto;
}
/** Second on migration path */
public static class V050511b extends DAO
implements Migration.Interface
{
/***/ public String string;
/***/ public int integer;
/***/ public float real;
/***/ public static class PrimaryIndex
extends Index
{ /***/ public String string; }
/***/ public static class SecondaryIndex
extends Index
{ /***/ public int integer; }
/***/ public static class TertiaryIndex
extends Index
{
/***/ public int integer;
/***/ public String string;
}
/** @see Migration.Interface#migrate() */
public DAO migrate()
{
com.marringtons.object.DAOTest.TestDAO
testDAO = new
com.marringtons.object.DAOTest.TestDAO();
if (dto == null)
dto = new DTO( this, testDAO);
dto.copy( testDAO, this);
testDAO.real++;
return testDAO;
}
private static DTO dto;
}
}
}
}
Adept migration is more transparent. Your setup code must call Migration.database(), giving it the name of the database to migrate. It will review every persistent class and record the operation, so only the parts of the migration path not yet performed will be done. As an example: suppose over 10 releases, a particular DAO has changed 5 times (shame on you). Any user could be upgrading from an earlier version to the latest at that time and migrate again later. The migration code will account for this and pick up the migration thread from where it left off the last time the software was upgraded.