导入.sql文件显示进度条
业务以及问题描述:要将.sql文件进行上传并导入到数据库中。使用ScriptRunner的runScript方法。封装好的方法没办法获取执行的次数就没办法算百分比。
开始的思路是计算大文件上传的进度条,但是后来想到实际上占用时间比较久的是sql导入执行的过程。怎么把这两个进度一起进行合并计算执行进度就是个比较大的问题了。
将重点放在runScript方法,runScript有两种执行方法,一种是sendFullScript为true的将会执行executeFullScript方法,另为executeLineByLine。
public void runScript(Reader reader) {
setAutoCommit();
try {
if (sendFullScript) {
executeFullScript(reader);
} else {
executeLineByLine(reader);
}
} finally {
rollbackConnection();
}
}
顾名思义,一种是全部一起执行,一种为一行一行的执行。可以查看源代码得知,executeFullScript将每一行进行拼接,传入executeStatement方法。executeLineByLine将读取到的每一行传入到handleLine方法中,底层实现也是通过executeStatement执行。(这里我就不贴源码啦,可以自己去看一下)
说一下实现思路:
骚操作一波,文件上传部分我自己设置了百分之十,剩下百分之九十为sql的导入。创建文件夹设置百分之三到百分之四的随机数,上传文件设置百分之五到百分之六。计算文件行数(为计算sql进度做准备)设置百分之十。
接下来是sql部分:
先说思路,再贴代码。思路很简单,将ScriptRunner的代码copy出来自己改。改executeLineByLine的方法就行。需要传入三个参数:Read read和文件的总行数total,用户主键userAccount(存储进度时候的key值)在读取每一行数据的时候进行累加,将累加的值除以文件总行数total就行。
(用Redis的时候这里遇到个问题,不能注入于是乎自己写了个方法存储)
上传文件代码:
public boolean uploadFile(MultipartFile file, HttpServletRequest request,User user) throws Exception {
Random random = new Random();
String f = file.getOriginalFilename();
//拼接文件名
String filename = f.substring(f.lastIndexOf("/")+1);
String id = UUID.randomUUID().toString().replaceAll("_","").substring(0,8);
String type = filename.substring(filename.lastIndexOf("."));
String name = filename.substring(0,filename.lastIndexOf("."));
filename = name+id+type;
//如果上传的不是sql文件抛出文件错误
if(!".sql".equals(type)){
throw new BusinessException(CodeEnum.FILE_ERROR.getCode(),CodeEnum.FILE_ERROR.getMsg());
}
//创建文件夹
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
String nowDate = format.format(new Date());
String filePath = new File(fileUploadUrl+"/"+nowDate).getAbsolutePath();
File fileUpload = new File(filePath);
//在创建文件夹的时候设置百分之三到百分之四的进度
ProcessUtil.setKey(user.getAccount(),(double) Math.round(3+(random.nextDouble())) / 100);
//不存在就创建
if (!fileUpload.exists()) {
fileUpload.mkdirs();
}
fileUpload = new File(filePath, filename);
//上传文件
file.transferTo(fileUpload);
//在文件上传的时候设置百分之5到百分之6的进度
ProcessUtil.setKey(user.getAccount(),(double) Math.round(5+(random.nextDouble())) / 100);
//文件路径
String path = fileUpload.getAbsolutePath();
//计算文件行数,为sql进度条做准备
long lineNumber = this.getLineNumber(fileUpload);
//计算完文件行数之后设置
ProcessUtil.setKey(user.getAccount(),0.1);
//sql上传
boolean flag = this.uploadSql(path,lineNumber,user);
return flag;
}
getLineNumber计算文件行数:
private long getLineNumber(File file) {
if (file.exists()) {
try {
FileReader fileReader = new FileReader(file);
LineNumberReader lineNumberReader = new LineNumberReader(fileReader);
lineNumberReader.skip(Long.MAX_VALUE);
long lines = lineNumberReader.getLineNumber() + 1;
fileReader.close();
lineNumberReader.close();
return lines;
} catch (IOException e) {
e.printStackTrace();
}
}
return 0;
}
uploadSql: 执行.sql文件导入数据库
public boolean uploadSql(String path,long lineNumber,User user) throws Exception {
Class.forName(driverClassName);
Connection conn = DriverManager.getConnection(url, username, password);
// 创建ScriptRunner,用于执行SQL脚本
ScriptRunner runner = new ScriptRunner(conn);
runner.setErrorLogWriter(null);
runner.setLogWriter(null);// 执行SQL脚本
Reader read = new FileReader(new File(path));
runner.runScript(read,lineNumber,user.getAccount());
//删除存储的进度条数值
ProcessUtil.removeKey(user.getAccount());
// 关闭连接
conn.close();
return true;
}
自己改写的ScriptRunner类(只贴了改的部分,剩下的不变):
public void runScript(Reader reader, long total, String userAccount) {
setAutoCommit();
try {
if (sendFullScript) {
executeFullScript(reader);
} else {
executeLineByLine(reader,total,userAccount);
}
} finally {
rollbackConnection();
}
}
private void executeLineByLine(Reader reader,long total,String userAccount) {
StringBuilder command = new StringBuilder();
try {
double i = 0.0;
BufferedReader lineReader = new BufferedReader(reader);
String line;
while ((line = lineReader.readLine()) != null) {
handleLine(command, line);
i++;
double percent = (i / total) * 0.9;
BigDecimal bigDecimal = new BigDecimal(percent);
percent = bigDecimal.setScale(3,BigDecimal.ROUND_HALF_UP).doubleValue();
double v = new BigDecimal(percent).add(new BigDecimal(0.09)).setScale(3,BigDecimal.ROUND_HALF_UP).doubleValue();
System.out.println(v);
ProcessUtil.setKey(userAccount,v);
}
commitConnection();
checkForMissingLineTerminator(command);
ProcessUtil.setKey(userAccount,1.00);
} catch (Exception e) {
String message = "Error executing: " + command + ". Cause: " + e;
printlnError(message);
throw new RuntimeSqlException(message, e);
}
}
ProcessUtil类:存储进度条数据
public class ProcessUtil {
private static Map<String, Double> map = new HashMap<String, Double>();
public static synchronized Double getKey(String key){
return map.get(key);
}
public static void setKey(String key,Double value){
map.put(key, value);
}
public static synchronized void removeKey(String key){
if(map.containsKey(key)){
map.remove(key);
}
}
}
基本上业务实现了,本来进度条也只是个大概的时间提示嘛,如果有大佬有将文件上传进度和sql导入进入可以一起计算的方法可以分享~因为实现这种计算方式要额外计算文本行数,多少会有一丢丢影响上传时间。但是没有想到两全其美的办法。
https://blog.csdn.net/qq_42065027/article/details/118387864