Database Persistence
Once the Java beans data structure has been created from a MusicXML file, the beans are then persisted to the database using Hibernate object-relational mapping.
Hibernate annotations added to the Java bean code in the tasks application:
As outlined on the Java Beans page in this section, the application's bean data structure is a translation from the MusicXML schema definition. Hibernate annotations are added to the beans individually to map the score data to the database schema definition.
Database mapping
Entities and database tables
A bean that maps to a database table is declared as a Hibernate entity. The table name for the bean is always declared, because MySQL table names are case-sensitive on some operating systems.
Discriminator columns
Abstract classes that represent many different concrete subtypes are mapped as a single-inheritance entity with a discriminator column. Each concrete subclass of the abstract superclass declares the value that is stored in the discriminator column. On fetch, Hibernate recognizes the stored discriminator value and creates a populated bean of the correct subclass type.
For example, DirectionType is annotated:
The Coda subclass of DirectionType is annotated with discriminator value coda:
Database columns and bean attributes
Scalar character values are annotated with their database table column name:
Scalar numeric values are annotated with precision = 12 and scale = 4. Numeric values in a MusicXML file beyond 4 places after the decimal point are truncated. Position values (default and relative x/y) greater than 99999999 will display an exception during conversion and the numeric value is stored as null.
Enumerations
A collection of MusicXML enumeration values are implemented as a Java enum.
Java enum naming convention is that values are upper-case with underscores separating words. By default, the Hibernate @Enumerated annotation persists these upper-case values as is.
Database users, however, would expect that the persisted value be the same as the enumeration value found in the MusicXML schema, as this would be simpler and more intuitive.
An attribute converter class translates between Java enum conventional notation and the MusicXML enumerated values. Bean attributes of an enum are annotated with the @Convert annotation that declares which implemented attribute converter class handles the translation.
For example, a Slur (along with many other classes) has connection enumeration values which are implemented as the Java enum Connection:
The ConnectionConverter class converts the enum values to database values and back:
The connection attribute in the Slur class is then annotated with the ConnectionConverter:
The database user can then construct queries using connection values 'start', 'stop', and 'continue', while at the same time the Java enum preserves its naming convention.
Any MusicXML enumerated values that aren't satisfied by the standard conversion process above (they contain capital letters, for example) are handled as special cases within their individual AttributeConverter class implementations.
Foreign keys
An association between tables is made by a foreign key column in one table that references the other table.
One-to-one relationships are annotated with the foreign key in the table of the declaring class. For example, the score table has a foreign key score_header_id to the score_header table:
One-to-many relationships are annotated with the foreign key in the table of the referenced class. They are always annotated with the subselect @Fetch annotation, and declare their ordering on the referenced table's ordering column. For example, a part has a list of measures, so the foreign key part_id is in the measure table that is mapped by the Measure class.
Transient columns
Various columns are annotated as transient. These are columns that are not used in the MusicXML database mapping, and are not persisted in the database.
All attributes in a bean entity are required to have an annotation. When an attribute is only used for building LilyPond output, the attribute is annotated as transient.
For example, during LilyPond processing, directions are queued and assigned to the next note, staff changes are marked, and a note's placement within a chord is noted. These are temporary processing values and are not persistent, so these attributes get the transient annotation:
Special case: MusicData and MeasureItem
The hierarchical Java bean structure reflects the hierarchical structure of an XML document. As a result, all beans mapped to the database are connected in a hierarchical fashion. The Score class is at the top of the hierarchy, and so fetching a score from the database can theoretically be done in one query. This query generated by Hibernate, however, will most likely be unnecessarily large, and will most likely violate the constraint on the number of tables in a single query in MySQL.
To get around this problem, there's a break point in the data model annotation:
When a score is fetched, the first query segment includes all of the tables within the score header, the parts, the measures, and the primary keys of the music_data records.
Then the full music_data records are fetched individually by the primary keys obtained in the first query segment, and each music data bean is added to the list of music data beans in its enclosing measure.
Likewise, when saving Java beans to the database, first the Score up to the measure level is saved, and then each MusicData object in each of the measures is saved individually.
This approach requires two separate mappings to the music_data table: one that maps the primary key and music data type of the music_data record, and one that maps the entire music data definition.
Java class MusicData represents the choice element in the music-data group, and maps the full music_data table definition. Java class MeasureItem has the partial mapping.
In class Measure, there's a mapped list of MeasureItem beans, and a transient attribute for the List of MusicData beans.
The annotation for Measure is:
The MeasureItem class partial mapping:
The MusicData class complete mapping includes its role as an abstract superclass of MusicData types, and the music_data table's role as a single-inheritance table with a discriminator column.