Apakah atribut Spring @Transactional berfungsi pada metode pribadi?


196

Jika saya memiliki @Transactional -annotation pada metode pribadi dalam kacang Spring, apakah anotasi memiliki efek apapun?

Jika @Transactionalanotasi menggunakan metode publik, anotasi itu berfungsi dan membuka transaksi.

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

  }
}

...

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

Jawaban:


163

Pertanyaannya bukan pribadi atau publik, pertanyaannya adalah: Bagaimana itu dipanggil dan implementasi AOP yang Anda gunakan!

Jika Anda menggunakan (default) Spring Proxy AOP, maka semua fungsionalitas AOP yang disediakan oleh Spring (seperti @Transational) hanya akan diperhitungkan jika panggilan melalui proxy. - Ini biasanya terjadi jika metode beranotasi dipanggil dari yang lain kacang .

Ini memiliki dua implikasi:

  • Karena metode pribadi tidak boleh dipanggil dari kacang lain (pengecualiannya adalah refleksi), @TransactionalAnotasi mereka tidak diperhitungkan.
  • Jika metode ini bersifat publik, tetapi dipanggil dari kacang yang sama, metode tersebut tidak akan diperhitungkan juga (pernyataan ini hanya benar jika (default) Spring Proxy AOP digunakan).

@Lihat Referensi Pegas: Bab 9.6 9.6 Mekanisme proksi

IMHO Anda harus menggunakan mode aspectJ, bukan Spring Proxies, yang akan mengatasi masalah. Dan Aspek Transaksional AspectJ ditenun bahkan menjadi metode pribadi (diperiksa untuk Spring 3.0).


4
Kedua poin itu belum tentu benar. Yang pertama tidak benar - metode pribadi dapat dipanggil secara reflektif, tetapi proxy yang menemukan logika memilih untuk tidak melakukannya. Poin kedua hanya berlaku untuk proksi JDK berbasis antarmuka, tetapi tidak untuk proksi berbasis subkelas CGLIB.
skaffman

@skaffman: 1 - saya membuat statment saya lebih tepat, 2. Tapi Proxy default adalah Antarmuka berbasis - bukan?
Ralph

2
Itu tergantung apakah target menggunakan antarmuka atau tidak. Jika tidak, CGLIB digunakan.
skaffman

canu ceritakan reson atau beberapa referensi mengapa cglib tidak bisa tetapi aspekj bisa?
phil

1
Referensi dari tautan di blok jawaban, jika Anda ingin menggunakan Spring Proxies [lingkungan default], beri anotasi pada doStuff () dan panggil doPrivateStuff () menggunakan ((Bean) AopContext.currentProxy ()). DoPrivateStuff (); Ini akan mengeksekusi kedua metode dalam satu transaksi yang sama jika propagasi diperlukan lagi [lingkungan default].
Michael Ouyang

219

Jawaban pertanyaan Anda adalah tidak - tidak @Transactionalakan berpengaruh jika digunakan untuk menjelaskan metode pribadi. Generator proksi akan mengabaikannya.

Ini didokumentasikan dalam Spring Manual bab 10.5.6 :

Visibilitas metode dan @Transactional

Saat menggunakan proxy, Anda harus menerapkan @Transactionalanotasi hanya untuk metode dengan visibilitas publik. Jika Anda melakukan anotasi metode terlindungi, pribadi atau paket-terlihat dengan @Transactionalanotasi, tidak ada kesalahan yang muncul, tetapi metode anotasi tidak menunjukkan pengaturan transaksional yang dikonfigurasi. Pertimbangkan penggunaan AspectJ (lihat di bawah) jika Anda perlu membuat anotasi metode non-publik.


Apa kau yakin tentang ini? Saya tidak berharap itu akan membuat perbedaan.
willcodejavaforfood

bagaimana kalau gaya proxy adalah Cglib?
lily

32

Secara default, @Transactionalatribut hanya berfungsi saat memanggil metode beranotasi pada referensi yang diperoleh dari applicationContext.

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

  }
}

Ini akan membuka transaksi:

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

Ini tidak akan:

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

Referensi Pegas: Menggunakan @Transaksional

Catatan: Dalam mode proxy (yang merupakan default), hanya panggilan metode 'eksternal' yang masuk melalui proxy yang akan disadap. Ini berarti 'invokasi diri', yaitu metode di dalam objek target yang memanggil beberapa metode lain dari objek target, tidak akan mengarah pada transaksi aktual pada saat runtime bahkan jika metode yang dipanggil ditandai dengan @Transactional!

Pertimbangkan penggunaan mode AspectJ (lihat di bawah) jika Anda mengharapkan pemanggilan sendiri akan dibungkus dengan transaksi juga. Dalam hal ini, tidak akan ada proxy di tempat pertama; sebagai gantinya, kelas target akan 'ditenun' (yaitu kode byte-nya akan dimodifikasi) untuk berubah @Transactionalmenjadi perilaku runtime pada segala jenis metode.


Apakah maksud Anda bean = new Bean () ;?
willcodejavaforfood

Nggak. Jika saya membuat kacang dengan Bean baru (), anotasi tidak akan pernah bekerja setidaknya tanpa menggunakan Aspect-J.
Juha Syrjälä

2
Terima kasih! Ini menjelaskan perilaku aneh yang saya amati. Cukup berlawanan dengan
intrik

Saya belajar "hanya panggilan metode eksternal yang masuk melalui proxy yang akan dicegat" dengan cara yang sulit
asgs

13

Ya, dimungkinkan untuk menggunakan @Transactional pada metode pribadi, tetapi seperti yang orang lain katakan ini tidak akan berhasil. Anda perlu menggunakan AspectJ. Butuh beberapa waktu untuk memikirkan cara membuatnya bekerja. Saya akan membagikan hasil saya.

Saya memilih untuk menggunakan tenun waktu kompilasi alih-alih tenun waktu karena saya pikir ini merupakan pilihan yang lebih baik secara keseluruhan. Saya juga menggunakan Java 8 sehingga Anda mungkin perlu menyesuaikan beberapa parameter.

Pertama, tambahkan dependensi untuk aspekjrt.

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

Kemudian tambahkan plugin AspectJ untuk melakukan tenun bytecode yang sebenarnya di Maven (ini mungkin bukan contoh minimal).

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

Akhirnya tambahkan ini ke kelas konfigurasi Anda

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Sekarang Anda harus dapat menggunakan @Transactional pada metode pribadi.

Satu peringatan untuk pendekatan ini: Anda harus mengkonfigurasi IDE Anda untuk mengetahui AspectJ sebaliknya jika Anda menjalankan aplikasi melalui Eclipse misalnya itu mungkin tidak berfungsi. Pastikan Anda menguji terhadap Maven langsung sebagai cek kewarasan.


jika metode proxy adalah cglib, tidak perlu untuk mengimplementasikan antarmuka yang mana metode tersebut harus publik, maka ia dapat menggunakan @Transactional pada metode pribadi?
lily

Ya, ini bekerja pada metode pribadi, dan tanpa antarmuka! Selama AspectJ dikonfigurasi dengan benar, pada dasarnya menjamin dekorator metode kerja. Dan user536161 menunjukkan dalam jawabannya bahwa itu bahkan akan bekerja pada doa diri. Ini sangat keren, dan hanya sedikit menakutkan.
James Watkins

12

Jika Anda perlu membungkus metode pribadi dalam transaksi dan tidak ingin menggunakan aspectj, Anda dapat menggunakan 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(){
        //...
    }

}

Bagus untuk menunjukkan TransactionTemplatepenggunaan, tetapi harap panggil metode kedua itu ..RequiresTransactiondaripada ..InTransaction. Selalu beri nama benda yang ingin Anda baca setahun kemudian. Saya juga akan berpendapat untuk berpikir jika itu benar-benar memerlukan metode pribadi kedua: Entah menempatkan kontennya secara langsung dalam executeimplementasi anonim atau jika itu menjadi berantakan itu mungkin merupakan indikasi untuk membagi implementasi menjadi layanan lain yang kemudian dapat Anda anotasi @Transactional.
Terjebak

@ Terjebak, metode ke-2 memang tidak perlu, tetapi menjawab pertanyaan awal yaitu bagaimana menerapkan transaksi pegas pada metode pribadi
loonis

ya, saya sudah memilih jawaban tetapi ingin berbagi beberapa konteks dan pemikiran tentang bagaimana menerapkannya, karena saya pikir dari sudut pandang arsitektur situasi ini merupakan indikasi potensial untuk cacat desain.
Terjebak

5

Spring Documents menjelaskan hal itu

Dalam mode proxy (yang merupakan default), hanya panggilan metode eksternal yang masuk melalui proxy yang disadap. Ini berarti bahwa invokasi diri, pada dasarnya, sebuah metode di dalam objek target yang memanggil metode lain dari objek target, tidak akan mengarah pada transaksi aktual saat runtime bahkan jika metode yang dipanggil ditandai dengan @Transactional.

Pertimbangkan penggunaan mode AspectJ (lihat atribut mode dalam tabel di bawah) jika Anda mengharapkan pemanggilan sendiri akan dibungkus dengan transaksi juga. Dalam hal ini, tidak akan ada proxy di tempat pertama; sebagai gantinya, kelas target akan dianyam (yaitu, kode byte-nya akan dimodifikasi) untuk mengubah @Transaksional menjadi perilaku runtime pada metode apa pun.

Cara lain adalah pengguna BeanSelfAware


bisakah Anda menambahkan referensi BeanSelfAware? Itu tidak terlihat seperti kelas Spring
as

@ Asgs Misalkan, ini tentang injeksi mandiri (sediakan kacang dengan contoh itu sendiri dibungkus menjadi proxy). Anda dapat melihat contoh di stackoverflow.com/q/3423972/355438 .
Lu55


1

Cara yang sama seperti @loonis menyarankan untuk menggunakan TransactionTemplate seseorang dapat menggunakan komponen pembantu ini (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()
    }
}

Pemakaian:

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

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

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

Tidak tahu apakah TransactionTemplatemenggunakan kembali transaksi yang ada atau tidak, tetapi kode ini pasti melakukannya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.