Querying
There is neither a query language nor a data definition language in ACDP. Simply put: ACDP's SQL is Java. This chapter shows you how to formulate and execute queries and how to change data in the database, inside or outside a database transaction.
Structural changes to the database are not an issue in this chapter.
Structural changes include removing a table from the database or inserting a
new column into an existing table.
How to structurally change the database is shown in Section
Refactoring
of Chapter
Maintenance
.
The remaining sections of this page are
Populating the Database
To populate the sample database just invoke therun
method of
the Populator
class.
Which of EduPL
, EduDB
, and
Database
to use?
How data stored in the database is accessed, depends on how the database is
opened.
If the database is opened by creating an instance of the EduPL
class, then this instance
is used for reading data from and writing data to the database.
If, on the other hand, the database is
opened directly, the central class to communicate
with the database is either the domain-specific
EduDB
class or the
non-domain-specific
Database
interface, depending on whether the database is
opened as a strongly typed or weakly typed database.
Path path = Paths.get("dbDir/layout");
// Open database by creating EduPL instance.
try (EduPL pl = new EduPL(path, -1, false, 0)) {
// do something with pl
}
// Open database as strongly typed database.
try (EduDB db = new EduDB(path, -1, false, 0)) {
// do something with db
}
// Open database as weakly typed database.
try (Database db = Database.open(path, -1, false, null)) {
// do something with db
}
Whenever possible, use the persistence layer, as it is the easiest and safest
to use.
This applies in particular if data is to be inserted, updated or deleted.
However, the persistence layer abstracts from the underlying database tables
and table rows.
So, whenever there is a need for a direct access to the tables and rows, the
database should be openend as a strongly typed database.
The database should only be opened as a weakly typed database if the use
case can neither be implemented with the persistence layer nor by opening
the database as a strongly typed database or if the use case is not
domain-specific.
(For example, a non-domain-specific use case is get the names of the
tables of the database
.)
Nigel Leeand
nigel@example.com, respectively). We do not repeat the process of opening the database.
EduPL
// pl instance of EduPL
Student student = pl.getStudent(74);
System.out.println(student.getName() + " " +
student.getContact().getEmail());
+ Compact+ Foolproof
+ Built-in index can be used
EduDB
// EduDB is open
Row row = STUDENT_TABLE.get(
STUDENT_TABLE.indexGet(74),
NAME, CONTACT);
String email = CONTACT_TABLE.getValue(
row.get(CONTACT), EMAIL);
System.out.println(row.get(NAME) + " " + email);
(We use static imports to avoid qualifying constants, for example
STUDENT_TABLE
instead of EudDB.STUDENT_TABLE
or
CONTACT
instead of StudentTable.CONTACT
.)
- Less convenient
- No enforcement of domain-specific constraints implemented in the
persistence layer (not a problem in this example)
+ Built-in index can be used
+ Easy and strongly typed access to tables and columns
Database
// db instance of Database
Table studentTable = db.getTable("Student");
Column<?> studentNumberColumn = studentTable.
getColumn("Student Number");
Column<?> nameColumn = studentTable.getColumn("Name");
Column<?> contactColumn = studentTable.getColumn("Contact");
Table contactTable = db.getTable("Contact");
Column<?> emailColumn = contactTable.getColumn("Email");
Row studentRow = studentTable.rows(studentNumberColumn,
nameColumn, contactColumn).
filter(row -> (int) row.get(studentNumberColumn) == 74).
findAny().get();
Row contactRow = contactTable.get((Ref) studentRow.get(
contactColumn), emailColumn);
System.out.println(studentRow.get(nameColumn) + " " +
contactRow.get(emailColumn));
- Verbose- Cumbersome and error-prone access to tables and columns
- Must iterate Student table
Ad-hoc Queries
When it comes to formulating a queryon the fly, ACDP cannot compete with SQL. This is not surprising, because SQL is an interpreted, declarative language developed with a focus on the spontaneous formulation of database queries, while Java is a compilable, general-purpose programming language. However, even if ad-hoc queries in ACDP are not quite as ad-hoc as in SQL: For a Java programmer who is practiced in querying data structures of a certain complexity, they do not pose a major challenge. And the nice thing is that no knowledge beyond Java is required to program database queries in ACDP and to process their results.
In the following, we define a query on our sample database to be an
ad-hoc query if it cannot be done with the persistence layer.
Therefore, we open the sample database as a strongly typed EduDB
database as was shown in the previous section.
Visiting the Rows of a Table
It is possible to iterate over the rows of a table. Good to know that the order in which the rows of an unchanged table are returned by any of the iterators is always the same. In addition, ACDP supports the streams API of Java 8.For example, displaying the names of all courses can be done in the following three ways. (The process of opening the database and the declaration of some necessary static imports are omitted.)
1. Applying a for-each loop for (Row row : COURSE_TABLE) {
System.out.println(row.get(NAME));
}
2. Getting an instance of Iterator<Row>
for (Iterator<Row> it = COURSE_TABLE.iterator(NAME);
it.hasNext();) {
System.out.println(it.next().get(NAME));
}
3. Using Stream<Row>
COURSE_TABLE.rows(NAME).forEach(row ->
System.out.println(row.get(NAME)));
Note that the Row
instances returned in the for-each loop of the
first variant contain all column values, including the values for the ID and
Teacher columns of the Course table.
In the second and third variant the row instances only contain the values of
the Name column.
More Queries
• Create a list of names of all students enrolled in the C# programming course: List<String> result = STUDENT_TABLE.rows(NAME, COURSES).
filter(r -> Arrays.stream(r.get(COURSES)).
filter(ref -> COURSE_TABLE.getValue(ref,
CourseTable.ID).equals("CSHARP")).
findAny().isPresent()).
map(r -> r.get(NAME)).collect(toList());
• Create a map with keys being the names of the teachers and values
being the names of the students attending at least one of the courses
instructed by the teacher:
Map<String, List<String>> result = new HashMap<>();
for (Row tRow : TEACHER_TABLE) {
List<Ref> l1 = Arrays.asList(tRow.get(
TeacherTable.COURSES));
List<String> sList = new ArrayList<>();
for (Row sRow : STUDENT_TABLE) {
List<Ref> l2 = Arrays.asList(sRow.get(
StudentTable.COURSES));
if (l1.stream().filter(l2::contains).
findAny().isPresent()) {
sList.add(sRow.get(StudentTable.NAME));
}
}
result.put(tRow.get(TeacherTable.NAME), sList);
}
Of course, many more queries are conceivable.
Data Changes
To ensure that no domain-specific constraints are violated, data changes should only be made using the persistence layer. For example, deleting teacher "Jane Smith" by calling // pl instance of EduPL
pl.deleteTeacher("Jane Smith");
not only causes the data of Jane Smithto be removed from the Teacher table, but also removes her contact details from the Contact table. Furthermore, the values in the Teacher column of the Course table are overwritten with the
null
value for the courses that "Jane
Smith" has instructed.
Here is how to change the email address of student 74 to
n.lee@example.com
and add the course LSSGBP
to his list of
courses:
Student student = pl.getStudent(74);
Contact contact = student.getContact();
List<String> courses = student.getCourses();
contact.setEmail("n.lee@example.com");
courses.add("LSSGBP");
pl.persistStudent(74, student.getName(), contact, courses);
Sometimes, however, changes to the data need to be made ad-hoc.
Let us assume, for example, that the system for assigning postal codes has
been changed: All postal codes starting with 8 are to start with 80.
So the postal code 84672 should now be 804672.
The persistence layer doesn't provide direct access to the contact details.
Hence, we open the database as a strongly typed EduDB
database
and call
EduDB.CONTACT_TABLE.updateAllChangeValues(
ContactTable.POSTAL_CODE,
pc -> pc / 10000 == 8 ? 800000 + pc % 80000 : pc);
Transactions, Units, Read Zones
So far, all database operations were executed outside a transaction. We tacitly assumed that- A possible destruction of the database's integrity is accepted. (This may be due to a write operation that catastrophically fails.)
- A possible violation of domain-specific constraints is accepted. (This may be because a sequence of write operations could not be completed due to an unexpected exceptional situation.)
- No other clients use the database at the same time.
For example, using a unit, the code snippet
// pl instance of EduPL
try (Unit u = pl.openUnit()) {
pl.deleteCourse("CFR");
u.commit();
}
removes the CFRcourse from the database. (The
CFRcourse can be deleted without provoking a
DeleteConstraintException
because it is
neither referenced by a student nor a teacher.)
If u.commit()
had been missing, the deletion process would have
been undone when the unit was closed.
For example, the following code does not remove the other non-referenced
courses from the database, because u.commit()
is missing:
// Don't forget u.commit() after the last write operation.
try (Unit u = pl.openUnit()) {
pl.deleteCourse("CDPC");
pl.deleteCourse("HDJ");
pl.deleteCourse("MBS");
}
And
// Don't forget u.commit() after the last write operation.
try (Unit u = pl.openUnit()) {
pl.deleteCourse("CDPC");
pl.deleteCourse("HDJ");
u.commit();
pl.deleteCourse("MBS");
}
just removes the CDPCand
HDJcourses but not the
MBScourse from the database.
Of course, the integrity of the database is maintained even if a call to
u.commit()
is missing.
However, one must be aware that if database changes are rolled back, other
things can get into disarray: In the example above, the index of the
Course table will miss the entry for the MBS
course, although that
course still resides in the database.
This is because the removal of the MBS
course from the index has not
been undone.
It is a good idea to treat any rollback situation as a bug.
Two database write operations that are started in two different threads are never executed concurrently, regardless of whether they run inside or outside a unit. Write operations are therefore synchronized. On the other hand, read operations are not synchronized. They can fail if a write operation is currently running in a different thread. To synchronize read operations, use a read zone. Read operations within a read zone are never executed concurrently with a write operation, but they are executed concurrently with read operations inside open read zones in different threads.
In Section Visiting the Rows of a Table
from above we displayed the name of all courses in the Course table.
Let's do the same thing but this time inside a read zone:
try (ReadZone rz = db.openReadZone()) {
COURSE_TABLE.rows(NAME).forEach(row ->
System.out.println(row.get(NAME)));
}
We could execute this operation within a unit, which would, however, have the
consequence that all program instructions within read zones in other threads
would be blocked until this operation was completed.
Learn more about how transactions are implemented in ACDP by reading the
sections Units,
Read Zones, and
Durabilityin the Javadoc description of the
Database
interface.