Opening
A database must be opened before it can be used and it must be closed when it is no longer used.
Creating a database by invoking the constructor of a class that extends the
CustomDatabase
class opens the database as a strongly
typed database whereas creating an instance of the
Database
interface by invoking the
Database.open
method opens the database as
a weakly typed database.
For example, suppose our sample database which we created in Section
Creating the Database
of Chapter
Creation
is still saved in the
dbDir
directory.
Then, with
Path path = Paths.get("dbDir/layout");
and applying the try-with-resources
statement
try (EduDB db = new EduDB(path, -1, false, 0)) {
// do something with db
}
opens and closes the sample database as a strongly typed database,
whereas
try (Database db = Database.open(path, -1, false, null)) {
// do something with db
}
opens and closes the same database as a weakly typed database.
(The parameters of the methods are explained in Section Parameters.)
The remaining sections of this page are
Strongly and Weakly Typed
Database queries make references to tables and columns and the result is typically a collection of values, each value being of a particular data type.
A strongly typed database has well-defined fields that refer to instances
of strongly typed tables which in turn refer to instances of strongly
typed columns.
For example, in Chapter Design
we defined
the field TEACHER_TABLE
in the EduDB
class and
the field POSTAL_CODE
in the ContactTable
class.
So
are valid assignments. Note that the value of a postal code is an instance of theTeacherTable t = EduDB.TEACHER_TABLE;
Column<Integer> c = ContactTable.POSTAL_CODE;
Integer
class.
Any typos or wrong assumptions about the type of a column's value are
reported at compile-time.
(Try it by replacing Integer
with a different type.)
Moreover, the program code is completely independent of the table and column
names so that these can be changed later without affecting client code.
Changing the type of a column is no problem either because the compiler
signals this change to the programmer by reporting a compilation error each
time this column or a value of this column is used in the program code.
Unfortunately, we can't use these strongly typed fields if we open the
database as a weakly typed database.
In a weakly typed database, tables and columns are referenced by name and a
value must often be cast to its assumed real
type.
For example, assume that the sample database was opened as a weakly typed
database.
Best we can do for assigning the Teacher table and the Postal Code column
is:
These methods do not return instances of the strong typesTable t = db.getTable("Teacher")
;Column<?> c = db.getTable("Contact").getColumn("PostalCode")
;
TeacherTable
and Column<Integer>
but of the
weak types
Table
and
Column<?>
, respectively.
Note how the realtype of a postal code is left open. You won't see any errors at compile-time if you misspelled the name of a table or column or if you did cast a postal code to a wrong type. Only at runtime will you notice sooner or later that your code does not behave as you expect. Even worse, the program code depends on table and column names, so any change in the name of a referenced table or column will silently break your code. The same applies if you change the type of a column and somewhere in your code lurks a cast of a value of that column to its
realtype.
Another point concerns the limitation to meet constraints resulting from the domain-specific data model. For example, the insertion of an arbitrary integer into the Postal Code column of the Contact table should be avoided, because not every arbitrary integer represents a postal code. Another example is the handling of contacts: As the UML diagram suggests, contacts should only be inserted into and removed from the Contact table in connection with students and teachers.
Such domain-specific constraints can be programmed in the custom database class and the custom table classes or outside of these classes, but using these classes. On the other hand, it is not possible to enforce domain-specific constraints if the database is opened as a weakly typed database.
After all, the reader might wonder why ACDP allows a database to be opened as a weakly typed database. The reason is that there are use cases that require reading and manipulating the data stored in a database in a general context that is not specific to its domain. One such use case is, for example, an application that outputs the content of any table in a human-readable manner.
Opening a database as a weakly typed database can be done from the information saved in the database layout only. (The database layout of a WR database is stored in the layout file whereas the database layout of an RO database is contained in the one and only one RO database file.) Therefore, ACDP does not need to know of a custom database class or of any custom table classes in order to open a database as a weakly typed database.Parameters
The signatures of the methods for opening a database as a strongly typed database and a weakly typed database are
CustomDatabase.open(Path mainFile, int opMode, boolean writeProtect,
ICipherFactory cipherFactory, int consistencyNumber,
CustomTable... customTables)
and
Database.open(Path mainFile, int opMode, boolean writeProtect,
ICipherFactory cipherFactory)
respectively.
The set of parameters of the latter method is a subset of the set of
parameters of the first method.
The semantic of each parameter is:
mainFile
- The path—including the file name—of the main database file. The main database file of a WR database is the layout file whereas the main database file of an RO database is the one and only RO database file.
opMode
- The mode of operation: -3 (RO only), -2 (RO only), -1, 0, > 0.
If the value is equal to zero then the backing table files (only one file if the database is an RO database) are immediately closed as soon as they become idle. If the value is positive and equal ton
then the backing table files are closedmax(10, n)
milliseconds after they become idle or when the database is closed. If the value is equal to -1 then the backing table files once opened remain open until the database is closed. If the value is equal to -2 then the zipped and encrypted (if encryption is turned on) content of an RO database is completely mapped into memory. If the value is equal to -3 then the unzipped and decrypted (if encryption is turned on) content of an RO database is completely mapped into memory.
A value equal to zero is recommended only if the database is a WR database and the operating system can't sustain a bunch of files all being opened at the same time. In a server environment, a positive value is recommended or, provided that the database is an RO database and there is enough memory available, a value equal to -2 (less memory required, slower) or -3 (more memory required, faster). writeProtect
- Protects a WR database from being modified.
If set to
true
then no data of the database can be inserted, deleted or updated. This parameter has no effect if the database is an RO database. cipherFactory
- The cipher factory or
null
if no encryption is required. See SectionCoding a Cipher Factory
of ChapterDesign
for an example of a cipher factory. consistencyNumber
- The consistency number.
The consistency number is stored in the database layout.
If the value of this parameter does not match the stored value then
opening the database fails.
The purpose of this parameter is to link a specific version of the
domain-specific database logic to a matching version of the data in the
database.
The
Setup Tool
which we learned about in Chapter
Creation
sets the value of the consistency number to zero when it creates the layout file. customTables
- The list of custom tables. The value of this parameter is predefined by the concrete custom database class and is not intended to be chosen by the database user.