Add instrumentation for hibernate 6 (#7773)
This commit is contained in:
parent
f074b93110
commit
d847bfcad5
|
@ -62,9 +62,9 @@ public class QueryInstrumentation implements TypeInstrumentation {
|
|||
return;
|
||||
}
|
||||
|
||||
VirtualField<Query, SessionInfo> criteriaVirtualField =
|
||||
VirtualField<Query, SessionInfo> queryVirtualField =
|
||||
VirtualField.find(Query.class, SessionInfo.class);
|
||||
SessionInfo sessionInfo = criteriaVirtualField.get(query);
|
||||
SessionInfo sessionInfo = queryVirtualField.get(query);
|
||||
|
||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||
hibernateOperation =
|
||||
|
|
|
@ -10,6 +10,7 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.
|
|||
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.v3_3.Hibernate3Singletons.instrumenter;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.any;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
|
@ -50,6 +51,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
|
|||
// Session synchronous methods we want to instrument.
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(takesArgument(0, any()))
|
||||
.and(
|
||||
namedOneOf(
|
||||
"save",
|
||||
|
|
|
@ -62,9 +62,9 @@ public class QueryInstrumentation implements TypeInstrumentation {
|
|||
return;
|
||||
}
|
||||
|
||||
VirtualField<Query, SessionInfo> criteriaVirtualField =
|
||||
VirtualField<Query, SessionInfo> queryVirtualField =
|
||||
VirtualField.find(Query.class, SessionInfo.class);
|
||||
SessionInfo sessionInfo = criteriaVirtualField.get(query);
|
||||
SessionInfo sessionInfo = queryVirtualField.get(query);
|
||||
|
||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||
hibernateOperation =
|
||||
|
|
|
@ -8,7 +8,6 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v4_0;
|
|||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.returns;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
@ -32,7 +31,7 @@ public class SessionFactoryInstrumentation implements TypeInstrumentation {
|
|||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return implementsInterface(
|
||||
named("org.hibernate.SessionFactory").or(named("org.hibernate.SessionBuilder")));
|
||||
namedOneOf("org.hibernate.SessionFactory", "org.hibernate.SessionBuilder"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.
|
|||
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.v4_0.Hibernate4Singletons.instrumenter;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.any;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
|
@ -51,6 +52,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
|
|||
// Session synchronous methods we want to instrument.
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(takesArgument(0, any()))
|
||||
.and(
|
||||
namedOneOf(
|
||||
"save",
|
||||
|
@ -68,7 +70,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
|
|||
// Handle the non-generic 'get' separately.
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(named("get").or(named("find")))
|
||||
.and(namedOneOf("get", "find"))
|
||||
.and(returns(Object.class))
|
||||
.and(takesArgument(0, String.class).or(takesArgument(0, Class.class))),
|
||||
SessionInstrumentation.class.getName() + "$SessionMethodAdvice");
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
plugins {
|
||||
id("otel.javaagent-instrumentation")
|
||||
}
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group.set("org.hibernate")
|
||||
module.set("hibernate-core")
|
||||
versions.set("[6.0.0.Final,)")
|
||||
assertInverse.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
library("org.hibernate:hibernate-core:6.0.0.Final")
|
||||
|
||||
implementation(project(":instrumentation:hibernate:hibernate-common:javaagent"))
|
||||
|
||||
testInstrumentation(project(":instrumentation:jdbc:javaagent"))
|
||||
// Added to ensure cross compatibility:
|
||||
testInstrumentation(project(":instrumentation:hibernate:hibernate-3.3:javaagent"))
|
||||
testInstrumentation(project(":instrumentation:hibernate:hibernate-4.0:javaagent"))
|
||||
testInstrumentation(project(":instrumentation:hibernate:hibernate-procedure-call-4.3:javaagent"))
|
||||
|
||||
testImplementation("com.h2database:h2:1.4.197")
|
||||
testImplementation("javax.xml.bind:jaxb-api:2.2.11")
|
||||
testImplementation("com.sun.xml.bind:jaxb-core:2.2.11")
|
||||
testImplementation("com.sun.xml.bind:jaxb-impl:2.2.11")
|
||||
testImplementation("javax.activation:activation:1.1.1")
|
||||
testImplementation("org.hsqldb:hsqldb:2.0.0")
|
||||
testImplementation("org.springframework.data:spring-data-jpa:3.0.0")
|
||||
}
|
||||
|
||||
otelJava {
|
||||
minJavaVersionSupported.set(JavaVersion.VERSION_11)
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
// TODO run tests both with and without experimental span attributes
|
||||
jvmArgs("-Dotel.instrumentation.hibernate.experimental-span-attributes=true")
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0;
|
||||
|
||||
import java.util.function.Function;
|
||||
import org.hibernate.SharedSessionContract;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
import org.hibernate.internal.StatelessSessionImpl;
|
||||
|
||||
public final class EntityNameUtil {
|
||||
|
||||
private EntityNameUtil() {}
|
||||
|
||||
private static String bestGuessEntityName(SharedSessionContract session, Object entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (session instanceof SessionImpl) {
|
||||
return ((SessionImpl) session).bestGuessEntityName(entity);
|
||||
} else if (session instanceof StatelessSessionImpl) {
|
||||
return ((StatelessSessionImpl) session).bestGuessEntityName(entity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Function<Object, String> bestGuessEntityName(SharedSessionContract session) {
|
||||
return (entity) -> bestGuessEntityName(session, entity);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateInstrumenterFactory;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation;
|
||||
|
||||
public class Hibernate6Singletons {
|
||||
|
||||
private static final Instrumenter<HibernateOperation, Void> INSTANCE =
|
||||
HibernateInstrumenterFactory.createInstrumenter("io.opentelemetry.hibernate-6.0");
|
||||
|
||||
public static Instrumenter<HibernateOperation, Void> instrumenter() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private Hibernate6Singletons() {}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import java.util.List;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class HibernateInstrumentationModule extends InstrumentationModule {
|
||||
|
||||
public HibernateInstrumentationModule() {
|
||||
super("hibernate", "hibernate-6.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
|
||||
return hasClassesNamed(
|
||||
// not present before 6.0
|
||||
"org.hibernate.query.Query");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return asList(
|
||||
new QueryInstrumentation(),
|
||||
new SessionFactoryInstrumentation(),
|
||||
new SessionInstrumentation(),
|
||||
new TransactionInstrumentation());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getOperationNameForQuery;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.v6_0.Hibernate6Singletons.instrumenter;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.hibernate.query.CommonQueryContract;
|
||||
import org.hibernate.query.Query;
|
||||
import org.hibernate.query.spi.SqmQuery;
|
||||
|
||||
public class QueryInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
||||
return hasClassesNamed("org.hibernate.query.CommonQueryContract");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return implementsInterface(named("org.hibernate.query.CommonQueryContract"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(
|
||||
namedOneOf(
|
||||
"list",
|
||||
"getResultList",
|
||||
"stream",
|
||||
"getResultStream",
|
||||
"uniqueResult",
|
||||
"getSingleResult",
|
||||
"getSingleResultOrNull",
|
||||
"uniqueResultOptional",
|
||||
"executeUpdate",
|
||||
"scroll")),
|
||||
QueryInstrumentation.class.getName() + "$QueryMethodAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class QueryMethodAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void startMethod(
|
||||
@Advice.This CommonQueryContract query,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
callDepth = CallDepth.forClass(HibernateOperation.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String queryString = null;
|
||||
if (query instanceof Query) {
|
||||
queryString = ((Query<?>) query).getQueryString();
|
||||
}
|
||||
if (query instanceof SqmQuery) {
|
||||
try {
|
||||
queryString = ((SqmQuery) query).getSqmStatement().toHqlString();
|
||||
} catch (RuntimeException exception) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
VirtualField<CommonQueryContract, SessionInfo> queryVirtualField =
|
||||
VirtualField.find(CommonQueryContract.class, SessionInfo.class);
|
||||
SessionInfo sessionInfo = queryVirtualField.get(query);
|
||||
|
||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||
hibernateOperation =
|
||||
new HibernateOperation(getOperationNameForQuery(queryString), sessionInfo);
|
||||
if (!instrumenter().shouldStart(parentContext, hibernateOperation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = instrumenter().start(parentContext, hibernateOperation);
|
||||
scope = context.makeCurrent();
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void endMethod(
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope != null) {
|
||||
scope.close();
|
||||
instrumenter().end(context, hibernateOperation, null, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.returns;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.hibernate.SharedSessionContract;
|
||||
|
||||
public class SessionFactoryInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
||||
return hasClassesNamed("org.hibernate.SessionFactory", "org.hibernate.SessionBuilder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return implementsInterface(
|
||||
namedOneOf("org.hibernate.SessionFactory", "org.hibernate.SessionBuilder"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(namedOneOf("openSession", "openStatelessSession"))
|
||||
.and(takesArguments(0))
|
||||
.and(returns(namedOneOf("org.hibernate.Session", "org.hibernate.StatelessSession"))),
|
||||
SessionFactoryInstrumentation.class.getName() + "$SessionFactoryAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class SessionFactoryAdvice {
|
||||
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void openSession(@Advice.Return SharedSessionContract session) {
|
||||
|
||||
VirtualField<SharedSessionContract, SessionInfo> virtualField =
|
||||
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
|
||||
virtualField.set(session, new SessionInfo());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.v6_0.Hibernate6Singletons.instrumenter;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.any;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.returns;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.hibernate.SharedSessionContract;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.query.CommonQueryContract;
|
||||
|
||||
public class SessionInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
||||
return hasClassesNamed("org.hibernate.SharedSessionContract");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return implementsInterface(named("org.hibernate.SharedSessionContract"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
|
||||
// Session synchronous methods we want to instrument.
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(takesArgument(0, any()))
|
||||
.and(
|
||||
namedOneOf(
|
||||
"save",
|
||||
"replicate",
|
||||
"saveOrUpdate",
|
||||
"update",
|
||||
"merge",
|
||||
"persist",
|
||||
"lock",
|
||||
"fireLock",
|
||||
"refresh",
|
||||
"insert",
|
||||
"delete")),
|
||||
SessionInstrumentation.class.getName() + "$SessionMethodAdvice");
|
||||
// Handle the non-generic 'get' separately.
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(namedOneOf("get", "find"))
|
||||
.and(returns(Object.class))
|
||||
.and(takesArgument(0, String.class).or(takesArgument(0, Class.class))),
|
||||
SessionInstrumentation.class.getName() + "$SessionMethodAdvice");
|
||||
|
||||
// These methods return some object that we want to instrument, and so the Advice will pin the
|
||||
// current SessionInfo to the returned object using a VirtualField.
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(namedOneOf("beginTransaction", "getTransaction"))
|
||||
.and(returns(named("org.hibernate.Transaction"))),
|
||||
SessionInstrumentation.class.getName() + "$GetTransactionAdvice");
|
||||
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod()
|
||||
.and(returns(implementsInterface(named("org.hibernate.query.CommonQueryContract")))),
|
||||
SessionInstrumentation.class.getName() + "$GetQueryAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class SessionMethodAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void startMethod(
|
||||
@Advice.This SharedSessionContract session,
|
||||
@Advice.Origin("#m") String name,
|
||||
@Advice.Origin("#d") String descriptor,
|
||||
@Advice.Argument(0) Object arg0,
|
||||
@Advice.Argument(value = 1, optional = true) Object arg1,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
callDepth = CallDepth.forClass(HibernateOperation.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualField<SharedSessionContract, SessionInfo> virtualField =
|
||||
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
|
||||
SessionInfo sessionInfo = virtualField.get(session);
|
||||
|
||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||
String entityName =
|
||||
getEntityName(descriptor, arg0, arg1, EntityNameUtil.bestGuessEntityName(session));
|
||||
hibernateOperation =
|
||||
new HibernateOperation(getSessionMethodOperationName(name), entityName, sessionInfo);
|
||||
if (!instrumenter().shouldStart(parentContext, hibernateOperation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = instrumenter().start(parentContext, hibernateOperation);
|
||||
scope = context.makeCurrent();
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void endMethod(
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope != null) {
|
||||
scope.close();
|
||||
instrumenter().end(context, hibernateOperation, null, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class GetQueryAdvice {
|
||||
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void getQuery(
|
||||
@Advice.This SharedSessionContract session, @Advice.Return CommonQueryContract query) {
|
||||
|
||||
VirtualField<SharedSessionContract, SessionInfo> sessionVirtualField =
|
||||
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
|
||||
VirtualField<CommonQueryContract, SessionInfo> queryVirtualField =
|
||||
VirtualField.find(CommonQueryContract.class, SessionInfo.class);
|
||||
|
||||
queryVirtualField.set(query, sessionVirtualField.get(session));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class GetTransactionAdvice {
|
||||
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void getTransaction(
|
||||
@Advice.This SharedSessionContract session, @Advice.Return Transaction transaction) {
|
||||
|
||||
VirtualField<SharedSessionContract, SessionInfo> sessionVirtualField =
|
||||
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
|
||||
VirtualField<Transaction, SessionInfo> transactionVirtualField =
|
||||
VirtualField.find(Transaction.class, SessionInfo.class);
|
||||
|
||||
transactionVirtualField.set(transaction, sessionVirtualField.get(session));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class GetCriteriaAdvice {
|
||||
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void getCriteria(
|
||||
@Advice.This SharedSessionContract session, @Advice.Return CriteriaQuery<?> criteria) {
|
||||
|
||||
VirtualField<SharedSessionContract, SessionInfo> sessionVirtualField =
|
||||
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
|
||||
VirtualField<CriteriaQuery<?>, SessionInfo> criteriaVirtualField =
|
||||
VirtualField.find(CriteriaQuery.class, SessionInfo.class);
|
||||
|
||||
criteriaVirtualField.set(criteria, sessionVirtualField.get(session));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
|
||||
import static io.opentelemetry.javaagent.instrumentation.hibernate.v6_0.Hibernate6Singletons.instrumenter;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.util.VirtualField;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation;
|
||||
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.hibernate.Transaction;
|
||||
|
||||
public class TransactionInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
||||
return hasClassesNamed("org.hibernate.Transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return implementsInterface(named("org.hibernate.Transaction"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyAdviceToMethod(
|
||||
isMethod().and(named("commit")).and(takesArguments(0)),
|
||||
TransactionInstrumentation.class.getName() + "$TransactionCommitAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class TransactionCommitAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void startCommit(
|
||||
@Advice.This Transaction transaction,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
callDepth = CallDepth.forClass(HibernateOperation.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualField<Transaction, SessionInfo> transactionVirtualField =
|
||||
VirtualField.find(Transaction.class, SessionInfo.class);
|
||||
SessionInfo sessionInfo = transactionVirtualField.get(transaction);
|
||||
|
||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||
hibernateOperation = new HibernateOperation("Transaction.commit", sessionInfo);
|
||||
if (!instrumenter().shouldStart(parentContext, hibernateOperation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = instrumenter().start(parentContext, hibernateOperation);
|
||||
scope = context.makeCurrent();
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void endCommit(
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth,
|
||||
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope != null) {
|
||||
scope.close();
|
||||
instrumenter().end(context, hibernateOperation, null, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import org.hibernate.Session
|
||||
import org.hibernate.SessionFactory
|
||||
import org.hibernate.cfg.Configuration
|
||||
import spock.lang.Shared
|
||||
|
||||
abstract class AbstractHibernateTest extends AgentInstrumentationSpecification {
|
||||
|
||||
@Shared
|
||||
protected SessionFactory sessionFactory
|
||||
|
||||
@Shared
|
||||
protected List<Value> prepopulated
|
||||
|
||||
def setupSpec() {
|
||||
sessionFactory = new Configuration().configure().buildSessionFactory()
|
||||
// Pre-populate the DB, so delete/update can be tested.
|
||||
Session writer = sessionFactory.openSession()
|
||||
writer.beginTransaction()
|
||||
prepopulated = new ArrayList<>()
|
||||
for (int i = 0; i < 5; i++) {
|
||||
prepopulated.add(new Value("Hello :) " + i))
|
||||
writer.save(prepopulated.get(i))
|
||||
}
|
||||
writer.getTransaction().commit()
|
||||
writer.close()
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
if (sessionFactory != null) {
|
||||
sessionFactory.close()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||
import jakarta.persistence.criteria.CriteriaBuilder
|
||||
import jakarta.persistence.criteria.CriteriaQuery
|
||||
import jakarta.persistence.criteria.Root
|
||||
import org.hibernate.Session
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CLIENT
|
||||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
||||
|
||||
class CriteriaTest extends AbstractHibernateTest {
|
||||
|
||||
def "test criteria query.#methodName"() {
|
||||
setup:
|
||||
runWithSpan("parent") {
|
||||
Session session = sessionFactory.openSession()
|
||||
session.beginTransaction()
|
||||
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder()
|
||||
CriteriaQuery<Value> createQuery = criteriaBuilder.createQuery(Value)
|
||||
Root<Value> root = createQuery.from(Value)
|
||||
createQuery.select(root)
|
||||
.where(criteriaBuilder.like(root.get("name"), "Hello"))
|
||||
.orderBy(criteriaBuilder.desc(root.get("name")))
|
||||
def query= session.createQuery(createQuery)
|
||||
interaction.call(query)
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "SELECT Value"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "SELECT db1.Value"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/^select /
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
methodName | interaction
|
||||
"getResultList" | { c -> c.getResultList() }
|
||||
"uniqueResult" | { c -> c.uniqueResult() }
|
||||
"getSingleResultOrNull" | { c -> c.getSingleResultOrNull() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||
import jakarta.persistence.EntityManager
|
||||
import jakarta.persistence.EntityManagerFactory
|
||||
import jakarta.persistence.EntityTransaction
|
||||
import jakarta.persistence.LockModeType
|
||||
import jakarta.persistence.Persistence
|
||||
import jakarta.persistence.Query
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Unroll
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CLIENT
|
||||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
||||
|
||||
class EntityManagerTest extends AbstractHibernateTest {
|
||||
|
||||
@Shared
|
||||
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test-pu")
|
||||
|
||||
@Unroll
|
||||
def "test hibernate action #testName"() {
|
||||
setup:
|
||||
EntityManager entityManager = entityManagerFactory.createEntityManager()
|
||||
EntityTransaction entityTransaction = entityManager.getTransaction()
|
||||
entityTransaction.begin()
|
||||
|
||||
def entity = prepopulated.get(0)
|
||||
if (attach) {
|
||||
entity = runWithSpan("setup") {
|
||||
entityManager.merge(prepopulated.get(0))
|
||||
}
|
||||
ignoreTracesAndClear(1)
|
||||
}
|
||||
|
||||
when:
|
||||
runWithSpan("parent") {
|
||||
try {
|
||||
sessionMethodTest.call(entityManager, entity)
|
||||
} catch (Exception e) {
|
||||
// We expected this, we should see the error field set on the span.
|
||||
}
|
||||
|
||||
entityTransaction.commit()
|
||||
entityManager.close()
|
||||
}
|
||||
|
||||
then:
|
||||
boolean isPersistTest = "persist" == testName
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 4 + (isPersistTest ? 1 : 0)) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name ~/Session.$methodName $resource/
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def offset = 0
|
||||
if (isPersistTest) {
|
||||
// persist test has an extra query for getting id of inserted element
|
||||
offset = 1
|
||||
span(2) {
|
||||
name "SELECT db1.Value"
|
||||
childOf span(1)
|
||||
kind CLIENT
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" String
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!flushOnCommit) {
|
||||
span(2 + offset) {
|
||||
childOf span(1)
|
||||
kind CLIENT
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" String
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(3 + offset) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
} else {
|
||||
span(2 + offset) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
span(3 + offset) {
|
||||
childOf span(2 + offset)
|
||||
kind CLIENT
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" String
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
testName | methodName | resource | attach | flushOnCommit | sessionMethodTest
|
||||
"lock" | "lock" | "Value" | true | false | { em, val ->
|
||||
em.lock(val, LockModeType.PESSIMISTIC_READ)
|
||||
}
|
||||
"refresh" | "refresh" | "Value" | true | false | { em, val ->
|
||||
em.refresh(val)
|
||||
}
|
||||
"find" | "(get|find)" | "Value" | false | false | { em, val ->
|
||||
em.find(Value, val.getId())
|
||||
}
|
||||
"persist" | "persist" | "Value" | false | true | { em, val ->
|
||||
em.persist(new Value("insert me"))
|
||||
}
|
||||
"merge" | "merge" | "Value" | true | true | { em, val ->
|
||||
val.setName("New name")
|
||||
em.merge(val)
|
||||
}
|
||||
"remove" | "delete" | "Value" | true | true | { em, val ->
|
||||
em.remove(val)
|
||||
}
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "test attaches State to query created via #queryMethodName"() {
|
||||
setup:
|
||||
runWithSpan("parent") {
|
||||
EntityManager entityManager = entityManagerFactory.createEntityManager()
|
||||
EntityTransaction entityTransaction = entityManager.getTransaction()
|
||||
entityTransaction.begin()
|
||||
Query query = queryBuildMethod(entityManager)
|
||||
query.getResultList()
|
||||
entityTransaction.commit()
|
||||
entityManager.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name resource
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "SELECT db1.Value"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
queryMethodName | resource | queryBuildMethod
|
||||
"createQuery" | "SELECT Value" | { em -> em.createQuery("from Value") }
|
||||
"getNamedQuery" | "SELECT Value" | { em -> em.createNamedQuery("TestNamedQuery") }
|
||||
"createSQLQuery" | "SELECT Value" | { em -> em.createNativeQuery("SELECT * FROM Value") }
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||
import jakarta.persistence.ParameterMode
|
||||
import org.hibernate.Session
|
||||
import org.hibernate.SessionFactory
|
||||
import org.hibernate.cfg.Configuration
|
||||
import org.hibernate.exception.SQLGrammarException
|
||||
import org.hibernate.procedure.ProcedureCall
|
||||
import spock.lang.Shared
|
||||
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
import java.sql.Statement
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CLIENT
|
||||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
||||
import static io.opentelemetry.api.trace.StatusCode.ERROR
|
||||
|
||||
class ProcedureCallTest extends AgentInstrumentationSpecification {
|
||||
|
||||
@Shared
|
||||
protected SessionFactory sessionFactory
|
||||
|
||||
@Shared
|
||||
protected List<Value> prepopulated
|
||||
|
||||
def setupSpec() {
|
||||
sessionFactory = new Configuration().configure("procedure-call-hibernate.cfg.xml").buildSessionFactory()
|
||||
// Pre-populate the DB, so delete/update can be tested.
|
||||
Session writer = sessionFactory.openSession()
|
||||
writer.beginTransaction()
|
||||
prepopulated = new ArrayList<>()
|
||||
for (int i = 0; i < 2; i++) {
|
||||
prepopulated.add(new Value("Hello :) " + i))
|
||||
writer.save(prepopulated.get(i))
|
||||
}
|
||||
writer.getTransaction().commit()
|
||||
writer.close()
|
||||
|
||||
// Create a stored procedure.
|
||||
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:test", "sa", "1")
|
||||
Statement stmt = conn.createStatement()
|
||||
stmt.execute("CREATE PROCEDURE TEST_PROC() MODIFIES SQL DATA BEGIN ATOMIC INSERT INTO Value VALUES (420, 'fred'); END")
|
||||
stmt.close()
|
||||
conn.close()
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
if (sessionFactory != null) {
|
||||
sessionFactory.close()
|
||||
}
|
||||
}
|
||||
|
||||
def "test ProcedureCall"() {
|
||||
setup:
|
||||
|
||||
runWithSpan("parent") {
|
||||
Session session = sessionFactory.openSession()
|
||||
session.beginTransaction()
|
||||
|
||||
ProcedureCall call = session.createStoredProcedureCall("TEST_PROC")
|
||||
call.getOutputs()
|
||||
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "ProcedureCall.getOutputs TEST_PROC"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "CALL test.TEST_PROC"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_STATEMENT" "{call TEST_PROC()}"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_OPERATION" "CALL"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
kind INTERNAL
|
||||
name "Transaction.commit"
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test failing ProcedureCall"() {
|
||||
setup:
|
||||
|
||||
runWithSpan("parent") {
|
||||
Session session = sessionFactory.openSession()
|
||||
session.beginTransaction()
|
||||
|
||||
ProcedureCall call = session.createStoredProcedureCall("TEST_PROC")
|
||||
def parameterRegistration = call.registerParameter("nonexistent", Long, ParameterMode.IN)
|
||||
call.setParameter(parameterRegistration, 420L)
|
||||
try {
|
||||
call.getOutputs()
|
||||
} catch (Exception e) {
|
||||
// We expected this.
|
||||
}
|
||||
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "ProcedureCall.getOutputs TEST_PROC"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
status ERROR
|
||||
errorEvent(SQLGrammarException, "could not prepare statement")
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,603 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||
import org.hibernate.LockMode
|
||||
import org.hibernate.LockOptions
|
||||
import org.hibernate.ReplicationMode
|
||||
import org.hibernate.Session
|
||||
import org.hibernate.UnknownEntityTypeException
|
||||
import spock.lang.Shared
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CLIENT
|
||||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
||||
import static io.opentelemetry.api.trace.StatusCode.ERROR
|
||||
|
||||
class SessionTest extends AbstractHibernateTest {
|
||||
|
||||
@Shared
|
||||
private Closure sessionBuilder = { return sessionFactory.openSession() }
|
||||
@Shared
|
||||
private Closure statelessSessionBuilder = { return sessionFactory.openStatelessSession() }
|
||||
|
||||
|
||||
def "test hibernate action #testName"() {
|
||||
setup:
|
||||
|
||||
// Test for each implementation of Session.
|
||||
for (def buildSession : sessionImplementations) {
|
||||
runWithSpan("parent") {
|
||||
def session = buildSession()
|
||||
session.beginTransaction()
|
||||
|
||||
try {
|
||||
sessionMethodTest.call(session, prepopulated.get(0))
|
||||
} catch (Exception e) {
|
||||
// We expected this, we should see the error field set on the span.
|
||||
}
|
||||
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(sessionImplementations.size()) {
|
||||
for (int i = 0; i < sessionImplementations.size(); i++) {
|
||||
trace(i, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "Session.$methodName $resource"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
childOf span(1)
|
||||
kind CLIENT
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" String
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
testName | methodName | resource | sessionImplementations | sessionMethodTest
|
||||
"lock" | "lock" | "Value" | [sessionBuilder] | { sesh, val ->
|
||||
sesh.lock(val, LockMode.READ)
|
||||
}
|
||||
"lock with entity name" | "lock" | "Value" | [sessionBuilder] | { sesh, val ->
|
||||
sesh.lock("Value", val, LockMode.READ)
|
||||
}
|
||||
"lock with null name" | "lock" | "Value" | [sessionBuilder] | { sesh, val ->
|
||||
sesh.lock(null, val, LockMode.READ)
|
||||
}
|
||||
"buildLockRequest" | "lock" | "Value" | [sessionBuilder] | { sesh, val ->
|
||||
sesh.buildLockRequest(LockOptions.READ)
|
||||
.lock(val)
|
||||
}
|
||||
"refresh" | "refresh" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.refresh(val)
|
||||
}
|
||||
"refresh with entity name" | "refresh" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.refresh("Value", val)
|
||||
}
|
||||
"get with entity name" | "get" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.get("Value", val.getId())
|
||||
}
|
||||
"get with entity class" | "get" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.get(Value, val.getId())
|
||||
}
|
||||
"insert" | "insert" | "Value" | [statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.insert(new Value("insert me"))
|
||||
}
|
||||
"insert with entity name" | "insert" | "Value" | [statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.insert("Value", new Value("insert me"))
|
||||
}
|
||||
"insert with null entity name" | "insert" | "Value" | [statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.insert(null, new Value("insert me"))
|
||||
}
|
||||
"update (StatelessSession)" | "update" | "Value" | [statelessSessionBuilder] | { sesh, val ->
|
||||
val.setName("New name")
|
||||
sesh.update(val)
|
||||
}
|
||||
"update with entity name (StatelessSession)" | "update" | "Value" | [statelessSessionBuilder] | { sesh, val ->
|
||||
val.setName("New name")
|
||||
sesh.update("Value", val)
|
||||
}
|
||||
"delete (Session)" | "delete" | "Value" | [statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.delete(val)
|
||||
prepopulated.remove(val)
|
||||
}
|
||||
"delete with entity name (Session)" | "delete" | "Value" | [statelessSessionBuilder] | { sesh, val ->
|
||||
sesh.delete("Value", val)
|
||||
prepopulated.remove(val)
|
||||
}
|
||||
}
|
||||
|
||||
def "test hibernate replicate: #testName"() {
|
||||
setup:
|
||||
|
||||
// Test for each implementation of Session.
|
||||
runWithSpan("parent") {
|
||||
def session = sessionFactory.openSession()
|
||||
session.beginTransaction()
|
||||
|
||||
try {
|
||||
sessionMethodTest.call(session, prepopulated.get(0))
|
||||
} catch (Exception e) {
|
||||
// We expected this, we should see the error field set on the span.
|
||||
}
|
||||
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 5) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "Session.$methodName $resource"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "SELECT db1.Value"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/^select /
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
span(4) {
|
||||
kind CLIENT
|
||||
childOf span(3)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" String
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
where:
|
||||
testName | methodName | resource | sessionMethodTest
|
||||
"replicate" | "replicate" | "Value" | { sesh, val ->
|
||||
Value replicated = new Value(val.getName() + " replicated")
|
||||
replicated.setId(val.getId())
|
||||
sesh.replicate(replicated, ReplicationMode.OVERWRITE)
|
||||
}
|
||||
"replicate by entityName" | "replicate" | "Value" | { sesh, val ->
|
||||
Value replicated = new Value(val.getName() + " replicated")
|
||||
replicated.setId(val.getId())
|
||||
sesh.replicate("Value", replicated, ReplicationMode.OVERWRITE)
|
||||
}
|
||||
}
|
||||
|
||||
def "test hibernate failed replicate"() {
|
||||
setup:
|
||||
|
||||
// Test for each implementation of Session.
|
||||
runWithSpan("parent") {
|
||||
def session = sessionFactory.openSession()
|
||||
session.beginTransaction()
|
||||
|
||||
try {
|
||||
session.replicate(new Long(123) /* Not a valid entity */, ReplicationMode.OVERWRITE)
|
||||
} catch (Exception e) {
|
||||
// We expected this, we should see the error field set on the span.
|
||||
}
|
||||
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "Session.replicate java.lang.Long"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
status ERROR
|
||||
errorEvent(UnknownEntityTypeException, "Unable to locate persister: java.lang.Long")
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def "test hibernate commit action #testName"() {
|
||||
setup:
|
||||
|
||||
runWithSpan("parent") {
|
||||
def session = sessionBuilder()
|
||||
session.beginTransaction()
|
||||
|
||||
try {
|
||||
sessionMethodTest.call(session, prepopulated.get(0))
|
||||
} catch (Exception e) {
|
||||
// We expected this, we should see the error field set on the span.
|
||||
}
|
||||
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "Session.$methodName $resource"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
kind CLIENT
|
||||
childOf span(2)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" String
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
testName | methodName | resource | sessionMethodTest
|
||||
"save" | "save" | "Value" | { sesh, val ->
|
||||
sesh.save(new Value("Another value"))
|
||||
}
|
||||
"save with entity name" | "save" | "Value" | { sesh, val ->
|
||||
sesh.save("Value", new Value("Another value"))
|
||||
}
|
||||
"saveOrUpdate save" | "saveOrUpdate" | "Value" | { sesh, val ->
|
||||
sesh.saveOrUpdate(new Value("Value"))
|
||||
}
|
||||
"saveOrUpdate save with entity name" | "saveOrUpdate" | "Value" | { sesh, val ->
|
||||
sesh.saveOrUpdate("Value", new Value("Value"))
|
||||
}
|
||||
"saveOrUpdate update with entity name" | "saveOrUpdate" | "Value" | { sesh, val ->
|
||||
val.setName("New name")
|
||||
sesh.saveOrUpdate("Value", val)
|
||||
}
|
||||
"merge" | "merge" | "Value" | { sesh, val ->
|
||||
sesh.merge(new Value("merge me in"))
|
||||
}
|
||||
"merge with entity name" | "merge" | "Value" | { sesh, val ->
|
||||
sesh.merge("Value", new Value("merge me in"))
|
||||
}
|
||||
"persist" | "persist" | "Value" | { sesh, val ->
|
||||
sesh.persist(new Value("merge me in"))
|
||||
}
|
||||
"persist with entity name" | "persist" | "Value" | { sesh, val ->
|
||||
sesh.persist("Value", new Value("merge me in"))
|
||||
}
|
||||
"persist with null entity name" | "persist" | "Value" | { sesh, val ->
|
||||
sesh.persist(null, new Value("merge me in"))
|
||||
}
|
||||
"update (Session)" | "update" | "Value" | { sesh, val ->
|
||||
val.setName("New name")
|
||||
sesh.update(val)
|
||||
}
|
||||
"update by entityName (Session)" | "update" | "Value" | { sesh, val ->
|
||||
val.setName("New name")
|
||||
sesh.update("Value", val)
|
||||
}
|
||||
"delete (Session)" | "delete" | "Value" | { sesh, val ->
|
||||
sesh.delete(val)
|
||||
prepopulated.remove(val)
|
||||
}
|
||||
"delete by entityName (Session)" | "delete" | "Value" | { sesh, val ->
|
||||
sesh.delete("Value", val)
|
||||
prepopulated.remove(val)
|
||||
}
|
||||
}
|
||||
|
||||
def "test attaches State to query created via #queryMethodName"() {
|
||||
setup:
|
||||
runWithSpan("parent") {
|
||||
Session session = sessionFactory.openSession()
|
||||
session.beginTransaction()
|
||||
def query = queryBuildMethod(session)
|
||||
query.list()
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name resource
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" String
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
queryMethodName | resource | queryBuildMethod
|
||||
"createQuery" | "SELECT Value" | { sess -> sess.createQuery("from Value") }
|
||||
"getNamedQuery" | "SELECT Value" | { sess -> sess.getNamedQuery("TestNamedQuery") }
|
||||
"createNativeQuery" | "SELECT Value" | { sess -> sess.createNativeQuery("SELECT * FROM Value") }
|
||||
"createSelectionQuery" | "SELECT Value" | { sess -> sess.createSelectionQuery("from Value") }
|
||||
}
|
||||
|
||||
def "test hibernate overlapping Sessions"() {
|
||||
setup:
|
||||
|
||||
runWithSpan("overlapping Sessions") {
|
||||
def session1 = sessionFactory.openSession()
|
||||
session1.beginTransaction()
|
||||
def session2 = sessionFactory.openStatelessSession()
|
||||
def session3 = sessionFactory.openSession()
|
||||
|
||||
def value1 = new Value("Value 1")
|
||||
session1.save(value1)
|
||||
session2.insert(new Value("Value 2"))
|
||||
session3.save(new Value("Value 3"))
|
||||
session1.delete(value1)
|
||||
|
||||
session2.close()
|
||||
session1.getTransaction().commit()
|
||||
session1.close()
|
||||
session3.close()
|
||||
}
|
||||
|
||||
expect:
|
||||
def sessionId1
|
||||
def sessionId2
|
||||
def sessionId3
|
||||
assertTraces(1) {
|
||||
trace(0, 9) {
|
||||
span(0) {
|
||||
name "overlapping Sessions"
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "Session.save Value"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId1 = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "Session.insert Value"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId2 = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "INSERT db1.Value"
|
||||
kind CLIENT
|
||||
childOf span(2)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/^insert /
|
||||
"$SemanticAttributes.DB_OPERATION" "INSERT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(4) {
|
||||
name "Session.save Value"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId3 = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(5) {
|
||||
name "Session.delete Value"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId1
|
||||
}
|
||||
}
|
||||
span(6) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId1
|
||||
}
|
||||
}
|
||||
span(7) {
|
||||
name "INSERT db1.Value"
|
||||
kind CLIENT
|
||||
childOf span(6)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/^insert /
|
||||
"$SemanticAttributes.DB_OPERATION" "INSERT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
span(8) {
|
||||
name "DELETE db1.Value"
|
||||
kind CLIENT
|
||||
childOf span(6)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "h2"
|
||||
"$SemanticAttributes.DB_NAME" "db1"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/^delete /
|
||||
"$SemanticAttributes.DB_OPERATION" "DELETE"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sessionId1 != sessionId2 != sessionId3
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.NamedQuery;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
@NamedQuery(name = "TestNamedQuery", query = "from Value")
|
||||
public class Value {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public Value() {}
|
||||
|
||||
public Value(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(generator = "increment")
|
||||
@GenericGenerator(name = "increment", strategy = "increment")
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String title) {
|
||||
name = title;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
|
||||
version="2.0">
|
||||
|
||||
<persistence-unit name="test-pu">
|
||||
<class>Value</class>
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
<properties>
|
||||
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
|
||||
<property name="hibernate.hbm2ddl.auto" value="update"/>
|
||||
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
|
||||
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE"/>
|
||||
<property name="javax.persistence.jdbc.user" value="sa"/>
|
||||
<property name="javax.persistence.jdbc.password" value=""/>
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
|
||||
|
||||
<hibernate-configuration>
|
||||
|
||||
<session-factory>
|
||||
|
||||
<!-- Use in-memory DB for testing -->
|
||||
<property name="connection.driver_class">org.h2.Driver</property>
|
||||
<property name="connection.url">jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE</property>
|
||||
<property name="connection.username">sa</property>
|
||||
<property name="connection.password"/>
|
||||
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
|
||||
|
||||
<property name="connection.pool_size">3</property>
|
||||
<property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
|
||||
<property name="show_sql">true</property>
|
||||
|
||||
<!-- Reset the DB each test -->
|
||||
<property name="hbm2ddl.auto">create</property>
|
||||
|
||||
<!-- Objects -->
|
||||
<mapping class="Value"/>
|
||||
|
||||
</session-factory>
|
||||
|
||||
</hibernate-configuration>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
|
||||
|
||||
<hibernate-configuration>
|
||||
|
||||
<session-factory>
|
||||
|
||||
<property name="hibernate.archive.autodetection">class,hbm</property>
|
||||
<property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
|
||||
<property name="hibernate.show_sql">true</property>
|
||||
<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
|
||||
<property name="hibernate.connection.username">sa</property>
|
||||
<property name="hibernate.connection.password">1</property>
|
||||
<property name="hibernate.connection.url">jdbc:hsqldb:mem:test</property>
|
||||
<property name="hibernate.hbm2ddl.auto">create</property>
|
||||
|
||||
<!-- Objects -->
|
||||
<mapping class="Value"/>
|
||||
|
||||
</session-factory>
|
||||
|
||||
</hibernate-configuration>
|
|
@ -0,0 +1,26 @@
|
|||
plugins {
|
||||
id("otel.javaagent-testing")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
library("org.hibernate:hibernate-core:6.0.0.Final")
|
||||
|
||||
testInstrumentation(project(":instrumentation:hibernate:hibernate-6.0:javaagent"))
|
||||
testInstrumentation(project(":instrumentation:jdbc:javaagent"))
|
||||
// Added to ensure cross compatibility:
|
||||
testInstrumentation(project(":instrumentation:hibernate:hibernate-3.3:javaagent"))
|
||||
testInstrumentation(project(":instrumentation:hibernate:hibernate-4.0:javaagent"))
|
||||
testInstrumentation(project(":instrumentation:hibernate:hibernate-procedure-call-4.3:javaagent"))
|
||||
|
||||
testImplementation("org.hsqldb:hsqldb:2.0.0")
|
||||
testImplementation("org.springframework.data:spring-data-jpa:3.0.0")
|
||||
}
|
||||
|
||||
otelJava {
|
||||
minJavaVersionSupported.set(JavaVersion.VERSION_17)
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
// TODO run tests both with and without experimental span attributes
|
||||
jvmArgs("-Dotel.instrumentation.hibernate.experimental-span-attributes=true")
|
||||
}
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||
import org.hibernate.Version
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import spock.lang.Shared
|
||||
import spring.jpa.Customer
|
||||
import spring.jpa.CustomerRepository
|
||||
import spring.jpa.PersistenceConfig
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CLIENT
|
||||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
||||
|
||||
class SpringJpaTest extends AgentInstrumentationSpecification {
|
||||
|
||||
@Shared
|
||||
def context = new AnnotationConfigApplicationContext(PersistenceConfig)
|
||||
|
||||
@Shared
|
||||
def repo = context.getBean(CustomerRepository)
|
||||
|
||||
def "test CRUD"() {
|
||||
setup:
|
||||
def isHibernate4 = Version.getVersionString().startsWith("4.")
|
||||
def customer = new Customer("Bob", "Anonymous")
|
||||
|
||||
expect:
|
||||
customer.id == null
|
||||
!runWithSpan("parent") {
|
||||
repo.findAll().iterator().hasNext()
|
||||
}
|
||||
|
||||
def sessionId
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "SELECT spring.jpa.Customer"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "SELECT test.Customer"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName(.*)from Customer(.*)/
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearExportedData()
|
||||
|
||||
when:
|
||||
runWithSpan("parent") {
|
||||
repo.save(customer)
|
||||
}
|
||||
def savedId = customer.id
|
||||
|
||||
then:
|
||||
customer.id != null
|
||||
def sessionId2
|
||||
assertTraces(1) {
|
||||
trace(0, 4 + (isHibernate4 ? 0 : 1)) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "Session.persist spring.jpa.Customer"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId2 = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isHibernate4) {
|
||||
span(2) {
|
||||
name "CALL test"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_STATEMENT" "call next value for Customer_SEQ"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_OPERATION" "CALL"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId2
|
||||
}
|
||||
}
|
||||
span(4) {
|
||||
name "INSERT test.Customer"
|
||||
kind CLIENT
|
||||
childOf span(3)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/insert into Customer \(.*\) values \(.*, \?, \?\)/
|
||||
"$SemanticAttributes.DB_OPERATION" "INSERT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
span(2) {
|
||||
name "INSERT test.Customer"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/insert into Customer \(.*\) values \(.*, \?, \?\)/
|
||||
"$SemanticAttributes.DB_OPERATION" "INSERT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearExportedData()
|
||||
|
||||
when:
|
||||
customer.firstName = "Bill"
|
||||
runWithSpan("parent") {
|
||||
repo.save(customer)
|
||||
}
|
||||
|
||||
then:
|
||||
customer.id == savedId
|
||||
def sessionId3
|
||||
assertTraces(1) {
|
||||
trace(0, 5) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name "Session.merge spring.jpa.Customer"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" {
|
||||
sessionId3 = it
|
||||
it instanceof String
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "SELECT test.Customer"
|
||||
kind CLIENT
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" sessionId3
|
||||
}
|
||||
}
|
||||
span(4) {
|
||||
name "UPDATE test.Customer"
|
||||
kind CLIENT
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" "update Customer set firstName=?, lastName=? where id=?"
|
||||
"$SemanticAttributes.DB_OPERATION" "UPDATE"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearExportedData()
|
||||
|
||||
when:
|
||||
customer = runWithSpan("parent") {
|
||||
repo.findByLastName("Anonymous")[0]
|
||||
}
|
||||
|
||||
then:
|
||||
customer.id == savedId
|
||||
customer.firstName == "Bill"
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
name { it == "SELECT spring.jpa.Customer" || it == "Hibernate Query" }
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" String
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "SELECT test.Customer"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)(where ([^.]+)\.lastName( ?)=( ?)\?|)/
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearExportedData()
|
||||
|
||||
when:
|
||||
runWithSpan("parent") {
|
||||
repo.delete(customer)
|
||||
}
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 6 + (isHibernate4 ? 0 : 1)) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
def offset = 0
|
||||
if (!isHibernate4) {
|
||||
offset = 2
|
||||
span(1) {
|
||||
name ~/Session.(get|find) spring.jpa.Customer/
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" String
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
name "SELECT test.Customer"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
}
|
||||
span(1 + offset) {
|
||||
name "Session.merge spring.jpa.Customer"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" String
|
||||
}
|
||||
}
|
||||
if (isHibernate4) {
|
||||
offset = 1
|
||||
span(2) {
|
||||
name "SELECT test.Customer"
|
||||
kind CLIENT
|
||||
childOf span(1)
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/
|
||||
"$SemanticAttributes.DB_OPERATION" "SELECT"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
}
|
||||
span(2 + offset) {
|
||||
name "Session.delete spring.jpa.Customer"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" String
|
||||
}
|
||||
}
|
||||
span(3 + offset) {
|
||||
name "Transaction.commit"
|
||||
kind INTERNAL
|
||||
childOf span(0)
|
||||
attributes {
|
||||
"hibernate.session_id" String
|
||||
}
|
||||
}
|
||||
span(4 + offset) {
|
||||
name "DELETE test.Customer"
|
||||
kind CLIENT
|
||||
attributes {
|
||||
"$SemanticAttributes.DB_SYSTEM" "hsqldb"
|
||||
"$SemanticAttributes.DB_NAME" "test"
|
||||
"$SemanticAttributes.DB_USER" "sa"
|
||||
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
|
||||
"$SemanticAttributes.DB_STATEMENT" "delete from Customer where id=?"
|
||||
"$SemanticAttributes.DB_OPERATION" "DELETE"
|
||||
"$SemanticAttributes.DB_SQL_TABLE" "Customer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package spring.jpa;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Entity
|
||||
public class Customer {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
||||
protected Customer() {}
|
||||
|
||||
public Customer(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Customer)) {
|
||||
return false;
|
||||
}
|
||||
Customer other = (Customer) obj;
|
||||
return Objects.equals(id, other.id)
|
||||
&& Objects.equals(firstName, other.firstName)
|
||||
&& Objects.equals(lastName, other.lastName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, firstName, lastName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package spring.jpa;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface CustomerRepository extends CrudRepository<Customer, Long> {
|
||||
|
||||
List<Customer> findByLastName(String lastName);
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package spring.jpa;
|
||||
|
||||
import java.util.Properties;
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@EnableJpaRepositories(basePackages = "spring/jpa")
|
||||
public class PersistenceConfig {
|
||||
|
||||
@Bean(name = "transactionManager")
|
||||
public PlatformTransactionManager dbTransactionManager() {
|
||||
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||
|
||||
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
|
||||
vendorAdapter.setDatabase(Database.HSQL);
|
||||
vendorAdapter.setGenerateDdl(true);
|
||||
|
||||
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
|
||||
em.setDataSource(dataSource());
|
||||
em.setPackagesToScan("spring/jpa");
|
||||
em.setJpaVendorAdapter(vendorAdapter);
|
||||
em.setJpaProperties(additionalProperties());
|
||||
|
||||
return em;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
dataSource.setUrl("jdbc:hsqldb:mem:test");
|
||||
dataSource.setUsername("sa");
|
||||
dataSource.setPassword("1");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private static Properties additionalProperties() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("hibernate.show_sql", "true");
|
||||
properties.setProperty("hibernate.hbm2ddl.auto", "create");
|
||||
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import org.hibernate.SessionFactory
|
|||
import org.hibernate.cfg.Configuration
|
||||
import org.hibernate.exception.SQLGrammarException
|
||||
import org.hibernate.procedure.ProcedureCall
|
||||
import org.junit.jupiter.api.Assumptions
|
||||
import spock.lang.Shared
|
||||
|
||||
import javax.persistence.ParameterMode
|
||||
|
@ -57,16 +56,6 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
|
|||
}
|
||||
}
|
||||
|
||||
def callProcedure(ProcedureCall call) {
|
||||
try {
|
||||
call.getOutputs()
|
||||
} catch (Exception exception) {
|
||||
// ignore failures on hibernate 6 where this functionality has not been implemented yet
|
||||
Assumptions.assumeFalse("org.hibernate.NotYetImplementedFor6Exception" == exception.getClass().getName())
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
def "test ProcedureCall"() {
|
||||
setup:
|
||||
|
||||
|
@ -75,7 +64,7 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
|
|||
session.beginTransaction()
|
||||
|
||||
ProcedureCall call = session.createStoredProcedureCall("TEST_PROC")
|
||||
callProcedure(call)
|
||||
call.getOutputs()
|
||||
|
||||
session.getTransaction().commit()
|
||||
session.close()
|
||||
|
@ -137,10 +126,9 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
|
|||
|
||||
ProcedureCall call = session.createStoredProcedureCall("TEST_PROC")
|
||||
def parameterRegistration = call.registerParameter("nonexistent", Long, ParameterMode.IN)
|
||||
Assumptions.assumeTrue(parameterRegistration.metaClass.getMetaMethod("bindValue", Object) != null)
|
||||
parameterRegistration.bindValue(420L)
|
||||
try {
|
||||
callProcedure(call)
|
||||
call.getOutputs()
|
||||
} catch (Exception e) {
|
||||
// We expected this.
|
||||
}
|
||||
|
|
|
@ -7,20 +7,6 @@
|
|||
|
||||
<session-factory>
|
||||
|
||||
<!--<!– Use in-memory DB for testing –>-->
|
||||
<!--<property name="connection.driver_class">org.apache.derby.jdbc.EmbeddedDriver</property>-->
|
||||
<!--<property name="connection.url">jdbc:derby:test;create=true</property>-->
|
||||
<!--<property name="connection.username">root</property>-->
|
||||
<!--<property name="connection.password">root</property>-->
|
||||
<!--<property name="dialect">org.hibernate.dialect.DerbyTenSevenDialect</property>-->
|
||||
|
||||
<!--<property name="connection.pool_size">3</property>-->
|
||||
<!--<property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>-->
|
||||
<!--<property name="show_sql">true</property>-->
|
||||
|
||||
<!--<!– Reset the DB each test –>-->
|
||||
<!--<property name="hbm2ddl.auto">create</property>-->
|
||||
|
||||
<property name="hibernate.archive.autodetection">class,hbm</property>
|
||||
<property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
|
||||
<property name="hibernate.show_sql">true</property>
|
||||
|
|
|
@ -232,6 +232,8 @@ hideFromDependabot(":instrumentation:guava-10.0:library")
|
|||
hideFromDependabot(":instrumentation:gwt-2.0:javaagent")
|
||||
hideFromDependabot(":instrumentation:hibernate:hibernate-3.3:javaagent")
|
||||
hideFromDependabot(":instrumentation:hibernate:hibernate-4.0:javaagent")
|
||||
hideFromDependabot(":instrumentation:hibernate:hibernate-6.0:javaagent")
|
||||
hideFromDependabot(":instrumentation:hibernate:hibernate-6.0:spring-testing")
|
||||
hideFromDependabot(":instrumentation:hibernate:hibernate-common:javaagent")
|
||||
hideFromDependabot(":instrumentation:hibernate:hibernate-procedure-call-4.3:javaagent")
|
||||
hideFromDependabot(":instrumentation:hikaricp-3.0:javaagent")
|
||||
|
|
Loading…
Reference in New Issue