有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?
基本上,我应该在什么时候以及为什么选择更改它们的默认值。
有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?
基本上,我应该在什么时候以及为什么选择更改它们的默认值。
当前回答
我们可以为此添加:
@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。
Propagation_required = 0;如果方法M1的DataSourceTransactionObject T1已经启动。如果需要另一个Method M2事务对象,则不创建新的事务对象。同样的对象T1用于M2。
Propagation_mandatory = 2;方法必须在事务中运行。如果没有 现有事务正在进行中,将引发异常。
Propagation_requires_new = 3;如果DataSourceTransactionObject T1已经为方法M1启动,并且正在进行中(执行方法M1)。如果另一个方法M2开始执行,那么T1将在方法M2的持续时间内挂起,并为M2创建新的DataSourceTransactionObject T2。M2在它自己的事务上下文中运行。
Propagation_not_supported = 4;如果方法M1的DataSourceTransactionObject T1已经启动。如果另一个方法M2同时运行。那么M2不应该在事务上下文中运行。T1暂停,直到M2完成。
Propagation_never = 5;没有一个方法在事务上下文中运行。
隔离级别: 它是关于一个事务在多大程度上可能受到其他并发事务活动的影响。它支持一致性,使跨多个表的数据处于一致的状态。它涉及到锁定数据库中的行和/或表。
多重事务的问题
场景1。如果T1事务从表A1读取由另一个并发事务T2写入的数据。如果T2正在回滚,则T1获取的数据无效1。例如a=2是原始数据。如果T1读取的a=1是T2写的。如果T2回滚,则a=1将在DB中回滚到a=2。但是,现在,T1有一个=1,但在DB表中它被更改为a=2。
Scenario2。如果T1事务从表A1读取数据。如果另一个并发事务(T2)更新表A1上的数据。那么T1读取的数据是 不同于表格A1。因为T2更新了表A1上的数据。例如,如果T1读取a=1, T2更新a=2。然后一个! = b。
场景3。如果T1事务从表A1读取一定行数的数据。如果另一个并发事务(T2)在表A1上插入更多行。的 T1读取的行数与表A1上的行数不同。
场景1称为脏读。
场景2称为不可重复读取。
场景3被称为幻影读取。
因此,隔离级别是可以防止场景1、场景2、场景3的扩展。 您可以通过实现锁定来获得完整的隔离级别。这阻止了对同一数据的并发读写。但它会影响性能。隔离级别取决于应用程序与应用程序之间所需的隔离程度。
ISOLATION_READ_UNCOMMITTED:允许读取尚未提交的更改。它遭受场景一,场景二,场景三。
ISOLATION_READ_COMMITTED:允许从已提交的并发事务中读取。它可能会受到场景2和场景3的影响。因为其他事务可能正在更新数据。
ISOLATION_REPEATABLE_READ:对同一个字段的多次读取将产生相同的结果,直到它被自己更改为止。它可能会受到场景3的影响。因为其他事务可能正在插入数据。
ISOLATION_SERIALIZABLE:场景1、场景2、场景3永远不会发生。这是完全的隔离。它涉及到完全锁定。由于锁定,它会影响性能。
您可以使用以下方法进行测试:
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();
}
}
您可以调试并使用隔离和传播的不同值查看结果。
我们可以为此添加:
@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
}
}