我有一个方法,' databaseChanges',它以迭代的方式调用2个操作:A,B。 ' A'首先,' B'持续。 ' A' &安培; ' B'在我的持久存储Oracle Database 11g中可以 C reate, U pdate D elete功能。
让我们说,
' A'更新表中的记录用户,属性zip,其中id = 1。
' B'在表爱好中插入记录。
场景:已调用databaseChanges方法,' A'操作和更新记录。 ' B'操作并尝试插入记录,发生一些事情,抛出异常,异常是冒充问databaseChanges方法。
预期:' A'和' B'什么都没改变。更新“A'做了,将会回滚。 ' B'没有改变,嗯...有一个例外。
实际:' A'更新似乎没有被回滚。 ' B'没有改变,嗯...有一个例外。
部分代码
如果我有连接,我会做类似的事情:
private void databaseChanges(Connection conn) {
try {
conn.setAutoCommit(false);
A(); //update.
B(); //insert
conn.commit();
} catch (Exception e) {
try {
conn.rollback();
} catch (Exception ei) {
//logs...
}
} finally {
conn.setAutoCommit(true);
}
}
问题:我没有连接(请参阅随问题发布的标签)
我试图:
@Service
public class SomeService implements ISomeService {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate npjt;
@Transactional
private void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
}
我的AppConfig课程:
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(dataSource);
}
}
' A'进行更新。来自' B'抛出异常。由A' A'做出的更新没有回滚。
根据我的阅读,我明白我没有正确使用@Transactional。 我阅读并尝试了几篇博客文章和stackverflow Q& A没有成功解决我的问题。
有什么建议吗?
修改
有一种方法可以调用databaseChanges()方法
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
哪种方法应使用@Transactional注释,
的变化()? databaseChanges()?
答案 0 :(得分:19)
@Transactional
注释通过将对象包装在代理中来工作,而代理又在事务中包装带有@Transactional
注释的方法。因为注释不适用于私有方法(如在您的示例中),因为私有方法不能被继承 =>他们不能被包裹(如果您使用aspectj的声明性交易,则不是这样,那么下面的代理相关警告不适用)。
以下是@Transactional
春天魔法如何运作的基本解释。
您写道:
class A {
@Transactional
public void method() {
}
}
但是这是你注入bean时实际得到的:
class ProxiedA extends A {
private final A a;
public ProxiedA(A a) {
this.a = a;
}
@Override
public void method() {
try {
// open transaction ...
a.method();
// commit transaction
} catch (RuntimeException e) {
// rollback transaction
} catch (Exception e) {
// commit transaction
}
}
}
这有局限性。他们不使用@PostConstruct
方法,因为在代理对象之前调用它们。即使您正确配置了所有内容,默认情况下,只会在未选中例外情况下回滚事务。如果您需要在某些已检查的异常上进行回滚,请使用@Transactional(rollbackFor={CustomCheckedException.class})
。
我知道另一个经常遇到的警告:
@Transactional
方法仅在您从外部"中调用它时才会起作用,在以下示例b()
中不会包含在事务中:
class X {
public void a() {
b();
}
@Transactional
public void b() {
}
}
这也是因为@Transactional
通过代理您的对象来工作。在上面的示例中,a()
将调用X.b()
而不是增强的" spring代理"方法b()
所以没有交易。作为一种解决方法,您必须从另一个bean调用b()
。
当您遇到任何这些警告并且无法使用建议的解决方法(make方法非私有或从另一个bean调用b()
)时,您可以使用TransactionTemplate
而不是声明性事务:
public class A {
@Autowired
TransactionTemplate transactionTemplate;
public void method() {
transactionTemplate.execute(status -> {
A();
B();
return null;
});
}
...
}
<强>更新强>
使用上面的信息回答OP更新的问题。
应使用@Transactional注释哪个方法: 变化()? databaseChanges()?
@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
确保changes()
被呼叫&#34;来自外部&#34;一个bean,而不是来自类本身,并且在实例化上下文之后(例如,这不是afterPropertiesSet()
或@PostConstruct
注释方法)。默认情况下,只知道未经检查的异常的spring rollbacks事务(尝试在rollbackFor checked exception列表中更具体)。
答案 1 :(得分:4)
任何
RuntimeException
触发回滚,任何已检查的异常都不会。
这是所有Spring事务API的常见行为。 默认情况下,如果从事务代码中抛出RuntimeException
,则事务将被回滚。如果抛出了已检查的异常(即不是RuntimeException
),则不会回滚该事务。
这取决于您在databaseChanges
函数中获得的异常。
因此,为了捕获所有异常,您需要做的就是添加rollbackFor = Exception.class
应该在服务类上进行更改,代码将是这样的:
@Service
public class SomeService implements ISomeService {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate npjt;
@Transactional(rollbackFor = Exception.class)
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
此外,你可以做一些不错的事情,所以不是所有的时间你都要写rollbackFor = Exception.class
。您可以通过编写自己的自定义注释来实现这一目标:
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor = Exception.class)
@Documented
public @interface CustomTransactional {
}
最终代码将是这样的:
@Service
public class SomeService implements ISomeService {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate npjt;
@CustomTransactional
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
答案 2 :(得分:1)
您提供的第一个代码是UserTransactions,即应用程序必须进行事务管理。通常您希望容器处理它并使用@Transactional注释。我认为你的问题可能是,你在私有方法上有注释。我将注释移到班级
@Transactional
public class MyFacade {
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
然后它应该正确回滚。你可以在这里找到更多细节 Does Spring @Transactional attribute work on a private method?
答案 3 :(得分:0)
试试这个:
@TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {
@TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
答案 4 :(得分:0)
您似乎缺少的是TransactionManager
。 TransactionManager
的目的是能够管理数据库事务。有两种类型的交易,程序化和声明式。您所描述的是需要通过注释进行声明性事务。
因此,您需要为项目制定以下内容:
Spring Transactions依赖关系(以Gradle为例)
compile("org.springframework:spring-tx")
在Spring Boot配置中定义事务管理器
像这样的东西
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
return new DataSourceTransactionManager(dataSource);
}
您还需要添加@EnableTransactionManagement
注释(不确定这是否是在较新版本的spring boot中免费提供。
@EnableTransactionManagement
public class AppConfig {
...
}
添加@Transactional
在这里,您要为要参与交易的方法添加@Transactional
注释
@Transactional
public void book(String... persons) {
for (String person : persons) {
log.info("Booking " + person + " in a seat...");
jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
}
};
请注意,此方法应该是公开的而不是私有的。您可以考虑将@Transactional
放在调用databaseChanges()
的公共方法上。
还有关于@Transactional
应该去哪里以及它的行为方式的高级主题,所以最好先让某些东西先行,然后稍后探索这个区域:)
完成所有这些(依赖+事务管理器配置+注释)之后,事务应该相应地工作。
<强>参考强>
Spring Reference Documentation on Transactions
Spring Guide for Transactions using Spring Boot - 这里有您可以使用的示例代码