แอตทริบิวต์ Spring @Transactional ทำงานกับวิธีส่วนตัวหรือไม่


196

หากฉันมี@Transactional -คำอธิบายประกอบในวิธีส่วนตัวใน Spring bean คำอธิบายประกอบจะมีผลกระทบหรือไม่?

หาก@Transactionalคำอธิบายประกอบเป็นวิธีสาธารณะมันจะทำงานและเปิดธุรกรรม

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

  }
}

...

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

คำตอบ:


163

คำถามนี้ไม่ได้เป็นแบบส่วนตัวหรือสาธารณะคำถามคือ: เป็นอย่างไรและมีการใช้งาน AOP แบบใด!

หากคุณใช้ (ค่าเริ่มต้น) Spring Proxy AOP ดังนั้นฟังก์ชั่น AOP ทั้งหมดที่มีให้โดย Spring (เช่น@Transational) จะถูกนำมาพิจารณาเฉพาะเมื่อมีการโทรผ่านพร็อกซี - กรณีนี้เป็นกรณีปกติหากวิธีการอธิบายประกอบถูกเรียกใช้จากbean อื่น

สิ่งนี้มีสองนัย:

  • เนื่องจากวิธีการส่วนตัวจะต้องไม่ถูกเรียกใช้จาก bean อื่น (ข้อยกเว้นเป็นการสะท้อนกลับ) @Transactionalคำอธิบายประกอบจึงไม่นำมาพิจารณา
  • หากวิธีนั้นเป็นแบบสาธารณะ แต่มันถูกเรียกใช้จาก bean เดียวกันมันจะไม่ถูกนำมาพิจารณาอย่างใดอย่างหนึ่ง (คำสั่งนี้จะถูกต้องก็ต่อเมื่อใช้ค่าเริ่มต้น) Spring Proxy AOP)

@See Spring Reference: บทที่ 9.6 9.6 กลไกการจัดทำ

IMHO คุณควรใช้โหมด widthJ แทนที่จะเป็น Spring Proxies ที่จะเอาชนะปัญหาได้ และ AspectJ Transactional Aspects ได้รับการทอเป็นวิธีการส่วนตัว (ตรวจสอบสำหรับ Spring 3.0)


4
คะแนนทั้งสองไม่จำเป็นต้องเป็นความจริง วิธีแรกไม่ถูกต้อง - วิธีส่วนตัวสามารถเรียกใช้แบบไตร่ตรองได้ แต่พร็อกซีที่ค้นพบลอจิกเลือกที่จะไม่ทำเช่นนั้น จุดที่สองนั้นเป็นจริงสำหรับพร็อกซีที่ใช้อินเตอร์เฟส JDK เท่านั้น แต่ไม่ใช่สำหรับพร็อกซีที่ใช้คลาสย่อย CGLIB
skaffman

@skaffman: 1 - ฉันทำให้สถานะของฉันแม่นยำยิ่งขึ้น 2 แต่พร็อกซีเริ่มต้นคือส่วนต่อประสานที่ใช้ - ใช่ไหม
Ralph

2
ขึ้นอยู่กับว่าเป้าหมายใช้อินเตอร์เฟสหรือไม่ ถ้าไม่ใช้ก็จะใช้ CGLIB
skaffman

คุณสามารถบอก reson หรือการอ้างอิงว่าทำไม cglib ถึงทำไม่ได้
phil

1
อ้างอิงจากลิงก์ในบล็อกคำตอบหากคุณต้องการใช้ Spring Proxies [สภาพแวดล้อมเริ่มต้น] ใส่คำอธิบายประกอบบน doStuff () และโทร doPrivateStuff () โดยใช้ ((Bean) AopContext.currentProxy ()) doPrivateStuff (); มันจะดำเนินการทั้งสองวิธีในการทำธุรกรรมเดียวกันถ้าการแพร่กระจาย reeuired [สภาพแวดล้อมเริ่มต้น]
Michael Ouyang

219

คำตอบสำหรับคำถามของคุณคือ - @Transactionalจะไม่มีผลกระทบหากใช้เพื่ออธิบายวิธีส่วนตัว ตัวสร้างพร็อกซีจะไม่สนใจพวกเขา

นี่เป็นเอกสารในคู่มือสปริงบทที่ 10.5.6 :

วิธีการมองเห็นและ @Transactional

เมื่อใช้พร็อกซีคุณควรใช้@Transactionalคำอธิบายประกอบกับวิธีการเปิดเผยต่อสาธารณะเท่านั้น ถ้าคุณทำหมายเหตุประกอบที่ได้รับการป้องกันวิธีส่วนตัวหรือมองเห็นแพคเกจพร้อม @Transactionalคำอธิบายประกอบจะไม่มีข้อผิดพลาดเกิดขึ้น แต่วิธีการใส่คำอธิบายประกอบนั้นจะไม่แสดงการตั้งค่าธุรกรรมที่กำหนดไว้ พิจารณาการใช้ AspectJ (ดูด้านล่าง) หากคุณต้องการใช้คำอธิบายประกอบที่ไม่ใช่แบบสาธารณะ


คุณแน่ใจเกี่ยวกับเรื่องนี้? ฉันไม่คิดว่าจะสร้างความแตกต่าง
willcodejavaforfood

ถ้าสไตล์พร็อกซี่เป็น Cglib ล่ะ?
ลิลลี่

32

ตามค่าดีฟอลต์แล้วแอ@Transactionalททริบิวต์จะทำงานเฉพาะเมื่อเรียกใช้วิธีการเพิ่มความคิดเห็นในการอ้างอิงที่ได้รับจาก applicationContext

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

  }
}

นี่จะเป็นการเปิดธุรกรรม:

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

สิ่งนี้จะไม่:

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

Spring Reference: การใช้ @Transactional

หมายเหตุ: ในโหมดพร็อกซี (ซึ่งเป็นค่าเริ่มต้น) เฉพาะการเรียกเมธอด 'ภายนอก' ที่เข้ามาทางพร็อกซีเท่านั้นที่จะถูกดักจับ ซึ่งหมายความว่า 'การภาวนาด้วยตนเอง' เช่นวิธีการภายในวัตถุเป้าหมายที่เรียกวิธีการอื่นของวัตถุเป้าหมายจะไม่นำไปสู่การทำธุรกรรมจริงที่รันไทม์แม้ว่าวิธีการที่เรียกใช้จะถูกทำเครื่องหมายด้วย@Transactional!

พิจารณาการใช้โหมด AspectJ (ดูด้านล่าง) หากคุณคาดว่าจะทำการรวมการทำธุรกรรมด้วยตนเองเช่นกัน ในกรณีนี้จะไม่มีพร็อกซีในตอนแรก คลาสเป้าหมายจะเป็น 'weaved' (เช่นโค้ดไบต์จะถูกแก้ไข) เพื่อเปลี่ยน@Transactionalเป็นลักษณะการทำงานแบบรันไทม์ในวิธีการใด ๆ


คุณหมายถึง bean = new Bean () ;?
willcodejavaforfood

Nope ถ้าฉันสร้างถั่วด้วย Bean ใหม่ () คำอธิบายประกอบจะไม่ทำงานอย่างน้อยโดยไม่ต้องใช้ Aspect-J
Juha Syrjälä

2
ขอบคุณ! สิ่งนี้อธิบายถึงพฤติกรรมแปลก ๆ ที่ฉันสังเกต ค่อนข้างเคาน์เตอร์ใช้งานง่ายข้อ จำกัด การภาวนาวิธีการภายในนี้ ...
Manuel aldana

ฉันได้เรียนรู้ว่า "การโทรด้วยวิธีภายนอกเท่านั้นที่เข้ามาทางพร็อกซีจะถูกดัก" วิธีที่ยาก
asgs

13

ใช่เป็นไปได้ที่จะใช้ @Transactional ในวิธีการส่วนตัว แต่อย่างที่คนอื่น ๆ ได้กล่าวถึงสิ่งนี้จะไม่ได้ผล คุณต้องใช้ AspectJ ฉันใช้เวลาพอสมควรในการหาวิธีการทำงาน ฉันจะแบ่งปันผลลัพธ์ของฉัน

ฉันเลือกที่จะใช้การทอผ้าแบบคอมไพล์แทนการทอผ้าแบบโหลดโหลดเพราะฉันคิดว่ามันเป็นตัวเลือกที่ดีกว่าโดยรวม นอกจากนี้ฉันใช้ Java 8 ดังนั้นคุณอาจต้องปรับพารามิเตอร์บางอย่าง

ก่อนอื่นให้เพิ่มการพึ่งพาสำหรับกว้างยาว

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

จากนั้นเพิ่มปลั๊กอิน AspectJ เพื่อทำ bytecode ที่แท้จริงใน Maven (นี่อาจไม่ใช่ตัวอย่างขั้นต่ำ)

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

ในที่สุดเพิ่มสิ่งนี้ไปยังระดับ config ของคุณ

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

ตอนนี้คุณควรจะสามารถใช้ @Transactional บนวิธีการส่วนตัวได้

ข้อแม้หนึ่งสำหรับวิธีนี้: คุณจะต้องกำหนดค่า IDE ของคุณให้ระวัง AspectJ ไม่เช่นนั้นหากคุณเรียกใช้แอปผ่าน Eclipse เช่นอาจไม่ทำงาน ตรวจสอบให้แน่ใจว่าคุณทดสอบกับ Maven build โดยตรงเป็นการตรวจสอบสติ


หากวิธีการ proxying เป็น cglib ไม่จำเป็นต้องใช้ส่วนต่อประสานที่วิธีการที่ควรเป็นสาธารณะแล้วมันจะสามารถใช้ @Transactional ในวิธีการส่วนตัว?
ลิลลี่

ใช่มันทำงานบนวิธีการส่วนตัวและไม่มีส่วนต่อประสาน! ตราบใดที่ AspectJ ได้รับการกำหนดค่าอย่างเหมาะสมมันก็รับประกันการตกแต่งภายในของวิธีการทำงาน และ user536161 ชี้ให้เห็นในคำตอบของเขาว่ามันจะทำงานด้วยการเรียกใช้ตนเอง มันเจ๋งจริงๆและน่ากลัวนิดหน่อย
James Watkins

12

หากคุณจำเป็นต้องห่อวิธีการส่วนตัวภายในการทำธุรกรรมและไม่ต้องการที่จะใช้ AspectJ คุณสามารถใช้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(){
        //...
    }

}

ดีที่จะแสดงTransactionTemplateการใช้งาน แต่โปรดเรียกว่าวิธีที่สองมากกว่า..RequiresTransaction ..InTransactionตั้งชื่อสิ่งต่าง ๆ เสมอว่าคุณต้องการอ่านมันในอีกหนึ่งปีต่อมา นอกจากนี้ฉันอยากจะคิดว่าถ้ามันต้องใช้วิธีการส่วนตัวที่สอง: ใส่เนื้อหาโดยตรงในการexecuteใช้งานที่ไม่ระบุชื่อหรือถ้ามันยุ่งอาจเป็นข้อบ่งชี้ที่จะแยกการใช้งานเป็นบริการอื่นที่คุณสามารถใส่คำอธิบายประกอบ@Transactionalการดำเนินการหรือถ้าจะกลายเป็นยุ่งก็อาจจะมีข้อบ่งชี้ในการแยกของการดำเนินการเข้าสู่บริการอื่นที่คุณสามารถใส่คำอธิบายประกอบ
ติด

@Stuck วิธีที่ 2 เป็นที่แน่นอนไม่จำเป็น แต่มันตอบคำถามเดิมซึ่งเป็นวิธีการที่จะใช้การทำธุรกรรมในฤดูใบไม้ผลิในวิธีส่วนตัว
loonis

ใช่ฉันได้ตอบคำถามแล้ว แต่ต้องการแบ่งปันบริบทและความคิดเกี่ยวกับวิธีการใช้เพราะฉันคิดว่าจากมุมมองของสถาปัตยกรรมสถานการณ์นี้เป็นข้อบ่งชี้ที่มีศักยภาพสำหรับข้อบกพร่องในการออกแบบ
ติด

5

Spring Docs อธิบายว่า

ในโหมดพร็อกซี (ซึ่งเป็นค่าเริ่มต้น) เฉพาะการเรียกใช้เมธอดภายนอกที่เข้ามาทางพร็อกซีเท่านั้นที่จะถูกดักจับ ซึ่งหมายความว่าการร้องขอด้วยตนเองซึ่งเป็นวิธีการภายในวัตถุเป้าหมายที่เรียกใช้วิธีอื่นของวัตถุเป้าหมายจะไม่นำไปสู่ธุรกรรมจริงที่รันไทม์แม้ว่าวิธีการเรียกใช้จะถูกทำเครื่องหมายด้วย @Transactional

พิจารณาการใช้โหมด AspectJ (ดูแอททริบิวต์โหมดในตารางด้านล่าง) หากคุณคาดว่าจะทำการรวมการร้องขอด้วยตนเองเข้าด้วยกัน ในกรณีนี้จะไม่มีการมอบฉันทะในสถานที่แรก คลาสเป้าหมายจะถูกทอผ้าแทน (นั่นคือโค้ดไบต์จะถูกแก้ไข) เพื่อเปลี่ยน @Transactional ให้เป็นลักษณะการทำงานแบบรันไทม์ในวิธีการใด ๆ

อีกวิธีคือผู้ใช้BeanSelfAware


คุณสามารถเพิ่มการอ้างอิงถึงได้BeanSelfAwareหรือไม่ ดูเหมือนว่าจะเป็นชั้นเรียนของ Spring
asgs

@asgs สมมติว่ามันเกี่ยวกับการฉีดด้วยตัวเอง (ระบุถั่วที่มีอินสแตนซ์ของตัวเองห่อไว้ในพร็อกซี) คุณสามารถดูตัวอย่างในstackoverflow.com/q/3423972/355438
Lu55

3

คำตอบคือไม่ โปรดดูการอ้างอิงสปริง: การใช้ @Transactional :

@Transactionalคำอธิบายประกอบอาจถูกวางไว้ก่อนนิยามอินเตอร์เฟซ, วิธีการเกี่ยวกับอินเตอร์เฟซที่นิยามชั้นเรียนหรือประชาชนวิธีการในชั้นเรียน


1

เช่นเดียวกับ@loonis แนะนำให้ใช้TransactionTemplateหนึ่งอาจใช้ส่วนประกอบผู้ช่วยนี้ (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()
    }
}

การใช้งาน:

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

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

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

ไม่ทราบว่าจะTransactionTemplateใช้ซ้ำธุรกรรมที่มีอยู่หรือไม่ แต่รหัสนี้จะทำอย่างแน่นอน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.