Java ACDP Tutorial

Elaboration

In Chapter Opening, we opened and closed a database without having done anything useful with it. We now could populate the database and carry out some queries right away. In this chapter, however, we program some key aspects of a robust persistence layer for a multilayered application. This requires the programming of a few additions to the database classes.

The persistence layer to be created has the following characteristics:

The remaining sections of this page are

Readers who want to see ACDP in action now, can skip this chapter and move on to Chapter Querying.

To keep this chapter from getting too large, only a few aspects are emphasized, citing the source code where necessary. Note that the Chapter Sources lists the source code of all classes used in this tutorial.

The EduPL Class

The persistence layer should cover at least the basic functionality of a persistent storage: Insert, Update, Delete and Get. For this purpose, we assign a DAO to each of the entities Contact, Course, Student, and Teacher and declare the following methods:
   void persistCourse(String id, String name)
   void persistStudent(int studentNumber, String name,
                         Contact contact, List<String> courses)
   void persistTeacher(String name, BigInteger salary,
                         Contact contact, List<String> courses)
                         
   void deleteCourse(String id)
   void deleteStudent(int studentNumber)
   void deleteTeacher(String name)
   
   Course getCourse(String id)
   Student getStudent(int studentNumber)
   Teacher getTeacher(String name)
No methods are defined for dealing directyl with contacts, because according to the UML diagram, the Contact entity is entirely managed by the Student and Teacher entities.

We could add these methods to the EduDB database class which we last saw in Section Preparing the Database Class of Chapter Creation. However, we prefer a clear separation between the functionality of the database and the functionality of the persistence layer. That is why we declare these methods in a new class, the EduPL class, where PL is an abbreviation for Persistence Layer.

Remarks:
  1. It is assumed that the course id, the studentNumber and the teacher name act as the primary keys for the Course, Student and Teacher entities, respectively.
  2. A persist method works as an insert method if the database does not yet contain an entry with the specified value for the primary key. Otherwise, it works as an update method.
  3. The persistCourse method has no teacher parameter: The link from a course to a teacher is controlled by the courses parameter of the persistTeacher method.
  4. The Contact DAO class just collects the details of a contact. We included the checkEmail method that checks if an arbitrary text is a valid email address. It would be a good idea to define such validation methods for the other contact-details as well and call them from within the constructor and setter methods.
  5. Both DAO classes Student and Teacher are derived from the Person class because students and teachers have common attributes: Besides a name, both have a contact and a list of courses. Note that a Person object does not store the contact or courses directly, but only links to the contact and courses. Technically, a link in ACDP is a row reference, that is, a reference that directly references a specific row in a table. The Person class stores the row reference of the contact in the contactRef variable and the row references of the courses in the courseRefs variable. Only when the getContact and the getCourses methods are called is the data in the referenced rows loaded from the Contact and Course table, respectively. The same mechanism is applied for the link to the teacher in the Course DAO class. Note that all contact details are loaded but only the values for the primary keys of the Course and Teacher entities which are the course id and the teacher name, respectively.
  6. ACDP does not allow dangling row references. A dangling row reference would arise if it were possible to delete a row in a table that is referenced by another row. ACDP therefore prevents the deletion of a referenced row and throws a DeleteConstraintException instead. Since courses can be referenced by both, students and teachers, it is not possible to delete a course from the database as long as at least one student or teacher references the course. Fine, but what about removing a teacher from the database? The UML diagram shows that the entities Course and Teacher can reference each other. Therefore, without further action no teacher could be removed from the database that references a course that in turn references the teacher. The solution is to delete the references to this teacher in all courses of the Course table before this teacher itself is deleted in the Teacher table. For details, see the part of the source code in the deleteTeacher method which is commented with Release link from course to teacher. (Releasing a link is done by setting the row reference to null.)
  7. The source code of the methods dealing with students or teachers includes the persistence management of contacts. For example, inserting a student into the Student table includes inserting that student's contact details into the Contact table. When a contact is updated, it is checked which contact details have actually changed compared to the stored contact details and only those that have actually changed are updated, see the source code of the updateContact method.
  8. Contacts do not have an independent identity. It is therefore not necessary to define a primary key for contacts. In a relational database, an artificial primary key for contacts would have to be defined.
  9. As we will see in the next section, an index is maintained for each of the Course, Student and Teacher tables to quickly determine whether a certain course, a certain student or a certain teacher is stored in the database and, if so, in which row it is located within the respective table. The index therefore maps the value for a primary key to a row reference. The corresponding indexGet method defined in the IdTable class is invoked in almost all public methods of the EduPL class. More about indexes and the IdTable class in the following sections.
The EduPL class has two additional methods openUnit() and openReadZone(). They are related to database transactions and are explained in Chapter Querying.

The rest of this section delves deeper into the source code by taking a closer look at the getContact method of the Person class as an example.

   public final Contact getContact() {
      final Row row = CONTACT_TABLE.get(contactRef);
      return new Contact(row.get(PHONE_NUMBER), row.get(EMAIL), row.get(STREET),
                                       row.get(CITY), row.get(STATE),
                                       row.get(POSTAL_CODE), row.get(COUNTRY));
   }
The first line of the method's body reads the row referenced by the contactRef variable from the Contact table. The following lines extract the individual column values from the row and create and return a new Contact DAO.

The parameters of the constructor of the Contact class are strongly typed. For example, the postalCode parameter is not simply of type Object, but of the primitive type int. The interesting thing is that the column value for the postal code does not have to be cast to int since row.get(POSTAL_CODE) returns an instance of the Integer class which is internally converted by the JVM to an int value. In fact, the get(Column) method of the Row interface is declared as

<T> T get(Column<T> column)
Since the POSTAL_CODE column is of type Column<Integer>, the type parameter T is replaced by the compiler with the type argument Integer.

If the concrete type of the POSTAL_CODE column were to be changed later, for example from Integer to String, the compiler would immediately detect an error wherever this column value was assigned to an int variable. Strong typing reduces the error-proneness of program code and makes it more robust against future changes.

Elaborating the Table Classes

We last saw the table classes ContactTable, StudentTable, CourseTable, and TeacherTable in Section Preparing the Table Classes of Chapter Creation. They all were derived from the CustomTable abstract class. We do not change these classes anymore with the exception that we place the new abstract IdTable class between the CustomTable class and the StudentTable, CourseTable, and TeacherTable classes:
UML table classes diagram
The write operations (insert, update, delete) of the CustomTable class are not declared with the final modifier. Concrete table classes can overwrite these methods for various purposes, such as to restrict their use or, as shown below, to update an index. For our sample database, we assume that the domain-specific validity checks of values, for example if a certain integer value is actually a valid postal code, are already programmed in the DAO classes, so that we don't have to worry about this in the table classes.

An ID table, that is technically a subclass of the IdTable class, maintains an index of type Map<Object, Ref> that maps an object to a row reference. The index is used to map a value for the primary key to a row reference, so that for a particular value of the primary key the remaining column values can be quickly loaded from the table without having to perform a costly search within the table. The IdTable class assumes that the first column of the ID table contains the values for the primary key.

Note that the public audience is able to consult the index of an ID table by invoking the IdTable.indexGet method. However, there is no direct way for changing the index. The index is changed on the fly every time a student, course or teacher is inserted into or deleted from the respective ID table, see the IdTable.insert and the IdTable.delete(Object) methods. Note that the IdTable.insert method overrides the CustomTable.insert method. Furthermore, the IdTable.delete(Ref) method overrides the CustomTable.delete(Ref) method by throwing an UnsupportedOperationException. It is therefore not possible to insert a row into or to delete a row from an ID table without the index being updated.

More about Indexes

As seen in the source code of the constructor of the EduDB class, the index for each ID table is completely loaded into main memory. Creating the indexes as part of opening the database works well because ACDP iterates very efficiently over the rows of a table. However, keeping the indexes in main memory may not be feasible. In such cases, other strategies may be appropriate, for example using MVStore of the H2 Database Engine.

Note that indexes are less important in ACDP than in relational databases because relational databases do not store row references. Instead, the concept of foreign keys is used to connect database relations. An index supports a join operation to efficiently map a value for a foreign key to a tuple. Since data is usually navigated along an object graph, there is only a need for an index in ACDP if data for a particular object has to be loaded from a value for its primary key. However, if there is a repeated need to evaluate data outside of the object graph, an index may be necessary if other measures, such as transforming the data model into another one that allows evaluation along the object graph, prove to be unfeasible.
Back To Top