001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.db.transactional;
017
018
019import com.jfinal.aop.Interceptor;
020import com.jfinal.aop.Invocation;
021import com.jfinal.kit.LogKit;
022import com.jfinal.kit.Ret;
023import com.jfinal.plugin.activerecord.*;
024import io.jboot.aop.InterceptorBuilder;
025import io.jboot.aop.Interceptors;
026import io.jboot.aop.annotation.AutoLoad;
027import io.jboot.aop.annotation.Transactional;
028import io.jboot.utils.AnnotationUtil;
029import io.jboot.utils.StrUtil;
030
031import java.lang.reflect.Method;
032import java.util.concurrent.Callable;
033import java.util.concurrent.Future;
034
035/**
036 * 缓存操作的拦截器
037 *
038 * @author michael yang
039 */
040@AutoLoad
041public class TransactionalInterceptor implements Interceptor, InterceptorBuilder {
042
043
044    @Override
045    public void intercept(Invocation inv) {
046
047        Transactional transactional = inv.getMethod().getAnnotation(Transactional.class);
048        String configName = AnnotationUtil.get(transactional.config());
049
050        DbPro dbPro = StrUtil.isBlank(configName) ? Db.use() : Db.use(configName);
051        Config config = StrUtil.isBlank(configName) ? DbKit.getConfig() : DbKit.getConfig(configName);
052
053        int transactionLevel = transactional.transactionLevel();
054        if (transactionLevel == -1) {
055            transactionLevel = config.getTransactionLevel();
056        }
057
058
059        IAtom runnable = () -> {
060            try {
061                inv.invoke();
062            } catch (Throwable ex) {
063                for (Class<? extends Throwable> forClass : transactional.noRollbackFor()) {
064                    if (ex.getClass().isAssignableFrom(forClass)) {
065                        LogKit.error(ex.toString(), ex);
066
067                        //允许事务提交
068                        return true;
069                    }
070                }
071                throw ex;
072            }
073
074            //没有返回值的方法,只要没有异常就是提交事务
075            if (inv.getMethod().getReturnType() == void.class) {
076                return true;
077            }
078
079            Object result = inv.getReturnValue();
080
081            if (result == null && transactional.rollbackForNull()) {
082                return false;
083            }
084
085            if (result instanceof Boolean && !(Boolean) result && transactional.rollbackForFalse()) {
086                return false;
087            }
088
089            if (result instanceof Ret && ((Ret) result).isFail() && transactional.rollbackForRetFail()) {
090                return false;
091            }
092
093            return true;
094        };
095
096
097        if (!inv.isActionInvocation() && transactional.inNewThread()) {
098            try {
099                Future<Boolean> future = txInNewThread(inv, transactional.threadPoolName(), dbPro, transactionLevel, runnable);
100
101                //有返回值的场景下,需要等待返回值
102                //或者没有返回值,但是配置了 @Transacional(threadWithBlocked=ture) 的时候
103                if (inv.getMethod().getReturnType() != void.class
104                        || transactional.threadWithBlocked()) {
105                    Boolean success = future.get();
106                }
107            } catch (Exception e) {
108                LogKit.error(e.toString(), e);
109            }
110        } else {
111            dbPro.tx(transactionLevel, runnable);
112        }
113
114    }
115
116
117    public Future<Boolean> txInNewThread(Invocation inv, String name, DbPro dbPro, int transactionLevel, IAtom atom) {
118        Callable<Boolean> callable = () -> dbPro.tx(transactionLevel, atom);
119        return TransactionalManager.me().execute(name, callable, inv);
120    }
121
122
123    @Override
124    public void build(Class<?> targetClass, Method method, Interceptors interceptors) {
125        if (Util.hasAnnotation(method, Transactional.class)) {
126            interceptors.add(this);
127        }
128    }
129}