I am not sure if this not really is a bug. StandardCacheEntryImpl calls TypeHelper.disassemble which causes the freshly converted (converttoEntityAttribute) attribute again to be converted the other way round (convertToDatabaseColumn). If this conversion is slow this is a problem. This is processed within one query.list() call. It would be nice if there was a way to suppress this forward and backward conversion that slows processing down a lot.
It ends up to be in AttributeConverterMutabilityPlan.deepCopyNotNull(). There I can see the convertToDatabaseColumn() method to be called immediately followed by a call to converttoEntityAttribute(). If I somehow could switch this off my application would be several orders of magnitude faster. What is this for?:
public class AttributeConverterMutabilityPlanImpl<T> extends MutableMutabilityPlan<T> { private final AttributeConverter attributeConverter;
public AttributeConverterMutabilityPlanImpl(AttributeConverter attributeConverter) { this.attributeConverter = attributeConverter; }
@Override @SuppressWarnings("unchecked") protected T deepCopyNotNull(T value) { return (T) attributeConverter.convertToEntityAttribute( attributeConverter.convertToDatabaseColumn( value ) ); } }
Here is the relevant part of the stacktrace:
AttributeConverterMutabilityPlanImpl<T>.deepCopyNotNull(T) line: 29 AttributeConverterMutabilityPlanImpl<T>(MutableMutabilityPlan<T>).deepCopy(T) line: 35 AttributeConverterMutabilityPlanImpl<T>(MutableMutabilityPlan<T>).disassemble(T) line: 24 AttributeConverterTypeAdapter<T>(AbstractStandardBasicType<T>).disassemble(Object, SessionImplementor, Object) line: 284 TypeHelper.disassemble(Object[], Type[], boolean[], SessionImplementor, Object) line: 129 StandardCacheEntryImpl.<init>(Object[], EntityPersister, Object, SessionImplementor, Object) line: 55 AbstractEntityPersister$StandardCacheEntryHelper.buildCacheEntry(Object, Object[], Object, SessionImplementor) line: 5216 SingleTableEntityPersister(AbstractEntityPersister).buildCacheEntry(Object, Object[], Object, SessionImplementor) line: 4227 TwoPhaseLoad.doInitializeEntity(Object, EntityEntry, boolean, SessionImplementor, PreLoadEvent) line: 182 TwoPhaseLoad.initializeEntity(Object, boolean, SessionImplementor, PreLoadEvent) line: 125 QueryLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean, List<AfterLoadAction>) line: 1139 QueryLoader(Loader).processResultSet(ResultSet, QueryParameters, SessionImplementor, boolean, ResultTransformer, int, List<AfterLoadAction>) line: 998 QueryLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean, ResultTransformer) line: 936 QueryLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean, ResultTransformer) line: 342 QueryLoader(Loader).doList(SessionImplementor, QueryParameters, ResultTransformer) line: 2622 QueryLoader(Loader).listUsingQueryCache(SessionImplementor, QueryParameters, Set<Serializable>, Type[]) line: 2464 QueryLoader(Loader).list(SessionImplementor, QueryParameters, Set<Serializable>, Type[]) line: 2426 QueryLoader.list(SessionImplementor, QueryParameters) line: 501 QueryTranslatorImpl.list(SessionImplementor, QueryParameters) line: 371 HQLQueryPlan.performList(QueryParameters, SessionImplementor) line: 216 SessionImpl.list(String, QueryParameters) line: 1339 QueryImpl.list() line: 87
Here is a test that shows the issue in the behavior.:
package org.hibernate.test.converter;
import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map;
import javax.persistence.AttributeConverter; import javax.persistence.Convert; import javax.persistence.Converter; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.Table;
import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.query.Query; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test;
import static org.junit.Assert.assertEquals;
/** * Test to check the number of attributeconverter calls on a simple save and list * * @author Carsten Hammer */ public class AttributeConverterLobTest extends BaseCoreFunctionalTestCase { @Override protected Class<?>[] getAnnotatedClasses() { return new Class[] { EntityImpl.class }; } @Override public void configure(Configuration cfg) { super.configure( cfg ); cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "true" ); cfg.setProperty( Environment.GENERATE_STATISTICS, "true" ); }
@Test public void testMappingAttributeWithLobAndAttributeConverter() { Session session = openSession(); session.beginTransaction(); EntityImpl object = new EntityImpl(); object.status=new HashMap<>(); object.status.put( "asdf", Integer.valueOf( 6 ) ); object.status.put( "key", "table" ); object.id=1; session.save( object ); session.getTransaction().commit(); session.close(); /** * What? Why the hell 2 and not 1? */ assertEquals(2,ConverterImpl.todatabasecounter); /** * Why a from database conversion at all? */ assertEquals(1,ConverterImpl.fromdatabasecounter); session = openSession(); session.beginTransaction(); Query<EntityImpl> createQuery = session.createQuery( "select e from EntityImpl e", EntityImpl.class ); List<EntityImpl> resultList = createQuery.getResultList(); assertEquals(1,resultList.size()); session.getTransaction().commit(); session.close(); /** * Why again a to database conversion? These conversions are very expensive and should only be done if really needed.. */ assertEquals(3,ConverterImpl.todatabasecounter); assertEquals(3,ConverterImpl.fromdatabasecounter); assertEquals("table",resultList.get(0 ).status.get( "key" )); assertEquals(3,ConverterImpl.fromdatabasecounter); }
@Converter public static class ConverterImpl implements AttributeConverter<Map, byte[]> { public static int todatabasecounter=0; public static int fromdatabasecounter=0; @Override public byte[] convertToDatabaseColumn(Map map) { todatabasecounter++; ByteArrayOutputStream out=new ByteArrayOutputStream(); try(XMLEncoder encoder=new XMLEncoder(out)){ encoder.writeObject( map ); } return out.toByteArray(); }
@Override public Map convertToEntityAttribute(byte[] dbData) { fromdatabasecounter++; try(ByteArrayInputStream in=new ByteArrayInputStream(dbData)){ XMLDecoder decoder=new XMLDecoder(in); return (Map) decoder.readObject(); } catch (IOException e) { return null; } } }
@Entity(name = "EntityImpl") @Table( name = "EntityImpl" ) public static class EntityImpl { @Id private Integer id;
@Lob @Convert(converter = ConverterImpl.class) private Map status; } }
|
|