2
votes

I am using Hibernate 3.6, JPA 2.0, and Spring 3.0.6. I have fields in my objects like the following:

class PersonContact {
    Long eAddressCpId;
    ElectronicAddress eAddress;
}

I use field access (in my orm files) and queries/inserts/etc work without an issue. The fields are both in the class as well as in the orm files. But on startup of the application, the JPA configuration load spits out warnings:

2011-02-22 15:38:10,785 [[STANDBY] ExecuteThread: '3' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.cfg.annotations.reflection.JPAOverridenAnnotationReader - Property com.foo.model.contactpoint.ElectronicAddress.eAddress not found in class but described in  (possible typo error)
2011-02-22 15:38:10,801 [[STANDBY] ExecuteThread: '3' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.cfg.annotations.reflection.JPAOverridenAnnotationReader - Property com.foo.model.person.PersonContact.eAddressCpId not found in class but described in  (possible typo error)
2011-02-22 15:38:10,801 [[STANDBY] ExecuteThread: '3' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.cfg.annotations.reflection.JPAOverridenAnnotationReader - Property com.foo.model.person.PersonContact.eAddress not found in class but described in  (possible typo error)
2011-02-22 15:38:10,817 [[STANDBY] ExecuteThread: '3' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.cfg.annotations.reflection.JPAOverridenAnnotationReader - Property com.foo.model.person.PartyContact.eAddressCpId not found in class but described in  (possible typo error)
2011-02-22 15:38:10,817 [[STANDBY] ExecuteThread: '3' for queue: 'weblogic.kernel.Default (self-tuning)'] WARN org.hibernate.cfg.annotations.reflection.JPAOverridenAnnotationReader - Property com.foo.model.person.PartyContact.eAddress not found in class but described in  (possible typo error)

If I change my field names to be electronicAddressCpId and electronicAddress, then I don't get these warnings.

Is there requirements around the field names?

Thanks..jay

---------Additional Details-------------------------------- Here is a snippet of my POJO.

/**
 * This is a generated class. Do not modify.
 */
@XmlRootElement(namespace = "http://foo.com/model", name = "ElectronicAddress")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(namespace = "http://foo.com/model", name = "ElectronicAddress", propOrder = { "eAddress", "id" })
public class ElectronicAddress extends ContactPoint {

    /**
     * The serialize value.
     */
    private static final long serialVersionUID = -1L;
    /**
     * The eAddress field.
     */
    @XmlElement(name = "EAddress", namespace = "##default")
    private String eAddress;
    /**
     * The id field.
     */
    @XmlElement(name = "Id", namespace = "##default")
    private Long id; //NOPMD

    /**
     * Gets the value of the eAddress property.
     * This field is Required.
     * 
     * @return eAddress object is {@link String }
     */
    public String getEAddress() {
        return eAddress;
    }

    /**
     * Sets the value of the eAddress property.
     * This field is Required
     * 
     * @param eAddress object is {@link String }
     */
    public void setEAddress(String eAddress) {
        this.eAddress = eAddress;
    }

    /**
     * Gets the value of the id property.
     * This field is Optional.
     * 
     * @return id object is {@link Long }
     */
    public Long getId() {
        return id;
    }

    /**
     * Sets the value of the id property.
     * This field is Optional
     * genericInheritGetterSetter
     * @param id object is {@link Long }
     */
    public void setId(Long id) {
        this.id = (Long) id;
    }

}

Here is a snippet of the orm file.

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
  http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
    version="2.0">
    <description>com.foo.model.ElectronicAddress Entity Mapping</description>
    <package>com.foo.model</package>
    <schema>COMMON</schema>
    <access>FIELD</access>
    <entity class="com.foo.model.ElectronicAddress"
        access="FIELD" metadata-complete="true">
        <table name="ELECTRONIC_ADDRESSES" />
        <attributes>
            <id name="id">
                <column name="ELECTRONIC_ADDRESS_ID" />
                <generated-value strategy="SEQUENCE" generator="ADDR_SEQ" />
                <sequence-generator name="ADDR_SEQ"
                    sequence-name="COMMON.ADDR_SEQ" allocation-size="1" />
            </id>
            <basic name="eAddress">
                <column name="ELECTRONIC_ADDRESS" />
            </basic>
        </attributes>
    </entity>
</entity-mappings>
2

2 Answers

1
votes

This is the relevant Code from JPAOverridenAnnotationReader (I'm using 3.5.6, but that probably hasn't changed much):

Element element = tree != null ? tree.element( "attributes" ) : null;
//put entity.attributes elements
if ( element != null ) {
    //precompute the list of properties
    //TODO is it really useful...
    Set<String> properties = new HashSet<String>();
    for (Field field : clazz.getFields()) {
        properties.add( field.getName() );
    }
    for (Method method : clazz.getMethods()) {
        String name = method.getName();
        if ( name.startsWith( "get" ) ) {
            properties.add( Introspector.decapitalize( name.substring( "get".length() ) ) );
        }
        else if ( name.startsWith( "is" ) ) {
            properties.add( Introspector.decapitalize( name.substring( "is".length() ) ) );
        }
    }
    for (Element subelement : (List<Element>) element.elements()) {
        String propertyName = subelement.attributeValue( "name" );
        if ( !properties.contains( propertyName ) ) {
            log.warn( "Property {} not found in class"
                    + " but described in <mapping-file/> (possible typo error)",
                    StringHelper.qualify( className, propertyName ) );
        }
    }
}

I don't see where the bug you describe should occur.

This code:

for (Field field : clazz.getFields()) {
    properties.add( field.getName() );
}

adds all field names and this code

if ( !properties.contains( propertyName ) ) {
    log.warn( "Property {} not found in class"
        + " but described in <mapping-file/> (possible typo error)",
        StringHelper.qualify( className, propertyName ) );

checks if the field name mapped in the XML exists in the class. None of these do any String processing (String processing is only done for methods). I'd say you have a typo somewhere.

1
votes

I found the problem and it is part of the method checkForOrphanProperties in JPAOverridenAnnotationReader:

for (Method method : clazz.getMethods()) {
    String name = method.getName();
    if ( name.startsWith( "get" ) ) {
        properties.add( Introspector.decapitalize( name.substring( "get".length() ) ) );
    }
    else if ( name.startsWith( "is" ) ) {
        properties.add( Introspector.decapitalize( name.substring( "is".length() ) ) );
    }
}

The problem is that the method looks for all public fields and then starts adding field names based on "get" and "is" methods it finds. The Introspector.decapitalize method utilizes specific rules to determine what to decapitalize.

From the Introspector class:

/**
 * Utility method to take a string and convert it to normal Java variable
 * name capitalization.  This normally means converting the first
 * character from upper case to lower case, but in the (unusual) special
 * case when there is more than one character and both the first and
 * second characters are upper case, we leave it alone.
 * <p>
 * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
 * as "URL".
 *
 * @param  name The string to be decapitalized.
 * @return  The decapitalized version of the string.
 */
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
    return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
        Character.isUpperCase(name.charAt(0))){
    return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}

So for instance our field is:

private String eAddress;

And our getter is:

public String getEAddress() {
        return eAddress;
}

So based on the Introspector.decapitalize functionality, the result of the decapitalize would be "EAddress", not "eAddress". Because it sees two capital letters in the "EAddress" when the code substrings off the "get"...it won't decapitalize those. Therefore it complains that the eAddress field in the orm.xml doesn't exist. The persistence of the field works totally fine, these warnings just show up when the war starts and the files are bootstrapped.