Skip to content

Commit

Permalink
HHH-8558 - Bytecode enhancer: lazy loading support
Browse files Browse the repository at this point in the history
  • Loading branch information
barreiro authored and sebersole committed Jul 6, 2015
1 parent 750d6fb commit c6fa2b1
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 27 deletions.
Expand Up @@ -10,7 +10,7 @@

/**
* small low memory class to keep track of changed fields
*
* <p/>
* uses an array as a set (under the assumption that the number of elements will be low) to avoid having to instantiate an HashSet.
* if the assumption does not, hold the array can be kept ordered to reduce the cost of verifying duplicates
*
Expand All @@ -25,13 +25,19 @@ public SimpleDirtyTracker() {
}

public void add(String name) {
for (String existing : names) {
if ( !contains( name ) ) {
names = Arrays.copyOf( names, names.length + 1 );
names[names.length - 1] = name;
}
}

public boolean contains(String name) {
for ( String existing : names ) {
if ( existing.equals( name ) ) {
return;
return true;
}
}
names = Arrays.copyOf( names, names.length + 1 );
names[names.length - 1] = name;
return false;
}

public void clear() {
Expand Down
Expand Up @@ -26,12 +26,13 @@ public void add(String name) {
int insert = 0;
for ( int low = 0, high = names.length - 1; low <= high; ) {
final int middle = low + ( ( high - low ) / 2 );
if ( names[middle].compareTo( name ) > 0 ) {
final int compare = names[middle].compareTo( name );
if ( compare > 0 ) {
// bottom half: higher bound in (middle - 1) and insert position in middle
high = middle - 1;
insert = middle;
}
else if( names[middle].compareTo( name ) < 0 ) {
else if( compare < 0 ) {
// top half: lower bound in (middle + 1) and insert position after middle
insert = low = middle + 1;
}
Expand All @@ -46,6 +47,25 @@ else if( names[middle].compareTo( name ) < 0 ) {
names = newNames;
}

public boolean contains(String name) {
for ( int low = 0, high = names.length - 1; low <= high; ) {
final int middle = low + ( ( high - low ) / 2 );
final int compare = names[middle].compareTo( name );
if ( compare > 0 ) {
// bottom half: higher bound in (middle - 1) and insert position in middle
high = middle - 1;
}
else if( compare < 0 ) {
// top half: lower bound in (middle + 1) and insert position after middle
low = middle + 1;
}
else {
return true;
}
}
return false;
}

public void clear() {
names = new String[0];
}
Expand Down
Expand Up @@ -6,9 +6,17 @@
*/
package org.hibernate.bytecode.enhance.spi;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import javax.tools.JavaFileObject;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;

import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.internal.CompositeEnhancer;
import org.hibernate.bytecode.enhance.internal.EntityEnhancer;
Expand All @@ -21,13 +29,6 @@
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;

import javax.tools.JavaFileObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;

/**
* Class responsible for performing enhancement.
*
Expand Down Expand Up @@ -161,10 +162,8 @@ private byte[] getByteCode(CtClass managedCtClass) {
}

protected void addInterceptorHandling(CtClass managedCtClass) {
// interceptor handling is only needed if either:
// a) in-line dirty checking has *not* been requested
// b) class has lazy-loadable attributes
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) && !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
// interceptor handling is only needed if class has lazy-loadable attributes
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
return;
}
log.debugf( "Weaving in PersistentAttributeInterceptable implementation on [%s]", managedCtClass.getName() );
Expand Down
@@ -0,0 +1,184 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/

package org.hibernate.bytecode.enhance.spi.interceptor;

import java.util.Set;

import org.hibernate.LazyInitializationException;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker;
import org.hibernate.bytecode.instrumentation.spi.LazyPropertyInitializer;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SessionImplementor;

/**
* Interceptor that loads attributes lazily
*
* @author Luis Barreiro
*/
public class LazyAttributeLoader implements PersistentAttributeInterceptor {

private final transient SessionImplementor session;
private final Set<String> lazyFields;
private final String entityName;

private final SimpleDirtyTracker initializedFields = new SimpleDirtyTracker();

public LazyAttributeLoader(SessionImplementor session, Set<String> lazyFields, String entityName) {
this.session = session;
this.lazyFields = lazyFields;
this.entityName = entityName;
}

protected final Object intercept(Object target, String fieldName, Object value) {
if ( lazyFields != null && lazyFields.contains( fieldName ) && initializedFields.contains( fieldName ) ) {
if ( session == null ) {
throw new LazyInitializationException( "entity with lazy properties is not associated with a session" );
}
else if ( !session.isOpen() || !session.isConnected() ) {
throw new LazyInitializationException( "session is not connected" );
}

Object loadedValue = ( (LazyPropertyInitializer) session.getFactory()
.getEntityPersister( entityName ) ).initializeLazyProperty(
fieldName,
target,
session
);

initializedFields.add( fieldName );
return loadedValue;
}
else {
return value;
}
}

@Override
public String toString() {
return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')';
}

/* --- */

@Override
public boolean readBoolean(Object obj, String name, boolean oldValue) {
return (Boolean) intercept( obj, name, oldValue );
}

@Override
public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public byte readByte(Object obj, String name, byte oldValue) {
return (Byte) intercept( obj, name, oldValue );
}

@Override
public byte writeByte(Object obj, String name, byte oldValue, byte newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public char readChar(Object obj, String name, char oldValue) {
return (Character) intercept( obj, name, oldValue );
}

@Override
public char writeChar(Object obj, String name, char oldValue, char newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public short readShort(Object obj, String name, short oldValue) {
return (Short) intercept( obj, name, oldValue );
}

@Override
public short writeShort(Object obj, String name, short oldValue, short newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public int readInt(Object obj, String name, int oldValue) {
return (Integer) intercept( obj, name, oldValue );
}

@Override
public int writeInt(Object obj, String name, int oldValue, int newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public float readFloat(Object obj, String name, float oldValue) {
return (Float) intercept( obj, name, oldValue );
}

@Override
public float writeFloat(Object obj, String name, float oldValue, float newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public double readDouble(Object obj, String name, double oldValue) {
return (Double) intercept( obj, name, oldValue );
}

@Override
public double writeDouble(Object obj, String name, double oldValue, double newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public long readLong(Object obj, String name, long oldValue) {
return (Long) intercept( obj, name, oldValue );
}

@Override
public long writeLong(Object obj, String name, long oldValue, long newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}

@Override
public Object readObject(Object obj, String name, Object oldValue) {
return intercept( obj, name, oldValue );
}

@Override
public Object writeObject(Object obj, String name, Object oldValue, Object newValue) {
if ( lazyFields != null && lazyFields.contains( name ) ) {
initializedFields.add( name );
}
return newValue;
}
}
@@ -0,0 +1,11 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/

/**
* interceptor implementations
*/
package org.hibernate.bytecode.enhance.spi.interceptor;
Expand Up @@ -525,6 +525,7 @@ public void bind() {
if ( cascadeStrategy != null && cascadeStrategy.indexOf( "delete-orphan" ) >= 0 ) {
collection.setOrphanDelete( true );
}
binder.setLazy( collection.isLazy() );
binder.setAccessType( accessType );
binder.setProperty( property );
binder.setInsertable( insertable );
Expand Down
Expand Up @@ -485,6 +485,8 @@ protected void dirtyCheck(final FlushEntityEvent event) throws HibernateExceptio
if ( entity instanceof SelfDirtinessTracker ) {
if ( ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes() ) {
dirtyProperties = persister.resolveAttributeIndexes( ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() );
} else {
dirtyProperties = new int[0];
}
}
else {
Expand Down
Expand Up @@ -568,7 +568,7 @@ public AbstractEntityPersister(

// PROPERTIES

final boolean lazyAvailable = isInstrumented();
final boolean lazyAvailable = isInstrumented() || entityMetamodel.isLazyLoadingBytecodeEnhanced();

int hydrateSpan = entityMetamodel.getPropertySpan();
propertyColumnSpans = new int[hydrateSpan];
Expand Down Expand Up @@ -4305,7 +4305,8 @@ public boolean hasSubclasses() {
}

public boolean hasProxy() {
return entityMetamodel.isLazy();
// skip proxy instantiation if entity is bytecode enhanced
return entityMetamodel.isLazy() && !entityMetamodel.isLazyLoadingBytecodeEnhanced();
}

public IdentifierGenerator getIdentifierGenerator() throws HibernateException {
Expand Down

0 comments on commit c6fa2b1

Please sign in to comment.