Spring - @Transaksional - Apa yang terjadi di latar belakang?

334

Saya ingin tahu apa yang sebenarnya terjadi ketika Anda menjelaskan metode dengan @Transactional? Tentu saja, saya tahu Spring akan membungkus metode itu dalam suatu Transaksi.

Tapi, saya memiliki keraguan sebagai berikut:

  1. Saya mendengar bahwa Spring membuat kelas proxy ? Dapatkah seseorang menjelaskan hal ini secara lebih mendalam . Apa yang sebenarnya berada di kelas proxy itu? Apa yang terjadi pada kelas aktual? Dan bagaimana saya bisa melihat kelas proksi yang dibuat Spring
  2. Saya juga membaca dalam dokumen Spring bahwa:

Catatan: Karena mekanisme ini didasarkan pada proksi, hanya panggilan metode 'eksternal' yang masuk melalui proxy yang akan disadap . Ini berarti bahwa 'invokasi diri', yaitu suatu metode di dalam objek target yang memanggil beberapa metode lain dari objek target, tidak akan mengarah pada transaksi aktual saat runtime bahkan jika metode yang dipanggil ditandai dengan @Transactional!

Sumber: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Mengapa hanya panggilan metode eksternal yang berada di bawah Transaksi dan bukan metode doa mandiri?

peakit
sumber
2
Diskusi yang relevan ada di sini: stackoverflow.com/questions/3120143/…
dma_k

Jawaban:

255

Ini adalah topik besar. Dokumen referensi Spring mencurahkan banyak bab untuk itu. Saya sarankan membaca yang pada Pemrograman Berorientasi Aspek dan Transaksi , karena dukungan transaksi deklaratif Spring menggunakan AOP di yayasannya.

Tetapi pada level yang sangat tinggi, Spring menciptakan proksi untuk kelas yang menyatakan @Transaksional pada kelas itu sendiri atau pada anggota. Proxy sebagian besar tidak terlihat saat runtime. Ini memberikan cara bagi Spring untuk menyuntikkan perilaku sebelum, setelah, atau sekitar pemanggilan metode ke objek yang sedang diproksikan. Manajemen transaksi hanyalah salah satu contoh perilaku yang dapat dihubungkan. Pemeriksaan keamanan adalah hal lain. Dan Anda juga bisa menyediakan sendiri untuk hal-hal seperti logging. Jadi, ketika Anda membubuhi keterangan metode dengan @Transaksional , Spring secara dinamis membuat proksi yang mengimplementasikan antarmuka yang sama dengan kelas yang Anda beri penjelasan. Dan ketika klien melakukan panggilan ke objek Anda, panggilan dicegat dan perilaku disuntikkan melalui mekanisme proxy.

Transaksi di EJB bekerja dengan cara yang sama.

Seperti yang Anda amati, melalui, mekanisme proksi hanya berfungsi ketika panggilan masuk dari beberapa objek eksternal. Saat Anda membuat panggilan internal di dalam objek, Anda benar-benar melakukan panggilan melalui referensi " ini ", yang melewati proxy. Namun, ada beberapa cara untuk mengatasi masalah itu. Saya menjelaskan satu pendekatan dalam posting forum ini di mana saya menggunakan BeanFactoryPostProcessor untuk menyuntikkan instance dari proxy ke kelas "referensi diri" pada saat runtime. Saya menyimpan referensi ini ke variabel anggota yang disebut " saya ". Kemudian jika saya perlu membuat panggilan internal yang memerlukan perubahan dalam status transaksi utas, saya mengarahkan panggilan melalui proxy (mis. " Me.someMethod ()".) Posting forum menjelaskan lebih detail. Perhatikan bahwa BeanFactoryPostProcessorkode akan sedikit berbeda sekarang, seperti yang ditulis kembali dalam jangka waktu Musim Semi 1.x. Tapi mudah-mudahan ini memberi Anda ide. Saya memiliki versi terbaru yang mungkin bisa saya sediakan.

Rob H
sumber
4
>> Proksi sebagian besar tidak terlihat saat runtime Oh !! Saya penasaran ingin bertemu mereka :) Istirahat .. jawaban Anda sangat komprehensif. Ini adalah kedua kalinya Anda membantu saya .. Terima kasih atas semua bantuannya.
peakit
17
Tidak masalah. Anda dapat melihat kode proksi jika Anda melangkah dengan debugger. Itu mungkin cara termudah. Tidak ada keajaiban; mereka hanya kelas dalam paket Spring.
Rob H
Dan jika metode yang memiliki anotasi @Transaction menerapkan antarmuka, pegas akan menggunakan API proxy dinamis untuk menyuntikkan transaksionalisasi dan tidak menggunakan proxy. Saya lebih suka memiliki kelas transaksional saya mengimplementasikan antarmuka dalam hal apa pun.
Michael Wiles
1
Saya menemukan skema "saya" juga (menggunakan kabel eksplisit untuk melakukannya sesuai dengan cara saya berpikir), tapi saya pikir jika Anda melakukannya dengan cara itu, Anda mungkin lebih baik refactoring sehingga Anda tidak harus. Tapi ya, itu terkadang sangat canggung!
Donal Fellows
2
2019: Karena jawaban ini semakin tua, posting forum yang dimaksud tidak lagi tersedia yang akan menggambarkan kasus ketika Anda harus membuat panggilan internal di dalam objek tanpa melewati proxy, menggunakanBeanFactoryPostProcessor . Namun, ada (menurut saya) metode yang sangat mirip yang dijelaskan dalam jawaban ini: stackoverflow.com/a/11277899/3667003 ... dan solusi lebih lanjut di seluruh utas juga.
Z3d4s
196

Ketika Spring memuat definisi kacang Anda, dan telah dikonfigurasi untuk mencari @Transactionalanotasi, itu akan membuat objek proxy ini di sekitar kacang Anda yang sebenarnya . Objek proxy ini adalah instance dari kelas yang dihasilkan secara otomatis saat runtime. Perilaku default objek-objek proxy ini ketika sebuah metode dipanggil hanya untuk memanggil metode yang sama pada kacang "target" (yaitu kacang Anda).

Namun, proksi juga dapat diberikan dengan pencegat, dan saat ini pencegat ini akan dipanggil oleh proxy sebelum memanggil metode target bean Anda. Untuk kacang target yang dijelaskan dengan @Transactional, Spring akan membuat TransactionInterceptor, dan meneruskannya ke objek proxy yang dihasilkan. Jadi ketika Anda memanggil metode dari kode klien, Anda memanggil metode pada objek proxy, yang pertama kali memanggil TransactionInterceptor(yang memulai transaksi), yang pada gilirannya memanggil metode pada kacang target Anda. Ketika doa selesai, TransactionInterceptorkomit / memutar kembali transaksi. Ini transparan untuk kode klien.

Adapun hal "metode eksternal", jika kacang Anda memanggil salah satu metodenya sendiri, maka kacang itu tidak akan melakukannya melalui proxy. Ingat, Spring membungkus kacang Anda di proxy, kacang Anda tidak memiliki pengetahuan tentang itu. Hanya panggilan dari "luar" kacang Anda melalui proxy.

Apakah itu membantu?

skaffman
sumber
36
> Ingat, Spring membungkus kacang Anda dalam proxy, kacang Anda tidak memiliki pengetahuan tentang hal ini. Ini mengatakan semuanya. Sungguh jawaban yang bagus. Terima kasih telah membantu.
peakit
Penjelasan yang bagus, untuk proksi dan pencegat. Sekarang saya mengerti spring mengimplementasikan objek proxy untuk mencegat panggilan ke target bean. Terima kasih!
dharag
Saya pikir Anda mencoba menggambarkan gambar dokumentasi Spring ini dan melihat gambar ini banyak membantu saya: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun
44

Sebagai orang visual, saya suka menimbang dengan diagram urutan pola proxy. Jika Anda tidak tahu cara membaca panah, saya membaca yang pertama seperti ini: Clientdijalankan Proxy.method().

  1. Klien memanggil metode pada target dari sudut pandangnya, dan diam-diam dicegat oleh proxy
  2. Jika aspek sebelum didefinisikan, proksi akan menjalankannya
  3. Kemudian, metode aktual (target) dieksekusi
  4. Setelah kembali dan setelah melempar adalah aspek opsional yang dieksekusi setelah metode kembali dan / atau jika metode melempar pengecualian
  5. Setelah itu, proxy mengeksekusi aspek after (jika didefinisikan)
  6. Akhirnya proksi kembali ke klien panggilan

Diagram Urutan Pola Proxy (Saya diizinkan memposting foto dengan syarat saya menyebutkan asal-usulnya. Penulis: Noel Vaes, situs web: www.noelvaes.eu)

progonkpa
sumber
27

Jawaban paling sederhana adalah:

Pada metode mana pun Anda menyatakan @Transactionalbatas transaksi dimulai dan batas berakhir ketika metode selesai.

Jika Anda menggunakan panggilan JPA maka semua komit ada dalam batas transaksi ini .

Katakanlah Anda menyimpan entitas1, entitas2 dan entitas3. Sekarang saat menyimpan entitas3 pengecualian terjadi , maka sebagai enitiy1 dan entitas2 datang dalam transaksi yang sama sehingga entitas1 dan entitas2 akan rollback dengan entitas3.

Transaksi :

  1. entitas1.save
  2. entitas2.save
  3. entitas3.save

Pengecualian akan menghasilkan kemunduran semua transaksi JPA dengan DB. Secara internal transaksi JPA digunakan oleh Spring.

RoshanKumar Mutha
sumber
2
"Pengecualian akan menyebabkan rollback semua transaksi JPA dengan DB." Catatan Only RuntimeException menghasilkan rollback. Pengecualian yang dicentang, tidak akan menghasilkan kembalikan.
Arjun
2

Mungkin terlambat tapi saya menemukan sesuatu yang menjelaskan kekhawatiran Anda terkait dengan proxy (hanya panggilan metode 'eksternal' yang masuk melalui proxy yang akan dicegat) dengan baik.

Misalnya, Anda memiliki kelas yang terlihat seperti ini

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

dan Anda memiliki aspek, yang terlihat seperti ini:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

Ketika Anda menjalankannya seperti ini:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

Hasil pemanggilan kickOff di atas diberikan kode di atas.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

tetapi ketika Anda mengubah kode Anda ke

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

Anda lihat, metode ini memanggil metode lain secara internal sehingga tidak akan dicegat dan hasilnya akan terlihat seperti ini:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

Anda dapat melewati ini dengan melakukan itu

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Cuplikan kode diambil dari: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

Danyal Sandeelo
sumber
1

Semua jawaban yang ada benar, tetapi saya merasa tidak bisa memberikan topik yang kompleks ini.

Untuk penjelasan praktis dan komprehensif, Anda mungkin ingin melihat pada panduan Kedalaman Transaksional Spring @Transactional ini , yang mencoba yang terbaik untuk membahas manajemen transaksi dalam ~ 4000 kata sederhana, dengan banyak contoh kode.

Marco Behler
sumber
Itu hebat ...
Alpit Anand