Add instrumentation for hibernate 6 (#7773)

This commit is contained in:
Lauri Tulmin 2023-02-14 00:39:27 +02:00 committed by GitHub
parent f074b93110
commit d847bfcad5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2442 additions and 35 deletions

View File

@ -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 =

View File

@ -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",

View File

@ -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 =

View File

@ -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

View File

@ -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");

View File

@ -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")
}

View File

@ -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);
}
}

View File

@ -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() {}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}
}

View File

@ -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());
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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()
}
}
}

View File

@ -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() }
}
}

View File

@ -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") }
}
}

View File

@ -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
}
}
}
}
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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")
}

View File

@ -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"
}
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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.
}

View File

@ -7,20 +7,6 @@
<session-factory>
<!--&lt;!&ndash; Use in-memory DB for testing &ndash;&gt;-->
<!--<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>-->
<!--&lt;!&ndash; Reset the DB each test &ndash;&gt;-->
<!--<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>

View File

@ -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")