Bagaimana cara memaksa komit secara manual dalam metode @Transaksional? [duplikat]

Saya menggunakan Spring/Spring-data-JPA dan merasa perlu memaksakan komit secara manual dalam pengujian unit. Kasus penggunaan saya adalah saya melakukan pengujian multi-utas di mana saya harus menggunakan data yang disimpan sebelum utas dibuat.

Sayangnya, mengingat pengujian berjalan dalam transaksi @Transactional, bahkan flush tidak membuatnya dapat diakses oleh thread yang muncul.

   @Transactional   
   public void testAddAttachment() throws Exception{
        final Contract c1 = contractDOD.getNewTransientContract(15);
        contractRepository.save(c1);

        // Need to commit the saveContract here, but don't know how!                
        em.getTransaction().commit();

        List<Thread> threads = new ArrayList<>();
        for( int i = 0; i < 5; i++){
            final int threadNumber = i; 
            Thread t =  new Thread( new Runnable() {
                @Override
                @Transactional
                public void run() {
                    try {
                        // do stuff here with c1

                        // sleep to ensure that the thread is not finished before another thread catches up
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
            threads.add(t);
            t.start();
        }

        // have to wait for all threads to complete
        for( Thread t : threads )
            t.join();

        // Need to validate test results.  Need to be within a transaction here
        Contract c2 = contractRepository.findOne(c1.getId());
    }

Saya sudah mencoba menggunakan manajer entitas, tetapi mendapat pesan kesalahan saat melakukannya:

org.springframework.dao.InvalidDataAccessApiUsageException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead; nested exception is java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:293)
    at org.springframework.orm.jpa.aspectj.JpaExceptionTranslatorAspect.ajc$afterThrowing$org_springframework_orm_jpa_aspectj_JpaExceptionTranslatorAspect$1$18a1ac9(JpaExceptionTranslatorAspect.aj:33)

Apakah ada cara untuk melakukan transaksi dan melanjutkannya? Saya tidak dapat menemukan metode apa pun yang memungkinkan saya menelepon commit().


person Eric B.    schedule 21.06.2014    source sumber
comment
Anda mungkin meneliti apakah ada cara agar thread yang muncul berpartisipasi dalam transaksi sehingga mereka akan melihat hasil yang tidak dikomit.   -  person Jim Garrison    schedule 21.06.2014
comment
Jika metodenya adalah @Transactional, kembali dari metode tersebut akan melakukan transaksi. Jadi mengapa tidak kembali saja dari metode tersebut?   -  person Raedwald    schedule 21.06.2014
comment
Secara konseptual pengujian unit seharusnya tidak bersifat transaksional, dan dengan model Spring hal ini juga tidak masuk akal secara praktis. Anda harus melihat tes integrasi menggunakan Spring TestContext yang memiliki alat untuk membantu transaksi: docs.spring.io/spring/docs/3.2.x/spring-framework-reference/   -  person Matt Whipple    schedule 21.06.2014
comment
@JimGarrison Sebenarnya inti dari pengujian unit saya adalah untuk menguji transaksi paralel dan memvalidasi bahwa tidak ada masalah konkurensi dalam transaksi.   -  person Eric B.    schedule 22.06.2014
comment
@Raedwald jika saya kembali dari metode ini, bagaimana cara melanjutkan pengujian saya? Saya memerlukan komit sebelum utas saya muncul karena utas menggunakan data yang dibuat sebelum muncul.   -  person Eric B.    schedule 22.06.2014
comment
Ngomong-ngomong, jika Anda menjalankan repositori, Anda memiliki metode flush() di JPARepository Anda. Lihat docs.spring.io/spring-data/jpa/docs/current/api/org/   -  person Josh    schedule 22.08.2016


Jawaban (3)


Saya memiliki kasus penggunaan serupa selama pengujian pendengar acara hibernasi yang hanya dipanggil saat komit.

Solusinya adalah dengan menggabungkan kode agar persisten ke dalam metode lain yang dianotasi dengan REQUIRES_NEW. (Di kelas lain) Dengan cara ini transaksi baru muncul dan flush/komit dikeluarkan setelah metode kembali.

Tx prop REQUIRES_NEW

Ingatlah bahwa ini mungkin mempengaruhi semua tes lainnya! Jadi tulislah sesuai atau Anda perlu memastikan bahwa Anda dapat membersihkannya setelah tes dijalankan.

person Martin Frey    schedule 21.06.2014
comment
Cemerlang. Belum memikirkan hal itu. Saya harus sedikit memecah metode pengujian saya, yang mana saya tidak terlalu menyukainya, namun berhasil dengan baik. - person Eric B.; 22.06.2014
comment
@MartinFrey Mengapa Di kelas lain? - person Basemasta; 07.09.2014
comment
Karena pegas bekerja dengan proxy untuk mencapai fitur ini (banyak fitur lainnya juga), Anda harus menggunakan kelas lain sehingga pegas dapat memicu transaksi. Setelah Anda berada di dalam kelas, Anda tidak akan melalui proxy lagi. - person Martin Frey; 07.09.2014
comment
Bisakah ini menjadi kelas dalam? - person Gleeb; 11.05.2015
comment
Bisa saja, asalkan spring bean. Tidak mengujinya seperti ini. Selamat mencoba :) - person Martin Frey; 11.05.2015
comment
Bisakah Anda memposting kutipan kode? Lakukan apa adanya, tetapi hanya satu transaksi yang pernah dilakukan di akhir pengujian - person PragmaticProgrammer; 29.06.2018
comment
Bagaimana REQUIRES_NEW akan memperbaiki masalah ini? Seperti yang Anda katakan, itu hanya menangguhkan transaksi induk dan belum tentu mengkomitnya (induk). Apakah ini berarti orang tua menunggu anaknya kembali dan baru kemudian komitmen orang tua terjadi? Jika demikian, maka itu bukan solusi 100% karena cara kerja threading. Itu hanya meningkatkan kemungkinan menghindari kondisi balapan - person Aladin; 30.07.2020
comment
Jika Anda memiliki transaksi bersarang, saya berasumsi bahwa intinya Anda tidak ingin menangguhkan transaksi luar. Misalnya. jika ada aliran yang ingin Anda tetap buka. - person html_programmer; 11.06.2021

Mengapa Anda tidak menggunakan TransactionTemplate pegas untuk mengontrol transaksi secara terprogram? Anda juga dapat menyusun ulang kode Anda sehingga setiap "blok transaksi" memiliki metode @Transactional sendiri, namun mengingat ini adalah pengujian, saya akan memilih kontrol terprogram atas transaksi Anda.

Perhatikan juga bahwa anotasi @Transactional pada runnable Anda tidak akan berfungsi (kecuali jika Anda menggunakan aspekj) karena runnable tidak dikelola pada musim semi!

@RunWith(SpringJUnit4ClassRunner.class)
//other spring-test annotations; as your database context is dirty due to the committed transaction you might want to consider using @DirtiesContext
public class TransactionTemplateTest {

@Autowired
PlatformTransactionManager platformTransactionManager;

TransactionTemplate transactionTemplate;

@Before
public void setUp() throws Exception {
    transactionTemplate = new TransactionTemplate(platformTransactionManager);
}

@Test //note that there is no @Transactional configured for the method
public void test() throws InterruptedException {

    final Contract c1 = transactionTemplate.execute(new TransactionCallback<Contract>() {
        @Override
        public Contract doInTransaction(TransactionStatus status) {
            Contract c = contractDOD.getNewTransientContract(15);
            contractRepository.save(c);
            return c;
        }
    });

    ExecutorService executorService = Executors.newFixedThreadPool(5);

    for (int i = 0; i < 5; ++i) {
        executorService.execute(new Runnable() {
            @Override  //note that there is no @Transactional configured for the method
            public void run() {
                transactionTemplate.execute(new TransactionCallback<Object>() {
                    @Override
                    public Object doInTransaction(TransactionStatus status) {
                        // do whatever you want to do with c1
                        return null;
                    }
                });
            }
        });
    }

    executorService.shutdown();
    executorService.awaitTermination(10, TimeUnit.SECONDS);

    transactionTemplate.execute(new TransactionCallback<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
            // validate test results in transaction
            return null;
        }
    });
}

}

person Pieter    schedule 21.06.2014
comment
Terima kasih atas idenya. Telah mempertimbangkan hal itu, tetapi sepertinya banyak pekerjaan/berlebihan untuk masalah sederhana. Saya berasumsi bahwa pasti ada sesuatu yang lebih sederhana. Memecahnya menjadi metode terpisah dengan Propagation.REQUIRES_NEW mencapai hal itu (lihat jawaban @ MartinFrey). - person Eric B.; 22.06.2014
comment
Untuk kasus saya ini hanya berfungsi dengan transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); Lihat kode di sini - person Grigory Kislin; 26.09.2017

Saya tahu bahwa karena penggunaan TransactionTemplate kelas dalam anonim yang jelek ini tidak terlihat bagus, tetapi ketika karena alasan tertentu kita ingin memiliki metode pengujian IMHO transaksional, itu adalah opsi yang paling fleksibel.

Dalam beberapa kasus (tergantung pada jenis aplikasi) cara terbaik untuk menggunakan transaksi dalam pengujian Spring adalah dengan menonaktifkan @Transactional pada metode pengujian. Mengapa? Karena @Transactional dapat menghasilkan banyak tes positif palsu. Anda dapat melihat contoh artikel ini untuk mengetahui detailnya . Dalam kasus seperti ini TransactionTemplate bisa menjadi sempurna untuk mengendalikan batas-batas transaksi ketika kita menginginkan kendali itu.

person G. Demecki    schedule 23.10.2014
comment
Hai, saya ingin tahu apakah ini berlaku: Saat membuat pengujian integrasi pada pernyataan yang menyimpan objek, disarankan untuk menghapus manajer entitas untuk menghindari negatif palsu, yaitu untuk menghindari pengujian berjalan dengan baik tetapi operasinya akan gagal saat dijalankan dalam produksi. Memang benar, pengujian mungkin berjalan dengan baik hanya karena cache tingkat pertama tidak dihapus dan tidak ada tulisan yang masuk ke database. Untuk menghindari pengujian integrasi negatif palsu ini, gunakan flush eksplisit di badan pengujian. - person Stephane; 08.10.2015
comment
@StephaneEybert IMO hanya membilas EntityManager lebih seperti peretasan :-) Tapi itu tergantung pada kebutuhan dan jenis tes yang Anda lakukan. Untuk pengujian integrasi nyata (yang seharusnya berperilaku persis seperti dalam produksi) jawabannya adalah: jangan gunakan @Transactional di sekitar pengujian. Namun kelemahannya juga ada: Anda harus menyiapkan database ke status yang diketahui sebelum setiap pengujian tersebut. - person G. Demecki; 09.10.2015