@Target(value=TYPE) @Retention(value=RUNTIME) public @interface EnumMappingTable
EnumMapping
.
EnumMapping
changes the default mechanism of persisting enums, making
persistence of enums steadier and more controllable. However, it doesn't
offer any functionality to have a mapping to a database table that holds all
of the possible values of an enum. With this annotation it is possible to
have a database table that will serve either as a source of enum values, that
will be incorporated by the application, or as a collection of all currently
possible enum values used in the application, with a possibility to track
historical values of previously used enums with either option.
Using this annotation allows to keep all possible enum values in a separate database table and to differentiate between discontinued enum values that were used in the past, but are not now supported by enforcing the established correspondence mapping between java enum and database table representations that can be further fine-tuned by using methods defined herewith.
When using this annotation the default mapping can be changed and one-to-one correspondence between java enum and database table representations can be achieved, with the additional possibility to track historical values of enum constants used before.
To enable this behaviour one must supply an EnumMappingTable
instance
to the EnumMapping
annotation declaration with a specified
mappingType()
that doesn't evaluate to EnumMappingTable.MappingType.NO_ACTION
.
The possible options are:
EnumMappingTable.MappingType.TABLE
. In this case the database table data takes
precedence over the java enum and the data of the specified table will be
used to adjust java enum constants accordingly. Database table must already
be in place to use this mapping type. It is of utmost importance to
provide a private no-argument constructor to the enum that would use the
EnumMappingTable.MappingType.TABLE
mechanism to update enum constant values so that
new entries could be successfully added to the enum constant values.
EnumMappingTable.MappingType.ENUM
. In this case the java enum takes precedence
over the database table data and the structure of the specified enum
constants will be used to update database table data accordingly. If the
database table is not yet created it will be created by the application.
The mapping between the enum and the database can be fine-tuned by using the following settings:
oneFieldMapping()
(defaults to true) is a flag that indicates
that only one field will be used to map the enum, the one that is specified
in EnumMapping.fieldName()
. If this flag is set to false, then two
fields will be persisted, both integer (holding the id) and string (holding
the code), basing on the specified settings.
deleteType()
(defaults to EnumMappingTable.DeleteAction.HARD_DELETE
) is a
setting that specifies the behaviour when an enum constant must be deleted
either from the database table, or from the java enum constants. If it
evaluates to EnumMappingTable.DeleteAction.HARD_DELETE
then only the currently used
enum data will be available in the database table, with all other data
deleted from the table. If it evaluates to EnumMappingTable.DeleteAction.SOFT_DELETE
then all currently unused enum data will be also available in the database,
either in a separate history table (for EnumMappingTable.MappingType.ENUM
mapping
type), or in an enum data table (for EnumMappingTable.MappingType.TABLE
mapping type)
that must have a special soft delete column of INT-compatible type declared
that will hold values other than 0
for soft-deleted enum values.
The column name for the latter can be specified via the
deletedColumnName()
that defaults to "deleted".
doInserts()
(defaults to true) is a flag that indicates that
insert statements will be performed in the database table, or new java enum
constants will be added to the java enum values, basing on
mappingType()
.
ordinalFieldName()
(for
the integer id of the enum) and by the stringFieldName()
(for the code
string value of the enum). The primary mapping key specified by the
EnumMapping.fieldName()
will overwrite the setting of this annotation.
Note that both the id and the code values must be unique, and it is a
developer's responsibility to enforce uniqueness in case of changes (either
in the database table, or in the java enum, basing on mappingType()
).
The associated database table counterparts are:
tableName()
for the name of the database table. In case it's not
supplied it will default to the lower- and snakecased simple name of enum
class with an added "_info" string.
ordinalColumnName()
for the integer-type column name holding the
id value of the enum.
stringColumnName()
for the string-type column name holding the
code value of the enum.
deletedColumnName()
for the integer-type column name holding the
deleted flag of the soft-deletable enums that's only applicable to mapping
type of EnumMappingTable.MappingType.TABLE
. In case of EnumMappingTable.MappingType.ENUM
an
additional table with the name equal to tableName()
with an added
"_history" string will be created, or used, to hold the soft deleted enums.
Usage of this annotation is as follows. The first example illustrates how to keep the enum id-code values in-sync from the java enum. It doesn't need to create any database tables in advance, as the source of information will come from the java enum and its modifications upon restart. So, for the following enum:
@EnumMapping(enumMappingTable = @EnumMappingTable(mappingType = EnumMappingTable.MappingType.ENUM, oneFieldMapping = false, deleteType = EnumMappingTable.DeleteAction.SOFT_DELETE)) public enum UserRole { USER(1, "USR"), EMPLOYEE(2, "EMP"), MANAGER(3, "MGR"); private String code; private int id; private UserRole(int id, String code) { this.id = id; this.code = code; } public String getCode() { return code; } public int getId() { return id; } }a table will be generated with the following content (an existing table can also be used:
CREATE TABLE user_role_info (id INT NOT NULL, code VARCHAR(32) NOT NULL, PRIMARY KEY (id), CONSTRAINT user_role_info_code_UNIQUE UNIQUE (code)); INSERT INTO user_role_info (id, code) values (1, 'USR'), (2, 'EMP'), (3, 'MGR');If at one moment the enum will be modified and the
EMPLOYEE
enum constant will be removed with two new constants
added to the following:
@EnumMapping(enumMappingTable = @EnumMappingTable(mappingType = EnumMappingTable.MappingType.ENUM, oneFieldMapping = false, deleteType = EnumMappingTable.DeleteAction.SOFT_DELETE)) public enum UserRole { USER(1, "USR"), MANAGER(3, "MGR"), PART_TIME_EMPLOYEE(4, "PTE"), FULL_TIME_EMPLOYEE(5, "FTE"); // Old values: EMPLOYEE(2, "EMP") ... }then the history table,
user_role_info_history
, will be
updated with the 2, 'EMP'
values and those values will be
removed from the user_role_info
table with two new values added.
Unique key updates will also be performed if unique key changes, i.e. if
EMPLOYEE(2, "EMP")
is changed at some point to
EMPLOYEE(2, "EMPL")
then these changes will be reflected in the
table as well. Note that it is a developer responsibility not to introduce
colliding primary/unique keys so that the mapping can be performed
successfully.
Another example is a database-driven enum. All possible values are declared in a database table, possibly keeping unused values with a soft deleted flag. The application will keep the java enums in line with the database table and update the enum class accordingly. It will also add currently declared in enum class constants, but absent in the data store, as soft deleted rows. In this case a database table must exist beforehand and could have the following structure:
CREATE TABLE user_role_info (id INT NOT NULL, code VARCHAR(32) NOT NULL, deleted INT DEFAULT 0 NOT NULL, PRIMARY KEY (code), CONSTRAINT user_role_info_code_UNIQUE UNIQUE (id, deleted)); INSERT INTO user_role_info (id, code, deleted) values (1, 'USR', 0), (2, 'EMP', 0), (3, 'MGR', 0);The mapping will not be performed if the database table doesn't exist at application startup. With the following table and the configured annotation shown below the enum, no matter what values it had at compile time, will have the values corresponding to the table data. So, for the enum:
@EnumMapping(type = EnumType.STRING, enumMappingTable = @EnumMappingTable(mappingType = EnumMappingTable.MappingType.TABLE, oneFieldMapping = false, deleteType = EnumMappingTable.DeleteAction.SOFT_DELETE)) public enum UserRole { USER(6, "USR"), EMPLOYEE(7, "EMP"), MANAGER(8, "MGR"); private String code; private int id; private UserRole() {} // Note that this no-argument constructor is obligatory with this setup. private UserRole(int id, String code) { this.id = id; this.code = code; } public String getCode() { return code; } public int getId() { return id; } }the identifiers of enums will be replaced with the data in the table, i.e.
1, 2, 3
accordingly. If at one moment the table will be
modified and two new rows are added, i.e.
(4, 'PTE', 0), (5, 'FTE', 0)
, and one row with id
'EMP'
is hard-deleted, then at application startup the enum
constants will be replaced with the ones essentially equal to
USER(1, "USR"), MANAGER(3, "MGR")
(the existing ones) and
NEW_CONSTANT_1(4, "PTE"), NEW_CONSTANT_2(5, "FTE")
(the new
ones). The value EMPLOYEE(7, "EMP")
will be removed and a
soft-deleted row (7, 'EMP', 1)
will be added to the database.
Note that it would be still possible to refer to the existing enum constants
via the traditional methods, i.e. UserRole.USER
, but the deleted
constant will be unavailable, i.e. UserRole.EMPLOYEE
will hold
null
value, and the freshly added ones will be unavailable via
static fields, but only via UserRole.values()
call, or
UserRole.valueOf("PTE")
call, and the valuesthat will hold
correct set of enum constants. Also note that it is a developer
responsibility not to introduce tables with colliding primary/unique keys so
that the mapping can be performed successfully.
Although the mapping will be correctly performed in any case, if no errors are encountered, the best practice is to keep enum constants in line with the database table and treat such functionality as a reminder to do the associated updates. To achieve this goal it is important to look at the generated warnings in log files at application startup stating that difference in enum class and table data was detected and correct the code accordingly. It is especially important for database-driven enums, as in the case of enum-driven tables the database table will be corrected after the first startup. So, these logs should serve as a reminder to cleanup the code.
The concluding remark about the internal functionality of the mapping is that
for the enum data to be treated correctly within the application the
id
values will be used to modify Enum.ordinal
and
code
values will be used to modify Enum.name
.
EnumMapping
Modifier and Type | Fields and Description |
---|---|
static String |
CODE_COLUMN_NAME |
static String |
DEFAULT_ENUM_HISTORY_TABLE_POSTFIX |
static String |
DEFAULT_ENUM_TABLE_POSTFIX |
static String |
DELETED_COLUMN_NAME |
static String |
ID_COLUMN_NAME |
Modifier and Type | Optional Element and Description |
---|---|
String |
deletedColumnName
Deleted column name of the database table associated with the java
enum.
|
EnumMappingTable.DeleteAction |
deleteType
Delete action type to be applied while mapping between java enum and
database table.
|
boolean |
doInserts
A flag indicating that insert statements will be performed in the
database table or in the java enum, basing on
EnumMapping.type() . |
EnumMappingTable.MappingType |
mappingType
Type of mapping between java enum and database table.
|
boolean |
oneFieldMapping
A flag indicating that only one field will be used to map the enum,
the one that is specified in
EnumMapping.fieldName() . |
String |
ordinalColumnName
Integer column name of the database table associated with the java
enum that will be treated as the unique id of the enum.
|
String |
ordinalFieldName
Integer field name of the java enum that will be treated as its
unique id.
|
String |
stringColumnName
String column name of the database table associated with the java
enum that will be treated as the unique code name of the enum.
|
String |
stringFieldName
String field name of the java enum that will be treated as its unique
code name.
|
String |
tableName
Corresponding database table name of java enum representation.
|
public static final String ID_COLUMN_NAME
public static final String CODE_COLUMN_NAME
public static final String DELETED_COLUMN_NAME
public static final String DEFAULT_ENUM_TABLE_POSTFIX
public static final String DEFAULT_ENUM_HISTORY_TABLE_POSTFIX
public abstract EnumMappingTable.MappingType mappingType
public abstract String tableName
public abstract String ordinalColumnName
EnumMapping.type()
evaluates to EnumType.STRING
and
oneFieldMapping()
evaluates to true
then this
value will not be used while applying the mapping. Defaults to "id".public abstract String stringColumnName
EnumMapping.type()
evaluates to EnumType.ORDINAL
and
oneFieldMapping()
evaluates to true
then this
value will not be used while applying the mapping. Defaults to
"code".public abstract String ordinalFieldName
EnumMapping.fieldName()
value will be used
instead if EnumMapping.type()
evaluates to
EnumType.ORDINAL
. Additionally, if EnumMapping.type()
evaluates to EnumType.STRING
and oneFieldMapping()
evaluates to true
then this value will not be used while
applying the mapping. Defaults to "id".public abstract String stringFieldName
EnumMapping.fieldName()
value will be used
instead if EnumMapping.type()
evaluates to
EnumType.STRING
. Additionally, if EnumMapping.type()
evaluates to EnumType.ORDINAL
and oneFieldMapping()
evaluates to true
then this value will not be used while
applying the mapping. Defaults to "code".public abstract EnumMappingTable.DeleteAction deleteType
public abstract String deletedColumnName
mappingType()
of this
annotation evaluates to EnumMappingTable.MappingType.TABLE
to mark deleted
rows in the database table representation. This column type must be
INT
-compatible and its value different from
0
indicates that the table row is soft-deleted. Defaults
to "deleted".public abstract boolean doInserts
EnumMapping.type()
. Defaults to true.public abstract boolean oneFieldMapping
EnumMapping.fieldName()
. If this
flag is set to false, then two fields will be persisted, both integer
(holding the id) and string (holding the code), basing on the
specified settings. Defaults to true.Copyright © 2015–2021 OmniFaces. All rights reserved.