-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: transaction tagging Adds support for transaction tags using an interceptor that traverses the call stack up to the method that actually started the transaction. The tag is then determined by: 1. Looking for an @TransactionTag annotation on the method. 2. Or, if enabled, automatically generating a tag from the class name and method name. The option to automatically add tags based on class and method name can be enabled through a system property. This makes it possible to enable this feature only during debugging, and disable it again when it is no longer needed. * chore: cleanup
- Loading branch information
Showing
8 changed files
with
441 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
...e/src/main/java/com/google/cloud/spanner/sample/TaggingHibernatePropertiesCustomizer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* Copyright 2019-2024 Google LLC | ||
* | ||
* This library is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 2.1 of the License, or (at your option) any later version. | ||
* | ||
* This library is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public | ||
* License along with this library; if not, write to the Free Software | ||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA | ||
*/ | ||
|
||
package com.google.cloud.spanner.sample; | ||
|
||
import com.google.cloud.spanner.hibernate.TransactionTagInterceptor; | ||
import com.google.common.collect.ImmutableSet; | ||
import java.util.Map; | ||
import org.hibernate.cfg.AvailableSettings; | ||
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; | ||
import org.springframework.stereotype.Component; | ||
|
||
/** This component adds the {@link TransactionTagInterceptor} to the sample application. */ | ||
@Component | ||
public class TaggingHibernatePropertiesCustomizer implements HibernatePropertiesCustomizer { | ||
@Override | ||
public void customize(Map<String, Object> hibernateProperties) { | ||
hibernateProperties.put(AvailableSettings.INTERCEPTOR, new TransactionTagInterceptor( | ||
ImmutableSet.of(SampleApplication.class.getPackageName()), false)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
...s/src/main/java/com/google/cloud/spanner/hibernate/AbstractTransactionTagInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* Copyright 2019-2024 Google LLC | ||
* | ||
* This library is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 2.1 of the License, or (at your option) any later version. | ||
* | ||
* This library is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public | ||
* License along with this library; if not, write to the Free Software | ||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA | ||
*/ | ||
|
||
package com.google.cloud.spanner.hibernate; | ||
|
||
import java.lang.reflect.Field; | ||
import javax.annotation.Nullable; | ||
import org.hibernate.HibernateException; | ||
import org.hibernate.Interceptor; | ||
import org.hibernate.Session; | ||
import org.hibernate.SessionFactory; | ||
import org.hibernate.Transaction; | ||
import org.hibernate.dialect.Dialect; | ||
import org.hibernate.engine.transaction.internal.TransactionImpl; | ||
import org.hibernate.internal.SessionFactoryImpl; | ||
|
||
/** Base class for interceptors that add transaction tags. */ | ||
public abstract class AbstractTransactionTagInterceptor implements Interceptor { | ||
private final Field sessionField; | ||
|
||
private Boolean dialectIsPostgres; | ||
|
||
protected AbstractTransactionTagInterceptor() { | ||
try { | ||
sessionField = TransactionImpl.class.getDeclaredField("session"); | ||
sessionField.setAccessible(true); | ||
} catch (NoSuchFieldException noSuchFieldException) { | ||
throw new HibernateException("Could not get 'session' field of TransactionImpl"); | ||
} | ||
} | ||
|
||
@Override | ||
public void afterTransactionBegin(Transaction tx) { | ||
String tag = getTag(); | ||
if (tag != null) { | ||
Session session = getSession(tx); | ||
if (session != null) { | ||
session.doWork(connection -> { | ||
if (!(connection.isReadOnly() || connection.getAutoCommit())) { | ||
connection.createStatement().execute(generateSetTransactionTagStatement(session, tag)); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
|
||
private String generateSetTransactionTagStatement(Session session, String tag) { | ||
if (dialectIsPostgres(session)) { | ||
return "set spanner.transaction_tag='" + tag + "'"; | ||
} | ||
return "set transaction_tag='" + tag + "'"; | ||
} | ||
|
||
private boolean dialectIsPostgres(Session session) { | ||
if (this.dialectIsPostgres == null) { | ||
synchronized (this) { | ||
if (this.dialectIsPostgres == null) { | ||
SessionFactory factory = session.getSessionFactory(); | ||
if (factory instanceof SessionFactoryImpl) { | ||
Dialect dialect = ((SessionFactoryImpl) factory).getJdbcServices().getDialect(); | ||
this.dialectIsPostgres = dialect.openQuote() == '"'; | ||
} else { | ||
this.dialectIsPostgres = false; | ||
} | ||
} | ||
} | ||
} | ||
return this.dialectIsPostgres; | ||
} | ||
|
||
/** Returns the tag that should be added to the transaction that is being started. */ | ||
protected abstract String getTag(); | ||
|
||
/** | ||
* Gets the session from the transaction. | ||
* Unfortunately, there is no public API to do so, so we have to use reflection. | ||
*/ | ||
private @Nullable Session getSession(Transaction tx) { | ||
if (tx instanceof TransactionImpl && sessionField != null) { | ||
try { | ||
TransactionImpl transaction = (TransactionImpl) tx; | ||
return (Session) sessionField.get(transaction); | ||
} catch (IllegalAccessException illegalAccessException) { | ||
throw new HibernateException( | ||
"Failed to get session from transaction", illegalAccessException); | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
} |
72 changes: 72 additions & 0 deletions
72
...nner-hibernate-tools/src/main/java/com/google/cloud/spanner/hibernate/TransactionTag.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* Copyright 2019-2024 Google LLC | ||
* | ||
* This library is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 2.1 of the License, or (at your option) any later version. | ||
* | ||
* This library is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public | ||
* License along with this library; if not, write to the Free Software | ||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA | ||
*/ | ||
|
||
package com.google.cloud.spanner.hibernate; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Annotation for adding transaction tags to Hibernate transactions. | ||
* | ||
* <p>Usage: | ||
* <ol> | ||
* <li>Add the {@link TransactionTagInterceptor} to your Hibernate configuration.</li> | ||
* <li>Add the {@link TransactionTag} annotation to a method that is also tagged with | ||
* {@link jakarta.transaction.Transactional}.</li> | ||
* </ol> | ||
* | ||
* <p>Example: | ||
* | ||
* <pre>{@code | ||
* // Add TransactionTagInterceptor to the Hibernate configuration. | ||
* @Component | ||
* public class TaggingHibernatePropertiesCustomizer implements HibernatePropertiesCustomizer { | ||
* @Override | ||
* public void customize(Map<String, Object> hibernateProperties) { | ||
* hibernateProperties.put(AvailableSettings.INTERCEPTOR, new TransactionTagInterceptor( | ||
* ImmutableSet.of(SampleApplication.class.getPackageName()), false)); | ||
* } | ||
* } | ||
* | ||
* @Service | ||
* public class VenueService { | ||
* @Transactional | ||
* @TransactionTag("generate_random_venues") | ||
* public List<Venue> generateRandomVenues(int count) { | ||
* // Code that is executed in a transaction... | ||
* } | ||
* } | ||
* }</pre> | ||
* | ||
* <p>See <a href="https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample"> | ||
* Spring Data JPA Full Sample</a> for a working sample application. | ||
*/ | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.METHOD) | ||
public @interface TransactionTag { | ||
|
||
/** | ||
* The transaction tag value. Max length is 50 characters. | ||
* See <a href="https://cloud.google.com/spanner/docs/introspection/troubleshooting-with-tags#limitations"> | ||
* Limitations</a> for all limitations on transaction tag values. | ||
*/ | ||
String value(); | ||
} |
Oops, something went wrong.