事务
最简配置
@Configuration
@EnableTransactionManagement
public class SpringConfig {
@Bean
public PlatformTransactionManager transactionManager(@Qualifier("dataSource")DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
@PostMapping("/save")
@Transactional(rollbackFor = Exception.class)
public boolean saveLog(@Valid @RequestBody LogReq req){
Log log = new Log();
BeanUtils.copyProperties(req,log);
Date date = DateUtils.toDateTime(req.getCreateTime());
log.setCreateTime(date);
return logService.save(log)&&logService.save(log);
}
由于id重复,第二个必定失败,事务会造成回滚,所以一条都不会插入
如果你非要捕获异常
@Override
public Result<Boolean> updateDeviceStrategy(DeviceStrategyRequest request) {
try{
deviceCurrentStatusMapper.updateDeviceStrategy(request);
deviceCurrentPerformanceMapper.updateDeviceStrategy(request);
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
log.error("updateDeviceStrategy执行失败",e);
return Result.fail();
}
return Result.ok();
}
使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();可以强行设置为回滚状态
提交事务再发送通知这样写会更节省连接
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
class DoTransactionCompletion implements TransactionSynchronization{
private Runnable runnable;
public DoTransactionCompletion(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void afterCompletion(int status) {
if(status==TransactionSynchronization.STATUS_COMMITTED){
this.runnable.run();
}
}
}
public class TransactionUtil{
public static void doAfterTransaction(DoTransactionCompletion doTransactionCompletion){
if(TransactionSynchronizationManager.isActualTransactionActive()){
TransactionSynchronizationManager.registerSynchronization(doTransactionCompletion);
}
}
}
@PostMapping("/save")
@Transactional(rollbackFor = Exception.class)
public boolean saveLog(@Valid @RequestBody LogReq req){
//扫描到注解,执行doBegin()
Log log = new Log();
BeanUtils.copyProperties(req,log);
Date date = DateUtils.toDateTime(req.getCreateTime());
log.setCreateTime(date);
int result = logService.save(log)
//提前将完成逻辑进行注入
TransactionUtil.doAfterTransaction(new DoTransactionCompletion(()->{
//MQ
//RPC
}));
return result;
//end tx,执行doCommit(),然后再执行AfterCompletion
}
以下是提交全流程
可以看出,执行顺序是,beforeCommit,beforeCompletion,doCommit,AfterCommit,AfterCompletion
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException | Error ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
注意点
1 datasource要正确连接数据库
2 Spring事务实质是AOP开启事务和提交事务,所以内部调用无效,因为内部调用的不是代理对象,而是对象本身
3 以上配置的前提条件是可以跑通ssm,即可以完成最简单的crud。
AOP
示例
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
对于Spring AOP而言,连接点就是所有组件的方法
@Pointcut是连接点中需要关注的点,也就是被execution标注了的方法
execution(* com.yuyi.pis.log.controller..check(…))
的语法为修饰符 包名.类名.方法名(方法参数)
@Before(“logAspect()”)这类注解就是用于在方法前后完成一些统一的自定义行为
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 定义连接点中哪些是需要关注的
* controller包下所有类的所有方法
*/
@Pointcut(value = "(execution(* com.yuyi.pis.*.controller.*.*(..))) " +
"&& !(execution(* com.yuyi.pis.*.controller.*.check*(..))) " +
"&& !execution(* com.yuyi.pis.*.controller.*.select*(..))" +
"&& !execution(* com.yuyi.pis.*.controller.*.find*(..))" +
"&& !execution(* com.yuyi.pis.*.controller.*.get*(..))"
)
public void logAspect() {
}
/**
* 前置通知 在目标方法 运行前执行
* @param joinpoint
*/
@Before("logAspect()")
public void logStart(JoinPoint joinpoint){
RequestAttributes requestAttributes = getRequestAttributesSafely();
//如果不是HTTP请求,就不用继续执行
if(requestAttributes instanceof NonWebAttributes) {return;}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
String requestURL = request.getRequestURL().toString();
log.info("\nThread name is {}.\nInvoke api url is {}.\nInput params is {}",Thread.currentThread().getName(),requestURL, getPointParamMap(joinpoint));
}
/**
* 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后执行
* @param joinpoint
* @param result
*/
//JoinPoint 一定要出现在参数表的第一位
@AfterReturning(value="logAspect()",returning="result")
public void logReturn(JoinPoint joinpoint,Object result){
RequestAttributes requestAttributes = getRequestAttributesSafely();
//如果不是HTTP请求,就不用继续执行
if(requestAttributes instanceof NonWebAttributes) {return;}
HttpServletRequest request = ((ServletRequestAttributes) (requestAttributes)).getRequest();
String requestURL = request.getRequestURL().toString();
log.info("\nThread name is {}.\nInvoke api url is {}.\nOutput params is {}",Thread.currentThread().getName(),requestURL,result);
}
public Map getPointParamMap(JoinPoint joinpoint){
//获取入参信息
Object[] args= joinpoint.getArgs();
String[] argNames = ((MethodSignature)joinpoint.getSignature()).getParameterNames(); // 参数名
Map map = new HashMap();
int len = argNames.length;
for(int i = 0; i < len; i++){
map.put(argNames[i],args[i]);
}
return map;
}
/**
* * 排除非HTTP请求干扰
* @return 请求属性
*/
public RequestAttributes getRequestAttributesSafely(){
RequestAttributes requestAttributes = null;
try{
requestAttributes = RequestContextHolder.currentRequestAttributes();
}catch (IllegalStateException e){
requestAttributes = new NonWebAttributes();
}
return requestAttributes;
}
/**
* * 一个空请求体,为了更好表示这类情况
*/
class NonWebAttributes implements RequestAttributes{
@Override
public Object getAttribute(String name, int scope) {
return null;
}
@Override
public void setAttribute(String name, Object value, int scope) {
}
@Override
public void removeAttribute(String name, int scope) {
}
@Override
public String[] getAttributeNames(int scope) {
return new String[0];
}
@Override
public void registerDestructionCallback(String name, Runnable callback, int scope) {
}
@Override
public Object resolveReference(String key) {
return null;
}
@Override
public String getSessionId() {
return null;
}
@Override
public Object getSessionMutex() {
return null;
}
}
}
关于流
有些小伙伴担心流作为参数,可能会导致无法正常输出,实际上MultipartFile类型在toString()时就会变成一个对象地址,类似如下,所以不用担心
声明
来源是本人语雀笔记(语雀分享竟然收费了!失踪人口回归)