You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/09/06 16:52:24 UTC
(cayenne) branch master updated (52d01165a -> 5781b42a4)
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
from 52d01165a CAY-3010 LocalDateTime and friends should be accessed directly via JDBC
new aab6333f8 Cleanup: no need to use reflection for expressions
new 38b21baca Cleanup: no need for reflection in EnumConverter
new a653e3dff Ugh
new e19449368 CAY-3012 Improve reflection performance
new 48a00c8a8 CAY-3012 Improve reflection performance
new ae18b6852 CAY-3012 Improve reflection performance
new 5781b42a4 CAY-3012 Improve reflection performance
The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
RELEASE-NOTES.txt | 1 +
.../cayenne/access/DataDomainQueryAction.java | 28 ++---
.../translator/sqltemplate/parser/ASTMethod.java | 7 +-
.../org/apache/cayenne/exp/ExpressionFactory.java | 134 +++++++--------------
.../org/apache/cayenne/graph/ChildDiffLoader.java | 17 +--
.../apache/cayenne/reflect/AbstractCallback.java | 41 +++++++
.../apache/cayenne/reflect/CallbackOnEntity.java | 24 ++--
.../apache/cayenne/reflect/CallbackOnListener.java | 30 ++---
.../apache/cayenne/reflect/DefaultConstructor.java | 69 +++++++++++
.../org/apache/cayenne/reflect/EnumConverter.java | 18 +--
.../cayenne/reflect/FieldEmbeddableDescriptor.java | 9 +-
.../cayenne/reflect/PersistentDescriptor.java | 25 +---
.../reflect/PersistentDescriptorFactory.java | 1 -
.../apache/cayenne/map/MockCallingBackEntity.java | 8 ++
.../cayenne/reflect/CallbackOnEntityTest.java | 20 +++
15 files changed, 233 insertions(+), 199 deletions(-)
create mode 100644 cayenne/src/main/java/org/apache/cayenne/reflect/DefaultConstructor.java
(cayenne) 05/07: CAY-3012 Improve reflection performance
Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 48a00c8a8ac8fc844fbb5ddd8ab28e733fad2751
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:30:08 2026 -0400
CAY-3012 Improve reflection performance
---
.../org/apache/cayenne/reflect/PersistentDescriptor.java | 14 ++------------
.../cayenne/reflect/PersistentDescriptorFactory.java | 1 -
2 files changed, 2 insertions(+), 13 deletions(-)
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
index f6207d624..6a651b8ef 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
@@ -45,8 +45,6 @@ import java.util.Map.Entry;
*/
public class PersistentDescriptor implements ClassDescriptor {
- static final Integer HOLLOW_STATE = PersistenceState.HOLLOW;
-
protected ClassDescriptor superclassDescriptor;
// compiled properties ...
@@ -56,7 +54,6 @@ public class PersistentDescriptor implements ClassDescriptor {
protected Map<String, PropertyDescriptor> properties;
protected Map<String, ClassDescriptor> subclassDescriptors;
- protected Accessor persistenceStateAccessor;
protected ObjEntity entity;
protected Collection<DbEntity> rootDbEntities;
@@ -234,16 +231,13 @@ public class PersistentDescriptor implements ClassDescriptor {
return additionalDbEntities;
}
+ @Override
public boolean isFault(Object object) {
if (superclassDescriptor != null) {
return superclassDescriptor.isFault(object);
}
- if (object == null) {
- return false;
- }
-
- return HOLLOW_STATE.equals(persistenceStateAccessor.getValue(object));
+ return object instanceof Persistent persistent && persistent.getPersistenceState() == PersistenceState.HOLLOW;
}
public Class<?> getObjectClass() {
@@ -417,10 +411,6 @@ public class PersistentDescriptor implements ClassDescriptor {
return true;
}
- public void setPersistenceStateAccessor(Accessor persistenceStateAccessor) {
- this.persistenceStateAccessor = persistenceStateAccessor;
- }
-
public void setEntity(ObjEntity entity) {
this.entity = entity;
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java
index e573f4501..b4cb5ae52 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java
@@ -74,7 +74,6 @@ public abstract class PersistentDescriptorFactory implements ClassDescriptorFact
descriptor.setEntity(entity);
descriptor.setSuperclassDescriptor(superDescriptor);
descriptor.setObjectClass(entityClass);
- descriptor.setPersistenceStateAccessor(new BeanAccessor(entityClass, "persistenceState", Integer.TYPE));
// only include this entity attributes and skip superclasses...
for (ObjAttribute attribute : descriptor.getEntity().getDeclaredAttributes()) {
(cayenne) 07/07: CAY-3012 Improve reflection performance
Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 5781b42a49296783cd19ec0384a466a6e3f64904
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:30:08 2026 -0400
CAY-3012 Improve reflection performance
---
RELEASE-NOTES.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 7e99ec3a1..24428e7bc 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -18,6 +18,7 @@ CAY-3000 Modeler: unifying the main views of all model objects
CAY-3004 Modeler: Rework DbEntity PK generation UI to match the XML model
CAY-3007 Deprecate SelectById
CAY-3010 LocalDateTime and friends should be accessed directly via JDBC
+CAY-3012 Improve reflection performance
Bug Fixes:
(cayenne) 03/07: Ugh
Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit a653e3dff00366b246ae409160eccc0deb46c3ad
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:19:50 2026 -0400
Ugh
---
.../cayenne/access/translator/sqltemplate/parser/ASTMethod.java | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/parser/ASTMethod.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/parser/ASTMethod.java
index bd4076e19..5d71765fb 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/parser/ASTMethod.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/parser/ASTMethod.java
@@ -19,10 +19,10 @@
package org.apache.cayenne.access.translator.sqltemplate.parser;
-import java.lang.reflect.Method;
-
-import org.apache.cayenne.reflect.PropertyUtils;
import org.apache.cayenne.access.translator.sqltemplate.Context;
+import org.apache.cayenne.reflect.PropertyUtils;
+
+import java.lang.reflect.Method;
/**
* @since 4.1
@@ -88,7 +88,6 @@ public class ASTMethod extends IdentifierNode {
return m.invoke(parentObject, arguments);
} catch (Exception ignored) {
// continue
- System.out.println("test");
}
}
}
(cayenne) 01/07: Cleanup: no need to use reflection for expressions
Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit aab6333f8d49919e144459719fe57a0d8629520c
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:12:44 2026 -0400
Cleanup: no need to use reflection for expressions
---
.../org/apache/cayenne/exp/ExpressionFactory.java | 134 +++++++--------------
1 file changed, 42 insertions(+), 92 deletions(-)
diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java b/cayenne/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index 0a41cbed8..61e24fa2c 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -78,7 +78,6 @@ import org.apache.cayenne.query.FluentSelect;
import java.io.Reader;
import java.io.StringReader;
-import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -100,105 +99,56 @@ public class ExpressionFactory {
*/
public static final char SPLIT_SEPARATOR = '|';
- private static Constructor<? extends SimpleNode>[] typeLookup;
private static volatile int autoAliasId;
private static final int PARSE_BUFFER_MAX_SIZE = 4096;
- static {
- // make sure all types are small integers, then we can use them as indexes in lookup array
- int[] allTypes = new int[] { Expression.AND, Expression.OR, Expression.NOT, Expression.EQUAL_TO,
- Expression.NOT_EQUAL_TO, Expression.LESS_THAN, Expression.GREATER_THAN, Expression.LESS_THAN_EQUAL_TO,
- Expression.GREATER_THAN_EQUAL_TO, Expression.BETWEEN, Expression.IN, Expression.LIKE,
- Expression.LIKE_IGNORE_CASE, Expression.ADD, Expression.SUBTRACT, Expression.MULTIPLY,
- Expression.DIVIDE, Expression.NEGATIVE, Expression.OBJ_PATH, Expression.DB_PATH, Expression.LIST,
- Expression.NOT_BETWEEN, Expression.NOT_IN, Expression.NOT_LIKE, Expression.NOT_LIKE_IGNORE_CASE,
- Expression.TRUE, Expression.FALSE, Expression.BITWISE_NOT, Expression.BITWISE_AND,
- Expression.BITWISE_OR, Expression.BITWISE_XOR, Expression.BITWISE_LEFT_SHIFT,
- Expression.BITWISE_RIGHT_SHIFT };
-
- int max = 0;
- for (int type : allTypes) {
- // sanity check....
- if (type > 500) {
- throw new RuntimeException("Types values are too big: " + type);
- } else if (type < 0) {
- throw new RuntimeException("Types values are too small: " + type);
- }
- if (type > max) {
- max = type;
- }
- }
-
- // now we know that if types are used as indexes,
- // they will fit in array "max + 1" long (though gaps are possible)
- @SuppressWarnings("unchecked")
- Constructor<? extends SimpleNode>[] lookupTable = (Constructor<? extends SimpleNode>[]) new Constructor[max + 1];
- typeLookup = lookupTable;
-
- try {
- typeLookup[Expression.AND] = ASTAnd.class.getDeclaredConstructor();
- typeLookup[Expression.OR] = ASTOr.class.getDeclaredConstructor();
- typeLookup[Expression.BETWEEN] = ASTBetween.class.getDeclaredConstructor();
- typeLookup[Expression.NOT_BETWEEN] = ASTNotBetween.class.getDeclaredConstructor();
-
- // binary types
- typeLookup[Expression.EQUAL_TO] = ASTEqual.class.getDeclaredConstructor();
- typeLookup[Expression.NOT_EQUAL_TO] = ASTNotEqual.class.getDeclaredConstructor();
- typeLookup[Expression.LESS_THAN] = ASTLess.class.getDeclaredConstructor();
- typeLookup[Expression.GREATER_THAN] = ASTGreater.class.getDeclaredConstructor();
- typeLookup[Expression.LESS_THAN_EQUAL_TO] = ASTLessOrEqual.class.getDeclaredConstructor();
- typeLookup[Expression.GREATER_THAN_EQUAL_TO] = ASTGreaterOrEqual.class.getDeclaredConstructor();
- typeLookup[Expression.IN] = ASTIn.class.getDeclaredConstructor();
- typeLookup[Expression.NOT_IN] = ASTNotIn.class.getDeclaredConstructor();
- typeLookup[Expression.LIKE] = ASTLike.class.getDeclaredConstructor();
- typeLookup[Expression.LIKE_IGNORE_CASE] = ASTLikeIgnoreCase.class.getDeclaredConstructor();
- typeLookup[Expression.NOT_LIKE] = ASTNotLike.class.getDeclaredConstructor();
- typeLookup[Expression.NOT_LIKE_IGNORE_CASE] = ASTNotLikeIgnoreCase.class.getDeclaredConstructor();
- typeLookup[Expression.ADD] = ASTAdd.class.getDeclaredConstructor();
- typeLookup[Expression.SUBTRACT] = ASTSubtract.class.getDeclaredConstructor();
- typeLookup[Expression.MULTIPLY] = ASTMultiply.class.getDeclaredConstructor();
- typeLookup[Expression.DIVIDE] = ASTDivide.class.getDeclaredConstructor();
-
- typeLookup[Expression.NOT] = ASTNot.class.getDeclaredConstructor();
- typeLookup[Expression.NEGATIVE] = ASTNegate.class.getDeclaredConstructor();
- typeLookup[Expression.OBJ_PATH] = ASTObjPath.class.getDeclaredConstructor();
- typeLookup[Expression.DB_PATH] = ASTDbPath.class.getDeclaredConstructor();
- typeLookup[Expression.LIST] = ASTList.class.getDeclaredConstructor();
-
- typeLookup[Expression.TRUE] = ASTTrue.class.getDeclaredConstructor();
- typeLookup[Expression.FALSE] = ASTFalse.class.getDeclaredConstructor();
-
- typeLookup[Expression.BITWISE_NOT] = ASTBitwiseNot.class.getDeclaredConstructor();
- typeLookup[Expression.BITWISE_OR] = ASTBitwiseOr.class.getDeclaredConstructor();
- typeLookup[Expression.BITWISE_AND] = ASTBitwiseAnd.class.getDeclaredConstructor();
- typeLookup[Expression.BITWISE_XOR] = ASTBitwiseXor.class.getDeclaredConstructor();
- typeLookup[Expression.BITWISE_LEFT_SHIFT] = ASTBitwiseLeftShift.class.getDeclaredConstructor();
- typeLookup[Expression.BITWISE_RIGHT_SHIFT] = ASTBitwiseRightShift.class.getDeclaredConstructor();
- } catch (NoSuchMethodException ex) {
- throw new ExpressionException("Wrong expression type found", ex);
- }
- }
-
/**
- * Creates a new expression for the type requested. If type is unknown,
- * ExpressionException is thrown.
+ * Creates a new expression for the type requested. If type is unknown, xpressionException is thrown.
*/
public static Expression expressionOfType(int type) {
- if (type < 0 || type >= typeLookup.length) {
- throw new ExpressionException("Bad expression type: " + type);
- }
-
- if (typeLookup[type] == null) {
- throw new ExpressionException("Bad expression type: " + type);
- }
+ return switch (type) {
+ case Expression.AND -> new ASTAnd();
+ case Expression.OR -> new ASTOr();
+ case Expression.BETWEEN -> new ASTBetween();
+ case Expression.NOT_BETWEEN -> new ASTNotBetween();
- // expected this
- try {
- return typeLookup[type].newInstance();
- } catch (Exception ex) {
- throw new ExpressionException("Error creating expression", ex);
- }
+ // binary types
+ case Expression.EQUAL_TO -> new ASTEqual();
+ case Expression.NOT_EQUAL_TO -> new ASTNotEqual();
+ case Expression.LESS_THAN -> new ASTLess();
+ case Expression.GREATER_THAN -> new ASTGreater();
+ case Expression.LESS_THAN_EQUAL_TO -> new ASTLessOrEqual();
+ case Expression.GREATER_THAN_EQUAL_TO -> new ASTGreaterOrEqual();
+ case Expression.IN -> new ASTIn();
+ case Expression.NOT_IN -> new ASTNotIn();
+ case Expression.LIKE -> new ASTLike();
+ case Expression.LIKE_IGNORE_CASE -> new ASTLikeIgnoreCase();
+ case Expression.NOT_LIKE -> new ASTNotLike();
+ case Expression.NOT_LIKE_IGNORE_CASE -> new ASTNotLikeIgnoreCase();
+ case Expression.ADD -> new ASTAdd();
+ case Expression.SUBTRACT -> new ASTSubtract();
+ case Expression.MULTIPLY -> new ASTMultiply();
+ case Expression.DIVIDE -> new ASTDivide();
+
+ case Expression.NOT -> new ASTNot();
+ case Expression.NEGATIVE -> new ASTNegate();
+ case Expression.OBJ_PATH -> new ASTObjPath();
+ case Expression.DB_PATH -> new ASTDbPath();
+ case Expression.LIST -> new ASTList();
+
+ case Expression.TRUE -> new ASTTrue();
+ case Expression.FALSE -> new ASTFalse();
+
+ case Expression.BITWISE_NOT -> new ASTBitwiseNot();
+ case Expression.BITWISE_OR -> new ASTBitwiseOr();
+ case Expression.BITWISE_AND -> new ASTBitwiseAnd();
+ case Expression.BITWISE_XOR -> new ASTBitwiseXor();
+ case Expression.BITWISE_LEFT_SHIFT -> new ASTBitwiseLeftShift();
+ case Expression.BITWISE_RIGHT_SHIFT -> new ASTBitwiseRightShift();
+
+ default -> throw new ExpressionException("Bad expression type: " + type);
+ };
}
/**
(cayenne) 06/07: CAY-3012 Improve reflection performance
Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit ae18b685279664f1e5174cdb5818f48971412995
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:30:08 2026 -0400
CAY-3012 Improve reflection performance
---
.../apache/cayenne/reflect/AbstractCallback.java | 41 ++++++++++++++++++++++
.../apache/cayenne/reflect/CallbackOnEntity.java | 24 ++++++-------
.../apache/cayenne/reflect/CallbackOnListener.java | 30 +++++++---------
.../apache/cayenne/map/MockCallingBackEntity.java | 8 +++++
.../cayenne/reflect/CallbackOnEntityTest.java | 20 +++++++++++
5 files changed, 92 insertions(+), 31 deletions(-)
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java b/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java
index 4703293bb..840225b0f 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/AbstractCallback.java
@@ -18,6 +18,13 @@
****************************************************************/
package org.apache.cayenne.reflect;
+import org.apache.cayenne.CayenneRuntimeException;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+
/**
* Defines a callback operation.
*
@@ -25,5 +32,39 @@ package org.apache.cayenne.reflect;
*/
abstract class AbstractCallback {
+ /**
+ * The uniform shape every callback handle is adapted to, so that it can be called with "invokeExact".
+ */
+ static final MethodType CALLBACK_TYPE = MethodType.methodType(void.class, Object.class);
+
abstract void performCallback(Object entity);
+
+ /**
+ * Turns a validated (and, if needed, made accessible) callback method into a method handle.
+ */
+ static MethodHandle unreflect(Method method) {
+ try {
+ return MethodHandles.lookup().unreflect(method);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Inaccessible callback method: " + method, e);
+ }
+ }
+
+ /**
+ * Rethrows an exception raised by a callback. Unchecked exceptions and errors propagate as they are, checked
+ * exceptions are wrapped.
+ */
+ static RuntimeException callbackFailure(String callbackName, Throwable th) {
+ if (th instanceof RuntimeException re) {
+ return re;
+ }
+ if (th instanceof Error error) {
+ throw error;
+ }
+ return new CayenneRuntimeException("Error invoking callback method " + callbackName, th);
+ }
+
+ static String callbackName(Method method) {
+ return method.getDeclaringClass().getName() + "." + method.getName();
+ }
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java
index ecf5f82b7..44b4f2945 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnEntity.java
@@ -18,10 +18,10 @@
****************************************************************/
package org.apache.cayenne.reflect;
+import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.util.Util;
/**
@@ -33,7 +33,8 @@ import org.apache.cayenne.util.Util;
*/
class CallbackOnEntity extends AbstractCallback {
- private final Method callbackMethod;
+ private final String callbackName;
+ private final MethodHandle callbackHandle;
CallbackOnEntity(Class<?> objectClass, String methodName) throws IllegalArgumentException {
this(findMethod(objectClass, methodName));
@@ -47,28 +48,25 @@ class CallbackOnEntity extends AbstractCallback {
throw new IllegalArgumentException("Class " + method.getDeclaringClass().getName()
+ " has no valid callback method '" + method.getName() + "'");
}
- this.callbackMethod = method;
- if (!Util.isAccessible(callbackMethod)) {
- callbackMethod.setAccessible(true);
+ if (!Util.isAccessible(method)) {
+ method.setAccessible(true);
}
+ this.callbackName = callbackName(method);
+ this.callbackHandle = unreflect(method).asType(CALLBACK_TYPE);
}
@Override
public void performCallback(Object entity) {
try {
- callbackMethod.invoke(entity, (Object[]) null);
- } catch (Exception e) {
- throw new CayenneRuntimeException("Error invoking entity callback method "
- + callbackMethod.getName(), e);
+ callbackHandle.invokeExact(entity);
+ } catch (Throwable th) {
+ throw callbackFailure(callbackName, th);
}
}
@Override
public String toString() {
- return "callback-entity: "
- + callbackMethod.getDeclaringClass().getName()
- + "."
- + callbackMethod.getName();
+ return "callback-entity: " + callbackName;
}
static private boolean validateMethod(Method method) {
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
index e23e2f9a0..fa8c1a1d0 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
@@ -18,10 +18,10 @@
****************************************************************/
package org.apache.cayenne.reflect;
+import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.util.Util;
/**
@@ -33,8 +33,8 @@ import org.apache.cayenne.util.Util;
*/
class CallbackOnListener extends AbstractCallback {
- private Method callbackMethod;
- private Object listener;
+ private final String callbackName;
+ private final MethodHandle callbackHandle;
CallbackOnListener(Object listener, String methodName)
throws IllegalArgumentException {
@@ -48,8 +48,9 @@ class CallbackOnListener extends AbstractCallback {
throw new IllegalArgumentException("Null listener");
}
- this.callbackMethod = findMethod(listener.getClass(), methodName, entityType);
- this.listener = listener;
+ Method method = findMethod(listener.getClass(), methodName, entityType);
+ this.callbackName = callbackName(method);
+ this.callbackHandle = unreflect(method).bindTo(listener).asType(CALLBACK_TYPE);
}
CallbackOnListener(Object listener, Method method, Class<?> entityType)
@@ -64,29 +65,22 @@ class CallbackOnListener extends AbstractCallback {
+ method.getName());
}
- this.callbackMethod = method;
- this.listener = listener;
+ this.callbackName = callbackName(method);
+ this.callbackHandle = unreflect(method).bindTo(listener).asType(CALLBACK_TYPE);
}
@Override
public void performCallback(Object entity) {
try {
- callbackMethod.invoke(listener, entity);
- }
- catch (Exception e) {
- throw new CayenneRuntimeException(
- "Error invoking entity listener callback method "
- + callbackMethod.getName(),
- e);
+ callbackHandle.invokeExact(entity);
+ } catch (Throwable th) {
+ throw callbackFailure(callbackName, th);
}
}
@Override
public String toString() {
- return "callback-listener: "
- + callbackMethod.getDeclaringClass().getName()
- + "."
- + callbackMethod.getName();
+ return "callback-listener: " + callbackName;
}
private boolean verifyMethod(Method method, Class<?> entityType) {
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java b/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java
index 36b0bb038..a4332afd4 100644
--- a/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java
+++ b/cayenne/src/test/java/org/apache/cayenne/map/MockCallingBackEntity.java
@@ -44,4 +44,12 @@ public class MockCallingBackEntity {
void defaultCallback() {
defaultCallbackInvoked = true;
}
+
+ public void uncheckedThrowingCallback() {
+ throw new IllegalStateException("unchecked");
+ }
+
+ public void checkedThrowingCallback() throws Exception {
+ throw new Exception("checked");
+ }
}
diff --git a/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java b/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java
index 861983bf0..6feeffc2e 100644
--- a/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/reflect/CallbackOnEntityTest.java
@@ -18,9 +18,11 @@
****************************************************************/
package org.apache.cayenne.reflect;
+import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.map.MockCallingBackEntity;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -83,6 +85,24 @@ public class CallbackOnEntityTest {
assertTrue(e.defaultCallbackInvoked);
}
+ @Test
+ public void uncheckedExceptionPropagatesAsIs() {
+ CallbackOnEntity callback = new CallbackOnEntity(MockCallingBackEntity.class, "uncheckedThrowingCallback");
+
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> callback.performCallback(new MockCallingBackEntity()));
+ assertEquals("unchecked", e.getMessage());
+ }
+
+ @Test
+ public void checkedExceptionIsWrapped() {
+ CallbackOnEntity callback = new CallbackOnEntity(MockCallingBackEntity.class, "checkedThrowingCallback");
+
+ CayenneRuntimeException e = assertThrows(CayenneRuntimeException.class,
+ () -> callback.performCallback(new MockCallingBackEntity()));
+ assertEquals("checked", e.getCause().getMessage());
+ }
+
@Test
public void staticCallbackMethod() {
assertThrows(IllegalArgumentException.class,
(cayenne) 04/07: CAY-3012 Improve reflection performance
Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit e19449368c6dc46ef32ffb378600be0c15a894ca
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:30:08 2026 -0400
CAY-3012 Improve reflection performance
---
.../cayenne/access/DataDomainQueryAction.java | 28 +++------
.../org/apache/cayenne/graph/ChildDiffLoader.java | 17 +-----
.../apache/cayenne/reflect/DefaultConstructor.java | 69 ++++++++++++++++++++++
.../cayenne/reflect/FieldEmbeddableDescriptor.java | 9 +--
.../cayenne/reflect/PersistentDescriptor.java | 11 ++--
5 files changed, 87 insertions(+), 47 deletions(-)
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java b/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
index bfe9fb3a6..762201790 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
@@ -54,6 +54,7 @@ import org.apache.cayenne.query.QueryRouter;
import org.apache.cayenne.query.RefreshQuery;
import org.apache.cayenne.query.RelationshipQuery;
import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.DefaultConstructor;
import org.apache.cayenne.reflect.LifecycleCallbackRegistry;
import org.apache.cayenne.tx.BaseTransaction;
import org.apache.cayenne.tx.ReadOnlyTransaction;
@@ -64,7 +65,6 @@ import org.apache.cayenne.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -849,15 +849,10 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
EmbeddableResultSegment resultSegment = (EmbeddableResultSegment) metadata.getResultSetMapping().getFirst();
Embeddable embeddable = resultSegment.getEmbeddable();
Class<? extends EmbeddableObject> embeddableClass = objectFactory.getJavaClass(embeddable.getClassName());
+ DefaultConstructor<? extends EmbeddableObject> constructor = new DefaultConstructor<>(embeddableClass);
List<EmbeddableObject> result = new ArrayList<>(mainRows.size());
mainRows.forEach(dataRow -> {
- EmbeddableObject eo;
- try {
- eo = embeddableClass.getDeclaredConstructor().newInstance();
- } catch (Exception e) {
- throw new CayenneRuntimeException("Unable to materialize embeddable '%s'", e,
- embeddable.getClassName());
- }
+ EmbeddableObject eo = constructor.newInstance();
dataRow.forEach(eo::writePropertyDirectly);
result.add(eo);
});
@@ -989,17 +984,12 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
Embeddable embeddable = resultSegment.getEmbeddable();
Class<? extends EmbeddableObject> embeddableClass = objectFactory
.getJavaClass(embeddable.getClassName());
- try {
- Constructor<? extends EmbeddableObject> declaredConstructor = embeddableClass
- .getDeclaredConstructor();
- for (Object[] row : result) {
- DataRow dataRow = (DataRow) row[i];
- EmbeddableObject eo = declaredConstructor.newInstance();
- dataRow.forEach(eo::writePropertyDirectly);
- row[i] = eo;
- }
- } catch (Exception e) {
- throw new CayenneRuntimeException("Unable to materialize embeddable '%s'", e, embeddable.getClassName());
+ DefaultConstructor<? extends EmbeddableObject> constructor = new DefaultConstructor<>(embeddableClass);
+ for (Object[] row : result) {
+ DataRow dataRow = (DataRow) row[i];
+ EmbeddableObject eo = constructor.newInstance();
+ dataRow.forEach(eo::writePropertyDirectly);
+ row[i] = eo;
}
}
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java b/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
index a2f4bca35..6d6ff8a17 100644
--- a/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
+++ b/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
@@ -28,7 +28,6 @@ import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.QueryResponse;
-import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.reflect.ArcProperty;
@@ -90,20 +89,8 @@ public class ChildDiffLoader implements GraphChangeHandler {
throw new NullPointerException("Null entity name in id " + id);
}
- ObjEntity entity = context.getEntityResolver().getObjEntity(id.getEntityName());
- if (entity == null) {
- throw new IllegalArgumentException("Entity not mapped with Cayenne: " + id);
- }
-
- Persistent persistent;
- Class<? extends Persistent> javaClass = context.getEntityResolver().getObjectFactory()
- .getJavaClass(entity.getJavaClassName());
- try {
- persistent = javaClass.getDeclaredConstructor().newInstance();
- } catch (Exception ex) {
- throw new CayenneRuntimeException("Error instantiating object.", ex);
- }
-
+ ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(id.getEntityName());
+ Persistent persistent = (Persistent) descriptor.createObject();
persistent.setObjectId(id);
context.registerNewObject(persistent);
} finally {
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/DefaultConstructor.java b/cayenne/src/main/java/org/apache/cayenne/reflect/DefaultConstructor.java
new file mode 100644
index 000000000..216f79f0a
--- /dev/null
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/DefaultConstructor.java
@@ -0,0 +1,69 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.reflect;
+
+import org.apache.cayenne.CayenneRuntimeException;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Constructor;
+
+/**
+ * A cached handle to the no-arg constructor of a class. Resolving a constructor via
+ * {@link Class#getDeclaredConstructor(Class[])} is comparatively expensive (a lookup, a defensive copy and an access
+ * check), so callers that instantiate the same class repeatedly should resolve it once and keep this object around.
+ *
+ * @since 5.0
+ */
+public final class DefaultConstructor<T> {
+
+ private static final MethodType NO_ARGS = MethodType.methodType(Object.class);
+
+ private final Class<T> type;
+ private final MethodHandle handle;
+
+ public DefaultConstructor(Class<T> type) {
+ this.type = type;
+ try {
+ Constructor<T> constructor = type.getDeclaredConstructor();
+ if (!constructor.canAccess(null)) {
+ constructor.setAccessible(true);
+ }
+ this.handle = MethodHandles.lookup().unreflectConstructor(constructor).asType(NO_ARGS);
+ } catch (NoSuchMethodException e) {
+ throw new CayenneRuntimeException("No default constructor found for class '%s'", e, type.getName());
+ } catch (IllegalAccessException | RuntimeException e) {
+ throw new CayenneRuntimeException("Default constructor of class '%s' is inaccessible", e, type.getName());
+ }
+ }
+
+ public Class<T> getType() {
+ return type;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T newInstance() {
+ try {
+ return (T) handle.invokeExact();
+ } catch (Throwable e) {
+ throw new CayenneRuntimeException("Error creating object of class '%s'", e, type.getName());
+ }
+ }
+}
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/FieldEmbeddableDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/reflect/FieldEmbeddableDescriptor.java
index d442672e0..8aec52ccb 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/FieldEmbeddableDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/FieldEmbeddableDescriptor.java
@@ -30,6 +30,7 @@ import org.apache.cayenne.map.Embeddable;
public class FieldEmbeddableDescriptor implements EmbeddableDescriptor {
protected Class<?> embeddableClass;
+ protected DefaultConstructor<?> constructor;
protected Embeddable embeddable;
protected Accessor ownerAccessor;
protected Accessor embeddedPropertyAccessor;
@@ -38,17 +39,13 @@ public class FieldEmbeddableDescriptor implements EmbeddableDescriptor {
String ownerProperty, String embeddedPropertyProperty) {
this.embeddable = embeddable;
this.embeddableClass = objectFactory.getJavaClass(embeddable.getClassName());
+ this.constructor = new DefaultConstructor<>(embeddableClass);
this.ownerAccessor = new FieldAccessor(embeddableClass, ownerProperty, Persistent.class);
this.embeddedPropertyAccessor = new FieldAccessor(embeddableClass, embeddedPropertyProperty, String.class);
}
public Object createObject(Object owner, String embeddedProperty) {
- Object embeddable;
- try {
- embeddable = embeddableClass.getDeclaredConstructor().newInstance();
- } catch (Throwable e) {
- throw new PropertyException("Error creating embeddable object of class '" + embeddableClass.getName() + "'", e);
- }
+ Object embeddable = constructor.newInstance();
ownerAccessor.setValue(embeddable, owner);
embeddedPropertyAccessor.setValue(embeddable, embeddedProperty);
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
index 9f981765e..f6207d624 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/PersistentDescriptor.java
@@ -18,7 +18,6 @@
****************************************************************/
package org.apache.cayenne.reflect;
-import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.exp.Expression;
@@ -52,6 +51,7 @@ public class PersistentDescriptor implements ClassDescriptor {
// compiled properties ...
protected Class<?> objectClass;
+ protected DefaultConstructor<?> constructor;
protected Map<String, PropertyDescriptor> declaredProperties;
protected Map<String, PropertyDescriptor> properties;
@@ -252,6 +252,7 @@ public class PersistentDescriptor implements ClassDescriptor {
void setObjectClass(Class<?> objectClass) {
this.objectClass = objectClass;
+ this.constructor = objectClass != null ? new DefaultConstructor<>(objectClass) : null;
}
public ClassDescriptor getSubclassDescriptor(String entityName) {
@@ -319,15 +320,11 @@ public class PersistentDescriptor implements ClassDescriptor {
* Creates a new instance of a class described by this object.
*/
public Object createObject() {
- if (objectClass == null) {
+ if (constructor == null) {
throw new NullPointerException("Null objectClass. Descriptor wasn't initialized properly.");
}
- try {
- return objectClass.getDeclaredConstructor().newInstance();
- } catch (Throwable e) {
- throw new CayenneRuntimeException("Error creating object of class '" + objectClass.getName() + "'", e);
- }
+ return constructor.newInstance();
}
/**
(cayenne) 02/07: Cleanup: no need for reflection in EnumConverter
Posted by aa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 38b21bacadd6822a8ae04759fd91ad01df432fd3
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 6 12:18:09 2026 -0400
Cleanup: no need for reflection in EnumConverter
---
.../java/org/apache/cayenne/reflect/EnumConverter.java | 18 ++++++------------
1 file changed, 6 insertions(+), 12 deletions(-)
diff --git a/cayenne/src/main/java/org/apache/cayenne/reflect/EnumConverter.java b/cayenne/src/main/java/org/apache/cayenne/reflect/EnumConverter.java
index 6bc4fe0ce..a456c37c6 100644
--- a/cayenne/src/main/java/org/apache/cayenne/reflect/EnumConverter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/reflect/EnumConverter.java
@@ -29,23 +29,17 @@ import java.util.Objects;
class EnumConverter<T extends Enum<T> & ExtendedEnumeration> implements Converter<T> {
@Override
- @SuppressWarnings("unchecked")
public T convert(Object object, Class<T> type) {
if (ExtendedEnumeration.class.isAssignableFrom(type)) {
- ExtendedEnumeration[] values;
-
- try {
- values = (ExtendedEnumeration[]) type.getMethod("values").invoke(null);
- }
- catch (Exception e) {
- // unexpected, all enums should have values
- throw new CayenneRuntimeException(e);
+ T[] values = type.getEnumConstants();
+ if (values == null) {
+ throw new CayenneRuntimeException("Not an enum type: %s", type.getName());
}
- for (ExtendedEnumeration en : values) {
+ for (T en : values) {
if (Objects.deepEquals(en.getDatabaseValue(), object)) {
- return (T) en;
+ return en;
}
}
@@ -56,6 +50,6 @@ class EnumConverter<T extends Enum<T> & ExtendedEnumeration> implements Converte
return null;
}
- return (T) Enum.valueOf(type, object.toString());
+ return Enum.valueOf(type, object.toString());
}
}