有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?
基本上,我应该在什么时候以及为什么选择更改它们的默认值。
有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?
基本上,我应该在什么时候以及为什么选择更改它们的默认值。
当前回答
好问题,虽然不是一个微不足道的问题。
传播
定义事务如何相互关联。常见的选项:
REQUIRED:代码总是在事务中运行。创建一个新的事务或重用一个可用的事务。 REQUIRES_NEW:代码总是在一个新的事务中运行。如果存在当前事务,则暂停当前事务。
@Transactional的默认值是REQUIRED,这通常是您想要的。
隔离
定义事务之间的数据契约。
ISOLATION_READ_UNCOMMITTED:允许脏读。 ISOLATION_READ_COMMITTED:不允许脏读。 ISOLATION_REPEATABLE_READ:如果在同一个事务中读取一行两次,结果总是相同的。 ISOLATION_SERIALIZABLE:按顺序执行所有事务。
在多线程应用程序中,不同的级别具有不同的性能特征。我认为如果你理解了脏读的概念,你就能选择一个好的选择。
缺省值在不同数据库之间可能有所不同。例如,对于MariaDB,它是REPEATABLE READ。
可以发生脏读的示例:
thread 1 thread 2
| |
write(x) |
| |
| read(x)
| |
rollback |
v v
value (x) is now dirty (incorrect)
因此,一个合理的默认值(如果可以声明的话)可以是ISOLATION_READ_COMMITTED,它只允许您读取已经由其他正在运行的事务提交的值,并结合传播级别REQUIRED。然后,如果您的应用程序有其他需求,您就可以从那里开始工作。
一个实际的例子,一个新的事务总是在进入provideService例程时创建,并在离开时完成:
public class FooService {
private Repository repo1;
private Repository repo2;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void provideService() {
repo1.retrieveFoo();
repo2.retrieveFoo();
}
}
如果我们改为使用REQUIRED,那么如果事务在进入例程时已经打开,那么事务将保持打开状态。 还要注意,回滚的结果可能不同,因为多个执行可能参与同一个事务。
我们可以很容易地用一个测试来验证行为,看看结果在传播级别上有什么不同:
@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 ...
}
的传播水平
REQUIRES_NEW:我们期望fooService.provideService()没有回滚,因为它创建了自己的子事务。 REQUIRED:我们期望所有东西都回滚了,备份存储没有改变。
其他回答
我已经用不同的传播模式运行了outerMethod, method_1和method_2。
下面是不同传播模式的输出。
外部方法
@Transactional
@Override
public void outerMethod() {
customerProfileDAO.method_1();
iWorkflowDetailDao.method_2();
}
Method_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()) {
}
}
}
Method_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()) {
}
}
}
OuterMethod -没有事务 传播。mandatory) - Method_2 -仅事务注释 method_1将抛出不存在事务的异常
OuterMethod -没有事务 Method_1 -仅事务注释 方法2 -传播。mandatory) 输出:method_2将抛出不存在事务的异常 输出:method_1将保存数据库中的记录。
OuterMethod -带有事务 Method_1 -仅事务注释 方法2 -传播。mandatory) 输出:method_2将记录保存在数据库中。 输出:method_1将保存数据库中的记录。 方法1和方法2都使用了Main Outer现有事务
OuterMethod -带有事务 传播。mandatory) Method_2 -仅事务注释并抛出异常 输出:没有记录保存在数据库中意味着回滚完成。
OuterMethod -带有事务 方法1 -传播。requires_new) Method_2 - Propagation.REQUIRES_NEW)并抛出1/0异常 输出:method_2将抛出异常,因此method_2记录不被保存。 输出:method_1将保存数据库中的记录。 输出:method_1没有回滚
我们可以为此添加:
@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
}
}
你可以这样用:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}
你也可以用这个东西:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
好问题,虽然不是一个微不足道的问题。
传播
定义事务如何相互关联。常见的选项:
REQUIRED:代码总是在事务中运行。创建一个新的事务或重用一个可用的事务。 REQUIRES_NEW:代码总是在一个新的事务中运行。如果存在当前事务,则暂停当前事务。
@Transactional的默认值是REQUIRED,这通常是您想要的。
隔离
定义事务之间的数据契约。
ISOLATION_READ_UNCOMMITTED:允许脏读。 ISOLATION_READ_COMMITTED:不允许脏读。 ISOLATION_REPEATABLE_READ:如果在同一个事务中读取一行两次,结果总是相同的。 ISOLATION_SERIALIZABLE:按顺序执行所有事务。
在多线程应用程序中,不同的级别具有不同的性能特征。我认为如果你理解了脏读的概念,你就能选择一个好的选择。
缺省值在不同数据库之间可能有所不同。例如,对于MariaDB,它是REPEATABLE READ。
可以发生脏读的示例:
thread 1 thread 2
| |
write(x) |
| |
| read(x)
| |
rollback |
v v
value (x) is now dirty (incorrect)
因此,一个合理的默认值(如果可以声明的话)可以是ISOLATION_READ_COMMITTED,它只允许您读取已经由其他正在运行的事务提交的值,并结合传播级别REQUIRED。然后,如果您的应用程序有其他需求,您就可以从那里开始工作。
一个实际的例子,一个新的事务总是在进入provideService例程时创建,并在离开时完成:
public class FooService {
private Repository repo1;
private Repository repo2;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void provideService() {
repo1.retrieveFoo();
repo2.retrieveFoo();
}
}
如果我们改为使用REQUIRED,那么如果事务在进入例程时已经打开,那么事务将保持打开状态。 还要注意,回滚的结果可能不同,因为多个执行可能参与同一个事务。
我们可以很容易地用一个测试来验证行为,看看结果在传播级别上有什么不同:
@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 ...
}
的传播水平
REQUIRES_NEW:我们期望fooService.provideService()没有回滚,因为它创建了自己的子事务。 REQUIRED:我们期望所有东西都回滚了,备份存储没有改变。
其他答案对每个参数都给出了足够的解释;但是,您要求的是一个真实世界的示例,下面是一个阐明不同传播选项的目的的示例:
Suppose you're in charge of implementing a注册服务
in which a confirmation e-mail is sent to the user. You come up with two service objects, one for招收
the user and one for发送
e-mails, which the latter is called inside the first one. For example something like this:/* 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)
}
}
您可能已经注意到第二个服务的传播类型为REQUIRES_NEW,而且它很可能抛出异常(SMTP服务器宕机、无效电子邮件或其他原因)。你可能不希望整个过程回滚,比如从数据库中删除用户信息或其他东西;因此,在单独的事务中调用第二个服务。
Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
// some CRUD methods
}
这意味着无论何时创建一个DAO对象,以及因此对DB的潜在访问,我们都需要确保调用是从我们的一个服务内部发出的,这意味着应该存在一个活动事务;否则会出现异常。因此,传播类型为MANDATORY。