¿El atributo Spring @Transactional funciona en un método privado?

196

Si tengo una anotación @Transactional en un método privado en un bean Spring, ¿tiene algún efecto la anotación?

Si la @Transactionalanotación está en un método público, funciona y abre una transacción.

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
Juha Syrjälä
fuente

Respuestas:

163

La pregunta no es privada ni pública, la pregunta es: ¿cómo se invoca y qué implementación de AOP utiliza?

Si usa Spring Proxy AOP de Spring (predeterminado), toda la funcionalidad de AOP proporcionada por Spring (like @Transational) solo se tendrá en cuenta si la llamada pasa por el proxy. - Este suele ser el caso si el método anotado se invoca desde otro bean.

Esto tiene dos implicaciones:

  • Como los métodos privados no deben invocarse desde otro bean (la excepción es la reflexión), su @Transactional Anotación no se tiene en cuenta.
  • Si el método es público, pero se invoca desde el mismo bean, tampoco se tendrá en cuenta (esta declaración solo es correcta si se usa Spring Proxy AOP (predeterminado)).

@Ver Spring Reference: Capítulo 9.6 9.6 Mecanismos de representación

En mi humilde opinión, debe utilizar el modo aspectoJ, en lugar de los Spring Proxies, que resolverá el problema. Y los Aspectos transaccionales de AspectJ se entrelazan incluso en métodos privados (verificado para Spring 3.0).

Ralph
fuente
44
Ambos puntos no son necesariamente ciertos. El primero es incorrecto: los métodos privados se pueden invocar reflexivamente, pero la lógica de descubrimiento del proxy elige no hacerlo. El segundo punto solo es cierto para los servidores proxy JDK basados ​​en interfaz, pero no para los servidores proxy basados ​​en subclase CGLIB.
skaffman
@skaffman: 1 - hago mi declaración más precisa, 2. Pero el Proxy predeterminado está basado en la interfaz, ¿no es así?
Ralph
2
Eso depende de si el objetivo usa interfaces o no. Si no es así, se usa CGLIB.
skaffman
¿me puede decir la resonancia o alguna referencia por qué cglib no puede pero se puede aspectar?
Phil
1
Referencia desde el enlace en el bloque de respuesta, si desea usar Spring Proxies [entorno predeterminado], ponga una anotación en doStuff () y llame a doPrivateStuff () usando ((Bean) AopContext.currentProxy ()). DoPrivateStuff (); Ejecutará ambos métodos en una misma transacción si se requiere la propagación [entorno predeterminado].
Michael Ouyang
219

La respuesta a su pregunta es no: no @Transactionaltendrá ningún efecto si se utiliza para anotar métodos privados. El generador proxy los ignorará.

Esto está documentado en Spring Manual, capítulo 10.5.6 :

Método de visibilidad y @Transactional

Al usar proxies, debe aplicar la @Transactionalanotación solo a los métodos con visibilidad pública. Si anota métodos protegidos, privados o visibles en el paquete con la @Transactionalanotación, no se genera ningún error, pero el método anotado no muestra la configuración transaccional configurada. Considere el uso de AspectJ (ver más abajo) si necesita anotar métodos no públicos.

skaffman
fuente
¿Estas seguro acerca de esto? No esperaría que hiciera la diferencia.
willcodejavaforfood
¿Qué tal si el estilo proxy es Cglib?
Lily
32

Por defecto, el @Transactionalatributo solo funciona cuando se llama a un método anotado en una referencia obtenida de applicationContext.

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

Esto abrirá una transacción:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

Esto no:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Referencia de Spring: Uso de @Transactional

Nota: En el modo proxy (que es el predeterminado), solo se interceptarán las llamadas de método 'externas' que ingresen a través del proxy. Esto significa que la 'autoinvocación', es decir, un método dentro del objeto de destino que llama a algún otro método del objeto de destino, no dará lugar a una transacción real en tiempo de ejecución, incluso si el método invocado está marcado con@Transactional !

Considere el uso del modo AspectJ (ver más abajo) si espera que las autoinvocaciones también se envuelvan con transacciones. En este caso, no habrá un proxy en primer lugar; en su lugar, la clase objetivo se 'tejirá' (es decir, se modificará su código de bytes) para que se convierta @Transactionalen un comportamiento de tiempo de ejecución en cualquier tipo de método.

Juha Syrjälä
fuente
¿Quieres decir bean = new Bean () ;?
willcodejavaforfood
No Si creo beans con new Bean (), la anotación nunca funcionará al menos sin usar Aspect-J.
Juha Syrjälä
2
¡Gracias! Esto explica el comportamiento extraño que estaba observando. Muy contradictorio, esta restricción de invocación de método interno ...
manuel aldana
Aprendí que "las únicas llamadas a métodos externos que
ingresen a
13

Sí, es posible usar @Transactional en métodos privados, pero como otros han mencionado, esto no funcionará de inmediato. Necesitas usar AspectJ. Me tomó un tiempo descubrir cómo hacerlo funcionar. Compartiré mis resultados.

Elegí usar el tejido en tiempo de compilación en lugar del tejido en tiempo de carga porque creo que es una mejor opción en general. Además, estoy usando Java 8, por lo que es posible que deba ajustar algunos parámetros.

Primero, agregue la dependencia para Aspectjrt.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

Luego agregue el complemento AspectJ para hacer el tejido de código de bytes real en Maven (esto puede no ser un ejemplo mínimo).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Finalmente agregue esto a su clase de configuración

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Ahora debería poder usar @Transactional en métodos privados.

Una advertencia para este enfoque: necesitará configurar su IDE para conocer AspectJ; de lo contrario, si ejecuta la aplicación a través de Eclipse, por ejemplo, puede que no funcione. Asegúrate de probar contra una construcción directa de Maven como un control de cordura.

James Watkins
fuente
si el método proxy es cglib, no hay necesidad de implementar una interfaz cuyo método sea público, ¿entonces puede usar @Transactional en métodos privados?
Lily
Sí, funciona con métodos privados y sin interfaces. Mientras AspectJ esté configurado correctamente, básicamente garantiza decoradores de métodos de trabajo. Y user536161 señaló en su respuesta que incluso funcionará en autoinvocaciones. Es realmente genial y da un poco de miedo.
James Watkins
12

Si necesita ajustar un método privado dentro de una transacción y no desea usar Aspectj, puede usar TransactionTemplate .

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}
loonis
fuente
Es bueno mostrar el TransactionTemplateuso, pero llame a ese segundo método en ..RequiresTransactionlugar de ..InTransaction. Siempre nombre las cosas como le gustaría leerlas un año después. También diría que si realmente requiere un segundo método privado: poner su contenido directamente en la executeimplementación anónima o si eso se vuelve desordenado, podría ser una indicación para dividir la implementación en otro servicio que luego puede anotar @Transactional.
Atrapado el
@Stuck, el segundo método de hecho no es necesario, pero responde a la pregunta original que es cómo aplicar una transacción de primavera en un método privado
loonis
Sí, ya voté por la respuesta, pero quería compartir algunos contextos y pensamientos sobre cómo aplicarla, porque creo que desde el punto de vista de la arquitectura, esta situación es una indicación potencial de un defecto de diseño.
Atrapado
5

Spring Docs explican que

En el modo proxy (que es el predeterminado), solo se interceptan las llamadas a métodos externos que ingresan a través del proxy. Esto significa que la auto invocación, en efecto, un método dentro del objeto de destino que llama a otro método del objeto de destino, no dará lugar a una transacción real en tiempo de ejecución, incluso si el método invocado está marcado con @Transactional.

Considere el uso del modo AspectJ (consulte el atributo de modo en la tabla a continuación) si espera que las autoinvocaciones también se envuelvan con transacciones. En este caso, no habrá un proxy en primer lugar; en su lugar, la clase objetivo será tejida (es decir, su código de byte será modificado) para convertir @Transactional en comportamiento de tiempo de ejecución en cualquier tipo de método.

Otra forma es el usuario BeanSelfAware

usuario536161
fuente
¿podría agregar una referencia a BeanSelfAware? No parece una clase de primavera
pide el
@asgs Supongamos que se trata de autoinyección (proporcione un bean con una instancia de sí mismo envuelta en un proxy). Puede ver ejemplos en stackoverflow.com/q/3423972/355438 .
Lu55
3

La respuesta es no. Consulte la Referencia de Spring: Uso de @Transactional :

La @Transactionalanotación se puede colocar antes de una definición de interfaz, un método en una interfaz, una definición de clase o un método público en una clase

hiedra
fuente
1

De la misma manera que @loonis sugirió usar TransactionTemplate, uno puede usar este componente auxiliar (Kotlin):

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

Uso:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

No sé si TransactionTemplatereutilizar la transacción existente o no, pero este código definitivamente lo hace.

Lu55
fuente