Câu trả lời:
Câu hỏi hay, mặc dù không phải là một câu hỏi nhỏ để trả lời.
Xác định cách các giao dịch liên quan với nhau. Tùy chọn phổ biến:
Required
: Mã sẽ luôn chạy trong một giao dịch. Tạo một giao dịch mới hoặc tái sử dụng một giao dịch nếu có sẵn.Requires_new
: Mã sẽ luôn chạy trong một giao dịch mới. Đình chỉ giao dịch hiện tại nếu có.Xác định hợp đồng dữ liệu giữa các giao dịch.
Read Uncommitted
: Cho phép đọc bẩn.Read Committed
: Không cho phép đọc bẩn.Repeatable Read
: Nếu một hàng được đọc hai lần trong cùng một giao dịch, kết quả sẽ luôn giống nhau.Serializable
: Thực hiện tất cả các giao dịch theo một trình tự.Các mức khác nhau có các đặc tính hiệu suất khác nhau trong một ứng dụng đa luồng. Tôi nghĩ rằng nếu bạn hiểu dirty reads
khái niệm này, bạn sẽ có thể chọn một lựa chọn tốt.
Ví dụ về thời điểm đọc bẩn có thể xảy ra:
thread 1 thread 2
| |
write(x) |
| |
| read(x)
| |
rollback |
v v
value (x) is now dirty (incorrect)
Vì vậy, một mặc định lành mạnh (nếu có thể được yêu cầu) có thể Read Committed
, chỉ cho phép bạn đọc các giá trị đã được cam kết bởi các giao dịch đang chạy khác, kết hợp với mức độ lan truyền Required
. Sau đó, bạn có thể làm việc từ đó nếu ứng dụng của bạn có nhu cầu khác.
Một ví dụ thực tế về nơi một giao dịch mới sẽ luôn được tạo khi nhập provideService
thói quen và hoàn thành khi rời khỏi:
public class FooService {
private Repository repo1;
private Repository repo2;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void provideService() {
repo1.retrieveFoo();
repo2.retrieveFoo();
}
}
Thay vào đó Required
, nếu chúng ta sử dụng , giao dịch sẽ vẫn mở nếu giao dịch đã mở khi vào thói quen. Cũng lưu ý rằng kết quả của một rollback
có thể khác nhau vì một số lần thực hiện có thể tham gia vào cùng một giao dịch.
Chúng tôi có thể dễ dàng xác minh hành vi bằng một thử nghiệm và xem kết quả khác nhau như thế nào với các mức lan truyền:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {
private @Autowired TransactionManager transactionManager;
private @Autowired FooService fooService;
@Test
public void testProvideService() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
fooService.provideService();
transactionManager.rollback(status);
// assert repository values are unchanged ...
}
Với mức độ lan truyền của
Requires new
: Chúng tôi mong chờ fooService.provideService()
được KHÔNG cuộn lại vì nó tạo ra nó là của riêng phụ giao dịch.
Required
: chúng tôi hy vọng mọi thứ sẽ được khôi phục và cửa hàng sao lưu không thay đổi.
sessionFactory.getCurrentTransaction()
đã được thêm vào nên không cần phải chạy HibernateTemplate
nữa để quản lý giao dịch. Tôi đã xóa nó :)
ĐỀ NGHỊ_REQUIRED = 0 ; Nếu DataSourceTransactionObject T1 đã được khởi động cho Phương thức M1. Nếu đối với Phương thức giao dịch M2 khác là bắt buộc, không có đối tượng Giao dịch mới nào được tạo. Đối tượng trò chơi T1 được sử dụng cho M2
ĐỀ NGHỊ_MANDATORY = 2 ; phương pháp phải chạy trong một giao dịch. Nếu không có giao dịch hiện tại đang diễn ra, một ngoại lệ sẽ được đưa ra
PROPAGATION_REQUIRES_NEW = 3 ; Nếu DataSourceTransactionObject T1 đã được khởi động cho Phương thức M1 và nó đang được tiến hành (thực thi phương thức M1). Nếu một phương thức khác M2 bắt đầu thực thi thì T1 bị đình chỉ trong thời gian của phương thức M2 với DataSourceTransactionObject T2 cho M2.M2 chạy
PROPAGATION_NOT_SUPPORTED = 4 ; Nếu DataSourceTransactionObject T1 đã được khởi động cho Phương thức M1. Nếu phương thức khác M2 được chạy đồng thời. Sau đó M2 không nên chạy trong ngữ cảnh giao dịch. T1 bị đình chỉ cho đến khi M2 kết thúc.
PROPAGATION_NEVER = 5 ; Không có phương thức nào chạy trong bối cảnh giao dịch.
Mức cô lập: Đó là khoảng bao nhiêu giao dịch có thể bị ảnh hưởng bởi các hoạt động của các giao dịch đồng thời khác. Nó hỗ trợ tính nhất quán để dữ liệu trên nhiều bảng ở trạng thái nhất quán. Nó liên quan đến việc khóa các hàng và / hoặc bảng trong cơ sở dữ liệu.
Vấn đề với nhiều giao dịch
Kịch bản 1. Nếu giao dịch T1 đọc dữ liệu từ bảng A1 được ghi bởi một giao dịch đồng thời khác là T2. Nếu trên đường đi T2, dữ liệu thu được bởi T1 không hợp lệ .Eg a = 2 là dữ liệu gốc. Nếu T1 đọc a = 1 được viết bởi T2. Nếu T2 rollback thì a = 1 sẽ được rollback thành a = 2 trong DB.But, Bây giờ, T1 có a = 1 nhưng trong bảng DB, nó được đổi thành a = 2.
Kịch bản 2. Nếu giao dịch T1 đọc dữ liệu từ bảng A1. Nếu một giao dịch đồng thời (T2) khác cập nhật dữ liệu trên bảng A1. Sau đó, dữ liệu mà T1 đã đọc khác với bảng A1. Bởi vì T2 đã cập nhật dữ liệu trên bảng A1.Eg nếu T1 đọc a = 1 và T2 được cập nhật a = 2.Sau đó a! = b.
Kịch bản 3. Nếu giao dịch T1 đọc dữ liệu từ bảng A1 với số lượng hàng nhất định. Nếu một giao dịch đồng thời (T2) khác chèn thêm các hàng trên bảng A1. Số lượng hàng được đọc bởi T1 khác với các hàng trên bảng A1
Kịch bản 1 được gọi là đọc bẩn.
Kịch bản 2 được gọi là đọc không lặp lại.
Kịch bản 3 được gọi là Phantom đọc.
Vì vậy, mức cô lập là mức mở rộng mà Kịch bản 1, Kịch bản 2, Kịch bản 3 có thể được ngăn chặn. Bạn có thể đạt được mức cô lập hoàn toàn bằng cách thực hiện khóa. Điều đó ngăn việc đọc và ghi đồng thời vào cùng một dữ liệu xảy ra. Nhưng nó ảnh hưởng đến hiệu suất. Mức độ cô lập phụ thuộc vào ứng dụng cần bao nhiêu cách ly.
ISOLATION_READ_UNCOMMITTED : Cho phép đọc các thay đổi chưa được cam kết. Nó bị kịch bản 1, Kịch bản 2, Kịch bản 3
ISOLATION_READ_COMMITTED : Cho phép đọc từ các giao dịch đồng thời đã được cam kết. Nó có thể bị kịch bản 2 và Kịch bản 3. Vì các giao dịch khác có thể đang cập nhật dữ liệu.
ISOLATION_REPEATABLE_READ : Nhiều lần đọc của cùng một trường sẽ mang lại kết quả tương tự cho đến khi chính nó bị thay đổi. Nó có thể bị kịch bản 3. Vì các giao dịch khác có thể chèn dữ liệu
ISOLATION_SERIALIZABLE : Kịch bản 1, Kịch bản 2, Kịch bản 3 không bao giờ xảy ra. Nó hoàn toàn bị cô lập. Nó liên quan đến khóa hoàn toàn. Nó ảnh hưởng đến việc khóa vì khóa.
Bạn có thể kiểm tra bằng cách sử dụng
public class TransactionBehaviour {
// set is either using xml Or annotation
DataSourceTransactionManager manager=new DataSourceTransactionManager();
SimpleTransactionStatus status=new SimpleTransactionStatus();
;
public void beginTransaction()
{
DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
// overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
// set is either using xml Or annotation
manager.setPropagationBehavior(XX);
manager.setIsolationLevelName(XX);
status = manager.getTransaction(Def);
}
public void commitTransaction()
{
if(status.isCompleted()){
manager.commit(status);
}
}
public void rollbackTransaction()
{
if(!status.isCompleted()){
manager.rollback(status);
}
}
Main method{
beginTransaction()
M1();
If error(){
rollbackTransaction()
}
commitTransaction();
}
}
Bạn có thể gỡ lỗi và xem kết quả với các giá trị khác nhau để phân lập và lan truyền.
Đủ giải thích về từng tham số được đưa ra bởi các câu trả lời khác; Tuy nhiên, bạn đã yêu cầu một ví dụ trong thế giới thực, đây là một ví dụ làm rõ mục đích của các tùy chọn lan truyền khác nhau :
Giả sử bạn chịu trách nhiệm triển khai dịch vụ đăng ký, trong đó email xác nhận được gửi đến người dùng. Bạn nghĩ ra hai đối tượng dịch vụ, một để đăng ký người dùng và một để gửi e-mail, cái sau được gọi bên trong cái đầu tiên. Ví dụ như một cái gì đó như thế này:/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService{
...
void SignUp(User user){
...
emailService.sendMail(User);
}
}
/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService{
...
void sendMail(User user){
try{
... // Trying to send the e-mail
}catch( Exception)
}
}
Bạn có thể nhận thấy rằng dịch vụ thứ hai thuộc loại lan truyền REQUIRES_NEW và hơn nữa rất có thể nó sẽ ném một ngoại lệ (máy chủ SMTP xuống, e-mail không hợp lệ hoặc các lý do khác). Bạn có thể không muốn toàn bộ quá trình quay ngược lại, như xóa thông tin người dùng khỏi cơ sở dữ liệu hoặc những thứ khác; do đó bạn gọi dịch vụ thứ hai trong một giao dịch riêng.
Quay lại ví dụ của chúng tôi, lần này bạn lo ngại về bảo mật cơ sở dữ liệu, vì vậy bạn xác định các lớp DAO của mình theo cách này:/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
// some CRUD methods
}
Có nghĩa là bất cứ khi nào một đối tượng DAO và do đó có thể truy cập db, chúng tôi cần phải đảm bảo rằng cuộc gọi được thực hiện từ một trong các dịch vụ của chúng tôi, ngụ ý rằng nên tồn tại một giao dịch trực tiếp; mặt khác, một trường hợp ngoại lệ xảy ra. Do đó, việc truyền bá thuộc loại MANDATORY .
Mức cô lập xác định cách các thay đổi được thực hiện đối với một số kho lưu trữ dữ liệu bởi một giao dịch ảnh hưởng đến các giao dịch đồng thời khác và cũng như cách thức và thời điểm dữ liệu thay đổi đó có sẵn cho các giao dịch khác. Khi chúng tôi xác định một giao dịch bằng khung Spring, chúng tôi cũng có thể định cấu hình mức độ cô lập mà giao dịch đó sẽ được thực hiện.
@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {
}
Mức cô lập READ_UNCOMMITTED nói rằng một giao dịch có thể đọc dữ liệu vẫn chưa được cam kết bởi các giao dịch khác.
Mức cô lập READ_COMMITTED nói rằng một giao dịch không thể đọc dữ liệu chưa được cam kết bởi các giao dịch khác.
Mức cô lập REPEATABLE_READ nói rằng nếu một giao dịch đọc một bản ghi từ cơ sở dữ liệu nhiều lần thì kết quả của tất cả các hoạt động đọc đó phải luôn giống nhau.
Mức cô lập SERIALIZABLE là mức giới hạn nhất trong tất cả các mức cô lập. Giao dịch được thực hiện với khóa ở tất cả các cấp (đọc, phạm vi và khóa ghi) để chúng xuất hiện như thể chúng được thực hiện theo cách nối tiếp.
Tuyên truyền là khả năng quyết định cách các phương thức kinh doanh nên được gói gọn trong cả giao dịch logic hoặc giao dịch vật lý.
Hành vi BẮT BUỘC có nghĩa là cùng một giao dịch sẽ được sử dụng nếu có một giao dịch đã được mở trong bối cảnh thực thi phương thức bean hiện tại.
Hành vi REQUIRES_NEW có nghĩa là một giao dịch vật lý mới sẽ luôn được tạo bởi container.
Hành vi NESTED làm cho các giao dịch Spring lồng nhau sử dụng cùng một giao dịch vật lý nhưng đặt các điểm lưu trữ giữa các yêu cầu lồng nhau để các giao dịch bên trong cũng có thể quay ngược lại độc lập với các giao dịch bên ngoài.
Hành vi MANDATORY nói rằng một giao dịch đã mở hiện tại phải tồn tại. Nếu không một ngoại lệ sẽ được ném bởi container.
Hành vi KHÔNG BAO GIỜ nói rằng một giao dịch đã mở hiện tại không được tồn tại. Nếu một giao dịch tồn tại, một ngoại lệ sẽ được ném bởi container.
Hành vi NOT_SUPPORTED sẽ thực hiện bên ngoài phạm vi của bất kỳ giao dịch nào. Nếu một giao dịch mở đã tồn tại, nó sẽ bị tạm dừng.
Hành vi SUPPORTS sẽ thực thi trong phạm vi giao dịch nếu đã có giao dịch mở. Nếu không có giao dịch nào được mở, phương thức sẽ thực hiện bằng mọi cách nhưng theo cách không giao dịch.
Giao dịch đại diện cho một đơn vị công việc với cơ sở dữ liệu.
Trong TransactionDefinition
giao diện mùa xuân xác định các thuộc tính giao dịch tương thích với Spring. @Transactional
chú thích mô tả các thuộc tính giao dịch trên một phương thức hoặc lớp.
@Autowired
private TestDAO testDAO;
@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {
// Interact with testDAO
}
Tuyên truyền (Sinh sản): được sử dụng cho quan hệ giao dịch liên. (tương tự như giao tiếp liên chủ đề java)
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value | Propagation | Description |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| -1 | TIMEOUT_DEFAULT | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
| 0 | PROPAGATION_REQUIRED | Support a current transaction; create a new one if none exists. |
| 1 | PROPAGATION_SUPPORTS | Support a current transaction; execute non-transactionally if none exists. |
| 2 | PROPAGATION_MANDATORY | Support a current transaction; throw an exception if no current transaction exists. |
| 3 | PROPAGATION_REQUIRES_NEW | Create a new transaction, suspending the current transaction if one exists. |
| 4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally. |
| 5 | PROPAGATION_NEVER | Do not support a current transaction; throw an exception if a current transaction exists. |
| 6 | PROPAGATION_NESTED | Execute within a nested transaction if a current transaction exists. |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
Cách ly: Cô lập là một trong các thuộc tính ACID (Nguyên tử, Tính nhất quán, Cách ly, Độ bền) của các giao dịch cơ sở dữ liệu. Cách ly xác định cách hiển thị toàn vẹn giao dịch cho người dùng và hệ thống khác. Nó sử dụng để khóa tài nguyên tức là kiểm soát đồng thời, đảm bảo rằng chỉ một giao dịch có thể truy cập tài nguyên tại một điểm nhất định.
Khóa nhận thức: mức cô lập xác định thời lượng khóa được giữ.
+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode | Read | Insert | Update | Lock Scope |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED | uncommitted data | Allowed | Allowed | No Lock |
| READ_COMMITTED (Default) | committed data | Allowed | Allowed | Lock on Committed data |
| REPEATABLE_READ | committed data | Allowed | Not Allowed | Lock on block of table |
| SERIALIZABLE | committed data | Not Allowed | Not Allowed | Lock on full table |
+---------------------------+-------------------+-------------+-------------+------------------------+
Đọc nhận thức: 3 loại vấn đề chính sau đây xảy ra:
UPDATES
từ một tx khác.INSERTS
và / hoặcDELETES
từ một tx khácCác mức cô lập với các kiểu đọc khác nhau:
+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode | Dirty reads | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED | allows | allows | allows |
| READ_COMMITTED (Default) | prevents | allows | allows |
| REPEATABLE_READ | prevents | prevents | allows |
| SERIALIZABLE | prevents | prevents | prevents |
+---------------------------+----------------+----------------------+----------------+
Bạn gần như không bao giờ muốn sử dụng Read Uncommited
vì nó không thực sự ACID
tuân thủ. Read Commmited
là một nơi bắt đầu mặc định tốt. Repeatable Read
có lẽ chỉ cần trong các tình huống báo cáo, tổng hợp hoặc tổng hợp. Lưu ý rằng nhiều DB, postgres bao gồm không thực sự hỗ trợ Đọc lặp lại, Serializable
thay vào đó bạn phải sử dụng . Serializable
là hữu ích cho những điều mà bạn biết phải xảy ra hoàn toàn độc lập với bất cứ điều gì khác; nghĩ về nó giống như synchronized
trong Java. Nối tiếp đi đôi vớiREQUIRES_NEW
tuyên truyền.
Tôi sử dụng REQUIRES
cho tất cả các chức năng chạy các truy vấn CẬP NHẬT hoặc XÓA cũng như các hàm cấp độ "dịch vụ". Đối với các hàm cấp DAO chỉ chạy CHỌN, tôi sử dụng hàm SUPPORTS
này sẽ tham gia vào TX nếu một hàm đã được khởi động (tức là được gọi từ hàm dịch vụ).
Cách ly giao dịch và Tuyên truyền giao dịch mặc dù có liên quan nhưng rõ ràng là hai khái niệm rất khác nhau. Trong cả hai trường hợp, mặc định được tùy chỉnh tại thành phần ranh giới của khách hàng bằng cách sử dụng quản lý giao dịch khai báo hoặc quản lý giao dịch theo chương trình . Chi tiết của từng cấp độ cách ly và thuộc tính lan truyền có thể được tìm thấy trong các liên kết tham chiếu bên dưới.
Đối với hai hoặc nhiều giao dịch / kết nối đang chạy với cơ sở dữ liệu, các thay đổi được thực hiện bởi các truy vấn trong một tác động giao dịch / hiển thị đối với các truy vấn trong một giao dịch khác nhau. Nó cũng liên quan đến loại khóa bản ghi cơ sở dữ liệu nào sẽ được sử dụng để cô lập các thay đổi trong giao dịch này với các giao dịch khác và ngược lại. Điều này thường được thực hiện bởi cơ sở dữ liệu / tài nguyên đang tham gia giao dịch.
.
Trong một ứng dụng doanh nghiệp cho bất kỳ yêu cầu / xử lý nhất định, có nhiều thành phần có liên quan để hoàn thành công việc. Một số thành phần này đánh dấu ranh giới (bắt đầu / kết thúc) của giao dịch sẽ được sử dụng trong thành phần tương ứng và các thành phần phụ của nó. Đối với ranh giới giao dịch của các thành phần này, Tuyên truyền giao dịch chỉ định nếu thành phần tương ứng sẽ hoặc không tham gia giao dịch và điều gì xảy ra nếu thành phần gọi đã có hoặc chưa có giao dịch được tạo / bắt đầu. Điều này giống như các thuộc tính giao dịch Java EE. Điều này thường được thực hiện bởi người quản lý giao dịch / kết nối khách hàng.
Tài liệu tham khảo:
Tôi đã chạy outerMethod
, method_1
và method_2
với chế độ lan truyền khác nhau.
Dưới đây là đầu ra cho chế độ lan truyền khác nhau.
Phương pháp bên ngoài
@Transactional
@Override
public void outerMethod() {
customerProfileDAO.method_1();
iWorkflowDetailDao.method_2();
}
Phương thức_1
@Transactional(propagation=Propagation.MANDATORY)
public void method_1() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "XXX");
session.save(entity);
System.out.println("Method - 1 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
Phương thức_2
@Transactional()
@Override
public void method_2() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "CCC");
session.save(entity);
int i = 1/0;
System.out.println("Method - 2 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
Chúng tôi có thể thêm cho điều này:
@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {
public Customer getDetail(String customername) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateCustomer(Customer customer) {
// do something
}
}
Bạn có thể sử dụng như thế này:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}
Bạn cũng có thể sử dụng điều này:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}