简体   繁体   中英

Why does Hibernate throw a ClassCastException when calling session.save(object)?

When I do the following Hibernate call I get a ClassCastException (see Stacktrace) but I have problems to understand why. What's Hibernate trying to do here? Is it trying to cast one of my objects to a different class type? If yes, why and to what class?

session.save(fooAccount);

Stacktrace:

com.foo.web.model.exception.FailedDatabaseOperationException: java.lang.ClassCastException: com.foo.web.model.authentication.SecurePassword
    at com.foo.web.controller.db.HibernateController.savefooAccount(HibernateController.java:883)
    at com.foo.web.model.account.fooAccount.save(fooAccount.java:459)
    at com.foo.web.controller.AccountController.createfooAccount(AccountController.java:258)
    at com.foo.web.view.start.RegisterController.register(RegisterController.java:233)
    at com.foo.web.view.start.RegisterController.onClick$btn_register(RegisterController.java:196)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.zkoss.zk.ui.event.GenericEventListener.onEvent(GenericEventListener.java:81)
    at org.zkoss.zk.ui.impl.EventProcessor.process0(EventProcessor.java:192)
    at org.zkoss.zk.ui.impl.EventProcessor.process(EventProcessor.java:138)
    at org.zkoss.zk.ui.impl.EventProcessingThreadImpl.process0(EventProcessingThreadImpl.java:517)
    at org.zkoss.zk.ui.impl.EventProcessingThreadImpl.sendEvent(EventProcessingThreadImpl.java:121)
    at org.zkoss.zk.ui.event.Events.sendEvent(Events.java:319)
    at org.zkoss.zk.ui.event.Events.sendEvent(Events.java:329)
    at org.zkoss.zk.ui.AbstractComponent$ForwardListener.onEvent(AbstractComponent.java:3034)
    at org.zkoss.zk.ui.impl.EventProcessor.process0(EventProcessor.java:192)
    at org.zkoss.zk.ui.impl.EventProcessor.process(EventProcessor.java:138)
    at org.zkoss.zk.ui.impl.EventProcessingThreadImpl.process0(EventProcessingThreadImpl.java:517)
    at org.zkoss.zk.ui.impl.EventProcessingThreadImpl.run(EventProcessingThreadImpl.java:444)
Caused by: java.lang.ClassCastException: com.foo.web.model.authentication.SecurePassword
    at org.hibernate.type.ComponentType.toLoggableString(ComponentType.java:410)
    at org.hibernate.type.ComponentType.toLoggableString(ComponentType.java:414)
    at org.hibernate.pretty.Printer.toString(Printer.java:76)
    at org.hibernate.pretty.Printer.toString(Printer.java:113)
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:120)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    at com.foo.web.controller.db.HibernateController.savefooAccount(HibernateController.java:879)

Edit: Here are the mappings as well as the code that stores the object:

Mapping file (shortened version. Irrelevant stuff omitted):

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated 26.04.2011 14:49:15 by Hibernate Tools 3.3.0.GA -->
<hibernate-mapping>
    <class name="com.foo.web.model.account.fooAccount" table="fooACCOUNT">

        <id name="id" type="long" access="field">
            <column name="foo_ACCOUNT_ID" />
            <generator class="native" />
        </id>

        <many-to-one name="defaultMailAccount" lazy="false" column="DEFAULT_MAIL_ACCOUNT_ID" />

        <bag name="mailAccounts" table="MAILACCOUNTS" lazy="false" inverse="true">
            <key column="foo_ACCOUNT_ID"></key>
            <one-to-many class="com.foo.web.model.mail.account.MailAccount" />
        </bag>

        <component name="user" class="com.foo.web.model.account.fooUser">
            <component name="activationCode">
                <property name="activationCode" column="ACTIVATION_CODE"></property>
            </component>

            <component name="password" class="com.foo.web.model.authentication.MediumSecurePassword">
                <property name="hash" column="PASSWORD" />
            </component>

            <component name="resetCode">
                <property name="resetCode" column="RESET_CODE" />
            </component>
            <one-to-one name="fooAccount" class="com.foo.web.model.account.fooAccount"></one-to-one>
            <component name="username">
                <property name="username" column="USERNAME" unique="true" />
            </component>

            <property name="userStatus">
                <column name="USERSTATUS" />
                <type name="org.hibernate.type.EnumType">
                    <param name="type">12</param>
                    <param name="enumClass">com.foo.web.model.account.UserStatus</param>
                </type>
            </property>
            <property name="userType">
                <column name="USERTYPE" />
                <type name="org.hibernate.type.EnumType">
                    <param name="type">12</param>
                    <param name="enumClass">com.foo.web.model.account.UserType</param>
                </type>
            </property>
        </component>

    </class>
</hibernate-mapping>

SecurePassword.java (shortened)

/**
 * Represents a password.
 */
public class SecurePassword extends Password {

    /**
     * Default constructor
     */
    public SecurePassword() {
        super();
    }

    /**
     * Constructor method. Will throw IllegalPasswordException if password is
     * not safe.
     */
    public SecurePassword(String password) throws IllegalPasswordException {
        setPassword(password);
    }

    /**
     * Checks if the given character is a number
     * 
     * @param c
     *            The character to check
     * @return Returns true if the given character is a number
     */
    public boolean isNumber(char c) {
        // ...
    }

    /**
     * Checks is a given password is valid.
     */
    public boolean passwordValid(Password password)
        throws IllegalPasswordException {
        return passwordValid(password.toString());
    }

    /**
     * Checks is a given password is valid.
     */
    public boolean passwordValid(String password)
        throws IllegalPasswordException {

        // ...
    }

    /**
     * Sets a new password.
     */
    @Override
    public void setPassword(String password) throws IllegalPasswordException {

        if (passwordValid(password)) {
            this.password = password;
        }
    }

}

Password.java (shortened)

/**
 * Represents a simple password without much restriction
 */
public class Password {
    Crypter crypter     = new Crypter();
    String  hash        = null;
    String  password    = null;

    public Password() {
        super();
    }

    public Password(String password) throws IllegalPasswordException {
        setPassword(password);
    }

    @Override
    public boolean equals(Object password) {

        if (this == password) {
            return true;
        }

        if (password instanceof Password) {
            Password p = (Password) password;
            return getHash().equals(p.getHash());
        }

        if (password instanceof String) {
            String password_str = getPassword();
            if (password_str != null) {
                return password_str.equals(password);
            }
        }

        return false;
    }

    /**
     * Returns the hashed version of this password
     * 
     * @return The hashed version of this password
     */
    public String getHash() {
        if (hash == null) {
            try {
                hash = crypter.hash(getPassword());
            } catch (FailedCryptOperationException e) {
                handleException(e, false, null, null);
            }
        }
        return hash;
    }

    public String getPassword() {
        return password;
    }

    /*
     * Handles the given exception
     */
    private void handleException(Exception e, boolean notifyUser,
        String customTitle, String customErrorMessage) {

        SystemController.handleException(e, notifyUser, customTitle,
            customErrorMessage);
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    @SuppressWarnings("unused")
    public void setPassword(String password) throws IllegalPasswordException {
        hash = null;
        this.password = password;
    }

    @Override
    public String toString() {

        return getPassword();
    }
}

MediumSecurePassword.java

public class MediumSecurePassword extends SecurePassword {

    public final int    MAX_LENGTH              = 64;
    public final int    MIN_LENGTH              = 6;
    StringUtil          stringUtil              = new StringUtil();


    public MediumSecurePassword() {
        super();
    }

    /**
     * Constructor method. Will throw IllegalPasswordException if password is
     * not safe. Medium Safe passwords are at least 6 characters long.
     */
    public MediumSecurePassword(String password) throws IllegalPasswordException {
        setPassword(password);
    }

    /**
     * Checks if the given character is a number
     */
    public boolean isNumber(char c) {
        // ...
    }

    /**
     * Checks is a given password is valid. Valid means that it's secure (at
     * least 8 characters, at least 1 number, at least 1 special character).
     */
    public boolean passwordValid(Password password)
        throws IllegalPasswordException {
        return passwordValid(password.toString());
    }

    /**
     * Checks is a given password is valid. Valid means that it's "medium secure" (min.
     * length of 6).
     */
    public boolean passwordValid(String password)
        throws IllegalPasswordException {

        // ...
    }

    /**
     * Sets a new password. Will throw IllegalPasswordException if password is
     * not safe. Safe passwords are at least 8 characters long, consist of
     * numbers, letters and special characters.
     */
    @Override
    public void setPassword(String password) throws IllegalPasswordException {

        if (passwordValid(password)) {
            this.password = password;
        }
    }
}

Note: If I remove these 3 lines from my mapping file everything runs smoothly:

<component name="password" class="com.foo.web.model.authentication.MediumSecurePassword">
 <property name="hash" column="PASSWORD" />
</component>

So, for me there seems to be a problem with getting the password's hash value via passwordInstance.getHash() ? Not sure though.

What is the idea behind using a subclass of a subclass of Password in the mapping?

        <component name="password" class="com.foo.web.model.authentication.MediumSecurePassword">
            <property name="hash" column="PASSWORD" />
        </component>

What happens here is that you require a specific implementation to be serialised on the persist. In you case your object implements the parent class (SecurePassword) so it can not be cast to MediumSecurePassword.

I will advise to use the parent class in the mapping, this way you will be able to use both SecurePassword and MediumSecurePassword in your implementation:

        <component name="password" class="com.foo.web.model.authentication.Password">
            <property name="hash" column="PASSWORD" />
        </component>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM