package org.jeecg.config.mybatis.update;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.db.sql.SqlExecutor;
import com.google.common.collect.Lists;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * @description: 自定义sql执行器 * <pre> * 格外注意: 方法参数的值，list中的每一个对象的属性赋值必须保持一致。否则将无差别更新对应主键的SQL
 * @author: niugeng
 * @create: 2023-01-07 16:04
 **/
@Slf4j
@Component
@RequiredArgsConstructor
public class UpdateBatchUtil<T> {
    private final DataSource dataSource;
    private final TransactionUtil transactionUtil;
    /** * 依据主键批量组装更新SQL语句，并执行SQL语句 * * @param list 需要批量更新的实体类集合 * @return boolean 执行成功true，反之false * @throws SpliceSqlRuntimeException 如果没有指定表名称 */
    public boolean updateByIds(@NonNull List<T> list) throws RuntimeException {
        TableCacheDTO tableCacheDTO = getTableCacheDTO(list.get(0).getClass());
        return updateBatch(list, tableCacheDTO, 500);
    }
    /** * 依据主键批量组装更新SQL语句，并执行SQL语句 * * @param list 需要批量更新的实体类集合 * @param partSize 分段大小配置 * @return boolean 执行成功true，反之false * @throws SpliceSqlRuntimeException 如果没有指定表名称 */
    public boolean updateByIds(@NonNull List<T> list, int partSize) throws RuntimeException {
        TableCacheDTO tableCacheDTO = getTableCacheDTO(list.get(0).getClass());
        return updateBatch(list, tableCacheDTO, partSize < 1 ? 500 : partSize);
    }
    /** * 获取数据库表缓存信息 * * @param entity 实体类 * @return priv.utrix.exam.domain.dto.TableCacheDTO 根据实体类获取的数据库表缓存信息 */
    private TableCacheDTO getTableCacheDTO(Class<?> entity) throws RuntimeException {
        TableCacheDTO tableCacheDTO = TableCache.getTABLE_MAPS().get(entity.getName());
        if (tableCacheDTO == null) {
            throw new RuntimeException("根据实体类名称获取不到表缓存信息");
        }
        return tableCacheDTO;
    }
    /** * 分段批量事务执行update语句 * * @param list 实体类集合 * @param tableCacheDTO 数据库表缓存信息 * @param partSize 分段大小 * @return boolean 更新成功true, 反之false */
    private boolean updateBatch(List<T> list, TableCacheDTO tableCacheDTO, int partSize) {
        List<List<T>> partList = CollUtil.split(list, partSize);
        List<String> updateSql = Lists.newArrayListWithExpectedSize(partList.size());
        long start = System.currentTimeMillis();
        for (List<T> part : partList) {
            // 获取数据库表名
            List<String> strings = MakeSqlUtil.spliceUpdateSql(part, tableCacheDTO);
            assert strings != null;
            if (strings.isEmpty()) {
                continue;
            }
            for (String sql : strings) {
                updateSql.add(sql);
            }
        }
        log.info("拼接[{}]条 SQL总耗时: {} 毫秒", updateSql.size(), System.currentTimeMillis()-start);
        if (CollUtil.isEmpty(updateSql)) {
            return false;
        }
        Connection connection = null;
        try {
            try {
                connection = dataSource.getConnection();
            } catch (SQLException e) {
                throw new RuntimeException("批量更新获取连接报错:"+e.getMessage());
            }
            // 开启事务，执行
            Connection finalConnection = connection;
            return transactionUtil.execute(() -> {
                for (String sql : updateSql) {
                    try {
                        log.info("拼接后的更新语句为：\n\n"+sql+"\n");

                        int execute = SqlExecutor.execute(finalConnection, sql);

                        log.info("更新条数："+execute);
                    } catch (Exception e) {
                        // 抛出异常后, 回滚执行的SQL
                        throw new RuntimeException(e.getMessage());

                    }
                }
                return  true;
            });
        } catch (Exception e) {
            log.error("执行update sql异常, 详情: ", e);
            throw new RuntimeException(e.getMessage());
        }finally {
            if (connection !=null){
                try {
                    connection.close();
                } catch (SQLException ignored) {

                }
            }
        }
    }
}
