-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: Wrapping raw database strings (varchar) with a custom class
PostPosted: Wed Sep 18, 2013 10:07 am 
Beginner
Beginner

Joined: Mon Jul 28, 2008 4:36 pm
Posts: 24
TaintedString is my own custom class that just wrapps String and deprecates toString(). I've told Hibernate to treat each varchar or text column as a TaintedString in the .hbm.xml files, but I haven't told Hibernate HOW to actually turn a varchar into a TaintedString. How do I do that? Implement org.hibernate.persister.entity.Loadable? Do I need to provide an org.hibernate.persister.entity.EntityPersister?

Here's some of UserSession.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Jul 6, 2012 2:33:02 PM by Hibernate Tools 3.2.1.GA -->
<hibernate-mapping>
    <class name="myApp.db.UserSession" table="user_session" catalog="my_app">
        <id name="id" type="long">
            <column name="id" />
            <generator class="identity" />
        </id>
        <property name="sessionIdStr" type="myApp.util.TaintedString">
            <column name="session_id_str" length="32" not-null="true">
                <comment>request.getSession().getId()</comment>
            </column>
        </property>


Here's some of my persistent UserSession.java class:
Code:
public class UserSession  implements java.io.Serializable {
    private static final long serialVersionUID = 20130917213740L;
    private long id;
    private TaintedString sessionIdStr;

    /** default constructor */
    public UserSession() {
    }

    public long getId() { return id; }
    public void setId(long x) { id = x; }

    public TaintedString getSessionIdStr() { return sessionIdStr; }
    public void setSessionIdStr(TaintedString x) { sessionIdStr = x; }


Here's some of TaintedString.java - the wrapper for String:
Code:
public class TaintedString implements Comparable<TaintedString>, Serializable {
    private static final long serialVersionUID = 20130917223500L;
    private final String s;
    private TaintedString(String str) { s = str; }
    public static TaintedString of(String str) {
        if (str == null) {
            return null;
        }
        return new TaintedString(str);
    }
...


And when I try to run, I get this exception:
Code:
org.hibernate.type.SerializationException: could not deserialize
   at org.hibernate.util.SerializationHelper.deserialize(SerializationHelper.java:217)
   at org.hibernate.util.SerializationHelper.deserialize(SerializationHelper.java:240)
   at org.hibernate.type.SerializableType.fromBytes(SerializableType.java:82)
   at org.hibernate.type.SerializableType.get(SerializableType.java:39)
   at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:163)
   at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:154)
   at org.hibernate.type.AbstractType.hydrate(AbstractType.java:81)
   at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2101)
   at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1380)
   at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1308)
   at org.hibernate.loader.Loader.getRow(Loader.java:1206)
   at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:580)
   at org.hibernate.loader.Loader.doQuery(Loader.java:701)
   at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
   at org.hibernate.loader.Loader.loadEntity(Loader.java:1860)
   at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:48)
   at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:42)
   at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3049)
   at org.hibernate.event.def.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:399)
   at org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:375)
   at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:139)
   at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:195)
   at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:103)
   at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:878)
   at org.hibernate.impl.SessionImpl.get(SessionImpl.java:815)
   at org.hibernate.impl.SessionImpl.get(SessionImpl.java:808)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
   at java.lang.reflect.Method.invoke(Method.java:606)
   at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:301)
   at com.sun.proxy.$Proxy4.get(Unknown Source)
   at planbase.hoshin.util.HibernateUtil.get(HibernateUtil.java:167)
   at planbase.hoshin.db.UserSession.getUSessFromCookies(UserSession.java:396)
   at planbase.hoshin.servlet.HoshinActionServlet.process(HoshinActionServlet.java:347)
   at planbase.hoshin.servlet.HoshinActionServlet.doGet(HoshinActionServlet.java:264)
   at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
   at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
   at planbase.hoshin.filters.UTF8Filter.doFilter(UTF8Filter.java:23)
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
   at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
   at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
   at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
   at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
   at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
   at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
   at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
   at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
   at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1001)
   at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:579)
   at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
   at java.lang.Thread.run(Thread.java:724)
Caused by: java.io.StreamCorruptedException: invalid stream header: 33344343
   at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:802)
   at java.io.ObjectInputStream.<init>(ObjectInputStream.java:299)
   at org.hibernate.util.SerializationHelper$CustomObjectInputStream.<init>(SerializationHelper.java:252)
   at org.hibernate.util.SerializationHelper.deserialize(SerializationHelper.java:209)
   ... 56 more


Hibernate has been working great for me in production for 6 years now. Since it worked before this change, looks to me like Hibernate expect to my custom string wrapper class in serialized form in the database. But the data is just stored in varchar and/or text fields as raw strings, not serialized java.lang.String objects, and certainly not as myApp.util.TaintedString objects. When Hibernate tries to deserialize it, java.io.ObjectInputStream wants the first two shorts to be some special secret Java serialization code that regular strings in the database don't start with. They are just raw strings.

So I think that I need Hibernate to know how to construct my class from a plain JDBC String. How do I let Hibernate know 1. that it needs to do that and 2. how to do that?


Top
 Profile  
 
 Post subject: Re: Wrapping raw database strings (varchar) with a custom class
PostPosted: Wed Sep 18, 2013 10:39 am 
Beginner
Beginner

Joined: Mon Jul 28, 2008 4:36 pm
Posts: 24
I thought the ensuing conversation on FreeNode#hibernate belonged here as the answer. Edited for brevity/clarity.

<sebersole> That is meant to name a "Hibernate Type" aka, an impl of org.hibernate.type.Type, which is the answer to your question. You'd use an impl of org.hibernate.type.Type to perform the mapping.

Do you *always* want to use your TaintedString in place of String? or just in certain cases?

<gpeterso> I want a TaintedString instead of String in 99% of cases and the other 1% of the time is not so important, so I went and changed them all.

<sebersole> If you wanted all replaced you could have simply asked Hibernate to use your TaintedStringType (after you implement it:) anytime it *implicitly* would have used its StringType. If you've already changed all the mappings, it's somewhat a moot point.

<gpeterso> I can change them back. How would I tell it to treat all Strings as TaintedStrings?

<sebersole> http://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html_single/#types-registry

cfg.registerTypeOverride( new TaintedStringType() );

where TaintedStringType names the exposed java type of the attribute in getRegistrationKeys()

You didn't say (that i saw) how you represent this in your java model. Are the attribute values exposed as this TainedString class? What type is UserSession#sessionIdStr defined as?

<gpeterso> A TaintedString

<sebersole> OK, in your TaintedStringType#getRegistrationKeys() you'd return: new String[] { TaintedString.class.getName() }

As the docs I pointed to discuss, those keys are how Hibernate decides which mapping-type (Type) to use for attributes which do not explicitly indicate one

<gpeterso> Glad you said so. My first thought was public String[] getRegistrationKeys() { return new String[] {"org.hibernate.type.StringType"}; }

<sebersole> No, its the attribute types

<gpeterso> So TaintedStringType is a separate class from TaintedString. I pass TaintedStringType to the Configuration with cfg.registerTypeOverride()? That's better than just making TaintedString extend org.hibernate.type.StringType.

<sebersole> They are different concepts. TaintedString is your domain concept. TaintedStringType is a Hibernate concept, telling Hibernate how to handle TaintedString, how to read it, write it, what its internal mutability policy is, how to cache it, etc.

I'm planning a series of topical docs, one of which is going to be "type mapping"

<gpeterso> So I understand that to map a Basic Value Type I implement org.hibernate.type.BasicType (not in 3.2.6) OR org.hibernate.type.Type (or one of its derivatives) OR org.hibernate.usertype.UserType. Of those three org.hibernate.usertype.UserType seems the friendliest. I think I want a separate class, TaintedStringType to implement UserType.
<sebersole> UserType is a stand-in for Type
<gpeterso> Do I also need to register: cfg.registerTypeOverride( new TaintedStringType() );
<sebersole> not understanding
<sebersole> 3.2.6?!?
<sebersole> yikes
<gpeterso> If I don't register my TaintedStringType, how will Hibernate know how to deal with all the things I mapped as TaintedStrings and stored in varchar or text fields?
<sebersole> well as we already said you have 2 options
<sebersole> 1) specify type="TaintedStringType" for all attributes
<sebersole> 2) cfg.registerTypeOverride( new TaintedStringType() );
<sebersole> actually
<sebersole> not sure cfg.registerTypeOverride is even available in 3.2
<gpeterso> So in the database reverse engineering, it will know that TaintedStringType (or myApp.util.TaintedStringType) in the .hbm.xml files needs to be just TaintedString in the .java files?
<sebersole> no
<sebersole> reverse engineering wont understand that
<gpeterso> So I should register.
<sebersole> reverse engineering has a different way to hook in specific Type mappings
<sebersole> but you'd need to ask max (the hibernate-tools dude)
<sebersole> #jbosstools
<gpeterso> the reverse enginnering tools are working with myApp.util.TaintedString for varchar and text. If I registerTypeOverride, then the Hibernate runtime will do the right thing with those mappings?
<sebersole> "reverse enginnering tools are working with myApp.util.TaintedString" would mean (most likely) that it is doing (1)
<sebersole> or does it not add type=""?
<sebersole> again, you have 2 options
<sebersole> for runtime
<gpeterso> Would this be easier if I was using annotations instead of .hbm.xml files?
<sebersole> 1) specify type="TaintedStringType" for all attributes in the mappings <- this is something reverse engineering should be able to do
<sebersole> 2) cfg.registerTypeOverride( new TaintedStringType() ); <- again, not sure that is actually available in 3.2
<sebersole> you need to realize just how old 3.2 is ;)
<sebersole> its gotta be 6 years old now
<sebersole> at least
<gpeterso> I built a lot of tools around the reverse engineering component and as a result, I became afraid of upgrading it.
<sebersole> upgradng the reverse eng tool and upgrading the runtime are not necessarily linked
<gpeterso> I didn't know that. This is good news!
<sebersole> like i said, i think reveng has a way to let you specify Type (type="") as well
<gpeterso> I really appreciate your help. Thank you very much. It's very cool that Hibernate has built-in capabilities to do this - had these abilities 6 years ago!
<gpeterso> Yeah, 3.2 doesn't have registerTypeOverride.
<gpeterso> I'll upgrade. Should have done this anyway.
<sebersole> well you could also look into reveng adding those type="" attributes

Time passes...

<gpeterso> Sorry, I was thinking about reverse engineering and the Hibernate run-time as a unit. When you said type="" I thought hibernate.reveng.xml instead of SomeEntity.hbm.xml. I think I'm on the same page now.
<gpeterso> You asked about type="" attributes. Right now it says <property name="sessionIdStr" type="myApp.util.TaintedString">.
<gpeterso> If I just change that to myApp.util.TaintedStringType, you are saying that I should be good.
<sebersole> type="myApp.util.TaintedString" would work too (bit redundant), but only if you did cfg.registerTypeOverride( new TaintedStringType() ); as we already discussed
<sebersole> in fact, type="myApp.util.TaintedString" would be the same as having no type="" at all

<gpeterso> But type="myApp.util.TaintedStringType" should work without registerTypeOverride()?
<gpeterso> It's probably safer to upgrade and use registerTypeOverride() too.
<sebersole> gpeterso: right, type="myApp.util.TaintedStringType" will work without registerTypeOverride()
<sebersole> if you do both its redundant
<gpeterso> Cool. I can do that. Thanks!
<sebersole> the purpose is that registerTypeOverride needs to be done once
<sebersole> type="" would need to be done for each attribute
<sebersole> for a unique domain type (TaintedString), registerTypeOverride() makes much more sense imo
<gpeterso> Yes. Good idea. I have to implement UserType either way, so I'll do that first to see a proof of concept. Once I have things working again, I can upgrade and use registerTypeOverride(). Thanks!

P.S. (Outside of the above conversation - just for Karma)
I've upgraded to 3.6.10. I would have upgraded to Hibernate 4, but it contains/requires jboss-logging which is pure LGPL (no Classpath Exception) and I can't use LGPL for this project without a classpath exception. So I'm at the latest possible version now. Thanks for encouraging me to upgrade!


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.