Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customize JPA field name mapping using EclipseLink

I need to map camel-case names to underscore-separated names in my current project, which uses EclipseLink, due to historical reasons. I know we can customize name mapping individually in JPA, but we have a long list of camel-case names to change, so we want to avoid that kind of boilerplate codes if all possible.

What I want to achieve is as follows. Suppose we have an entity class as below:

@Entity
public class FooBar {
    @Id @GeneratedValue
    private Long id;

    @Temporal( TemporalType.TIMESTAMP )
    private Date dateCreated;
}

I want this class maps to a table with name "foo_bar" and columns "id" and "date_created". Note that all names in database are in lower case.

I googled around, and found a solution for changing table names. However, I can't figure out how to change field names in an entity class.

Below is my name-mapping customizer, where the method updateFieldNameMappings() is not mapping fieldName to field_name, which is what I want to achieve. The problem boils down to how to get the field name as in the class definition. So, how do I do that in EclipseLink?

public class JpaNameMappingCustomizer implements SessionCustomizer {

    @Override
    public void customize( Session session ) throws Exception {
        Map<Class, ClassDescriptor> descs = session.getDescriptors();
        Collection<ClassDescriptor> descriptors = descs.values();

        // This code assumes single table per descriptor!
        for (ClassDescriptor desc : descriptors) {
            updateTableNameMapping( desc );
            updateFieldNameMapping( desc );
        }
    }

    private void updateTableNameMapping ( ClassDescriptor desc ) {
        Class clazz = desc.getJavaClass();
        String tableName = camelCaseToUnderscore( clazz.getSimpleName() );
        desc.setTableName( tableName );
    }

    private void updateFieldNameMapping ( ClassDescriptor desc ) {
        // build name maps
        Field[] fields = desc.getJavaClass().getDeclaredFields();
        String tableName = desc.getTableName();
        Map<String,String> nameMap = new HashMap<>();
        String prefix = tableName + ".";
        for( Field field : fields ) {
            String name = field.getName();
            String key = prefix + name.toUpperCase();
            String value = prefix + camelCaseToUnderscore( name );
            nameMap.put( key, value );
        }

        for (DatabaseMapping mapping : desc.getMappings()) {
            if (mapping.isDirectToFieldMapping()) {
                DirectToFieldMapping directMapping = (DirectToFieldMapping) mapping;
                String oldFieldName = directMapping.getFieldName(); // format: table_name.FIELD
                directMapping.setFieldName( nameMap.get( oldFieldName ) );
            }
        }
    }

    private String camelCaseToUnderscore( String camelCase ) {
        return camelCase.trim().replaceAll("(?<!^)[A-Z](?!$)", "_$0").toLowerCase();
    }
}

edit 11/10/13 I did some hacking and changed the customizer. The updateFieldNameMapping() still doesn't do the trick. It seems to me that this statement directMapping.setFieldName( nameMap.get( oldFieldName ) ) inside the method doesn't actually change the field name mapping, which really confuses me.

edit 11/11/13 I forgot to make it clear that I had the eclipselink.session.customizer enabled in the persistence.xml. Namely, I have a line like below in the persistence.xml:

<property name="eclipselink.session.customizer" value="pkg.JpaNameMappingCustomizer"/>
like image 394
JBT Avatar asked Nov 10 '13 22:11

JBT


2 Answers

Instead of

directMapping.setFieldName(nameMap.get(oldFieldName ));

try:

directMapping.getField().resetQualifiedName(nameMap.get(oldFieldName));

This does the trick for me under EclipseLink 2.5.0

like image 78
publysher Avatar answered Sep 22 '22 12:09

publysher


Perhaps you could use my session customizer. It converts table names, and field names to Camel case. It supports inheritance and embeded entities. I tested for 2 years, and I got no issues or side effects. Enjoy it!

import java.sql.SQLException;
import java.util.Locale;
import java.util.Vector;
import org.eclipse.persistence.config.SessionCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.tools.schemaframework.IndexDefinition;

public class CamelNamingStrategy implements SessionCustomizer {
    public static String unqualify(final String qualifiedName) {
        int loc = qualifiedName.lastIndexOf(".");
        return loc < 0 ? qualifiedName : qualifiedName.substring(qualifiedName.lastIndexOf(".") + 1);
    }

    @Override
    public void customize(final Session session) throws SQLException {
        for (ClassDescriptor descriptor : session.getDescriptors().values()) {
            if (!descriptor.getTables().isEmpty()) {
                // Take table name from @Table if exists
                String tableName = null;
                if (descriptor.getAlias().equalsIgnoreCase(descriptor.getTableName())) {
                    tableName = unqualify(descriptor.getJavaClassName());
                } else {
                    tableName = descriptor.getTableName();
                }
                tableName = camelCaseToUnderscore(tableName);
                descriptor.setTableName(tableName);
                for (IndexDefinition index : descriptor.getTables().get(0).getIndexes()) {
                    index.setTargetTable(tableName);
                }
                Vector<DatabaseMapping> mappings = descriptor.getMappings();
                camelCaseToUnderscore(mappings);
            } else if (descriptor.isAggregateDescriptor() || descriptor.isChildDescriptor()) {
                camelCaseToUnderscore(descriptor.getMappings());
            }
        }
    }

    private void camelCaseToUnderscore(Vector<DatabaseMapping> mappings) {
        for (DatabaseMapping mapping : mappings) {
            DatabaseField field = mapping.getField();
            if (mapping.isDirectToFieldMapping() && !mapping.isPrimaryKeyMapping()) {
                String attributeName = mapping.getAttributeName();
                String underScoredFieldName = camelCaseToUnderscore(attributeName);
                field.setName(underScoredFieldName);
            }
        }
    }

    private String camelCaseToUnderscore(final String name) {
        StringBuffer buf = new StringBuffer(name.replace('.', '_'));
        for (int i = 1; i < buf.length() - 1; i++) {
            if (Character.isLowerCase(buf.charAt(i - 1)) && Character.isUpperCase(buf.charAt(i))
                    && Character.isLowerCase(buf.charAt(i + 1))) {
                buf.insert(i++, '_');
            }
        }
        return buf.toString().toLowerCase(Locale.ENGLISH);
    }
}
like image 29
Antonio Maria Sanchez Berrocal Avatar answered Sep 21 '22 12:09

Antonio Maria Sanchez Berrocal