It is often useful to access the entity definition metadata. A classic example would be to access the actual maximum defined length of a string column from the code. Such a definition should be centralised to avoid multiple definitions of a constant value, which would otherwise require multiple changes if the value were increased. There are several approaches to this, but unfortunately not an obvious ‘silver bullet’ which is straightforward and also performant.
1/ Access Database Metadata via JDBC
You can access the actual database definitions via a JDBC connection. This would have a performance penalty, but would make sense if the database is your master definition source, i.e. you are starting with a database defninition and then creating entities based on the database. You can get a JDBC connection from JPA via the code below, as described at the end of this post here :-
JPA 2.0
entityManager.getTransaction().begin();
java.sql.Connection connection = entityManager.unwrap(java.sql.Connection.class);
...
entityManager.getTransaction().commit();
JPA 1.0
entityManager.getTransaction().begin();
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)((JpaEntityManager)entityManager.getDelegate()).getActiveSession();
unitOfWork.beginEarlyTransaction();
java.sql.Connection connection = unitOfWork.getAccessor().getConnection();
...
entityManager.getTransaction().commit();
This post here details how to get metadata from a connection. The sample code from the post follows :-
Get Column Size
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class Main {
public static void main(String[] args) throws Exception {
Connection conn = getMySqlConnection();
System.out.println("Got Connection.");
Statement st = conn.createStatement();
st.executeUpdate("drop table survey;");
st.executeUpdate("create table survey (id int,name varchar(30));");
st.executeUpdate("insert into survey (id,name ) values (1,'nameValue')");
ResultSet rsColumns = null;
DatabaseMetaData meta = conn.getMetaData();
rsColumns = meta.getColumns(null, null, "survey", null);
while (rsColumns.next()) {
String columnName = rsColumns.getString("COLUMN_NAME");
System.out.println("column name=" + columnName);
String columnType = rsColumns.getString("TYPE_NAME");
System.out.println("type:" + columnType);
int size = rsColumns.getInt("COLUMN_SIZE");
System.out.println("size:" + size);
int nullable = rsColumns.getInt("NULLABLE");
if (nullable == DatabaseMetaData.columnNullable) {
System.out.println("nullable true");
} else {
System.out.println("nullable false");
}
int position = rsColumns.getInt("ORDINAL_POSITION");
System.out.println("position:" + position);
}
st.close();
conn.close();
}
private static Connection getHSQLConnection() throws Exception {
Class.forName("org.hsqldb.jdbcDriver");
System.out.println("Driver Loaded.");
String url = "jdbc:hsqldb:data/tutorial";
return DriverManager.getConnection(url, "sa", "");
}
public static Connection getMySqlConnection() throws Exception {
String driver = "org.gjt.mm.mysql.Driver";
String url = "jdbc:mysql://localhost/demo2s";
String username = "oost";
String password = "oost";
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
public static Connection getOracleConnection() throws Exception {
String driver = "oracle.jdbc.driver.OracleDriver";
String url = "jdbc:oracle:thin:@localhost:1521:caspian";
String username = "mp";
String password = "mp2";
Class.forName(driver); // load Oracle driver
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
}
2/ Access JPA Annotations via Reflection
Alternatively, you could try using Reflection to read the JPA annotations via introspection. This post here describes how to read annotations with Reflection, but note that this is not a JPA example – it is a ‘roll your own persistance provider’ example, but the introspection techniques should work the same way. The downside of this technique is that all that introspection just seems laborious and slow performing if all you want is the length of a few string columns.
3/ Use Constants in Inner Classes to roll your own master Metadata Definitions
Another way, which makes sense if the entity defninitions are the ‘master’ and you are creating the database tables from the entities, is just to define some inner classes in the entity class containing static constants for the metadata you need access to. You can then use these definitions as the master, and refer to them both in the annotations and the calling code. You can also use an inheritance hierachy to standardise the metadata you are using, and centralise common definitions. As you are rolling your own metadata, you do not have to stick to the jpa annotations, you are free to invent any other constants that you need to tell you something about your entities/columns. The example which follows demonstrates column metadata, but you could of course apply the same technique to entity (table) level metadata if required.
In this example, a number of entities have Code fields containing a coded string which might be 30 characters, and description fields which might be 255 characters. The definitions for “Code” and “Description” can be centralised in a common superclass. In each Entity, for all the fields for which you want to expose the metadata, you define a public inner class named <FieldName>Meta just above the field and annotation declarations. You can then access the constants in the inner class statically from outside the entity. (If required, you could also provide a statically initialised field containing a reference to the <FieldName>Meta class, and create a getter for it, if you need getter access as well, e.g. for managed access via a framework such as JSF, although this involves non static access to a static value which eclipse/java will winge about). The following code samples illustrate the technique:-
Class ColumnMeta
package uk.co.salientsoft.jpa;
/* This base class defines the metadata used. */
public abstract class ColumnMeta {
public static final String NAME = null;
public static final int LENGTH = 255;
}
Class ColumnMetaCode
package uk.co.salientsoft.test.domain;
import uk.co.salientsoft.jpa.ColumnMeta;
public abstract class ColumnMetaCode extends ColumnMeta {
public static final String NAME = "Code";
public static final int LENGTH = 30;
}
Class ColumnMetaDescription
package uk.co.salientsoft.test.domain;
import uk.co.salientsoft.jpa.ColumnMeta;
public abstract class ColumnMetaDescription extends ColumnMeta {
public static final String NAME = "Description";
public static final int LENGTH = 255;
}
Entity Class AppRole
package uk.co.salientsoft.test.domain;
import java.io.Serializable;
import java.lang.String;
import javax.persistence.*;
@Entity
public class AppRole implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(generator="AppRoleID")
private long AppRoleID;
private long AppID;
public class CodeMeta extends ColumnMetaCode {};
@Column(length=CodeMeta.LENGTH)
private String Code;
public class DescriptionMeta extends ColumnMetaDescription {};
@Column(length=DescriptionMeta.LENGTH)
private String Description;
public AppRole() {
super();
}
public long getAppRoleID() {
return this.AppRoleID;
}
public void setAppRoleID(long AppRoleID) {
this.AppRoleID = AppRoleID;
}
public long getAppID() {
return this.AppID;
}
public void setAppID(long AppID) {
this.AppID = AppID;
}
public String getCode() {
return this.Code;
}
public void setCode(String Code) {
this.Code = Code;
}
public String getDescription() {
return this.Description;
}
public void setDescription(String Description) {
this.Description = Description;
}
}
Example MetaData References
codeLength = AppRole.CodeMeta.LENGTH;
descLength = AppRole.DescriptionMeta.LENGTH;