自写BINLOG2SQL方法
接上篇 Mysql回退方法(binlog数据回退的具体方法) 下面是根据binlog文件格式,将其转译成sql文件(支持误删、误更新操作),如有需求可以直接修改readFileToSQL 方法,在commit 里面自定义转译的语句。如果代码有写不好,不合理的地方,请多多指教。
package main;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.lang.reflect.Parameter;
import java.sql.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 爱喝姜撞奶
*/
public class BinLog2SqlMain {
public static final int WRITE_READ_UTF_MAX_LENGTH = 65535;
public enum SQL_PROGRESS {
/**
* 开始找
*/
BEGIN,
/**
* 找到操作关键字
*/
OPERATION,
/**
* 找到条件关键字
*/
CONDITION,
/**
* 结束
*/
COMMIT
}
public enum FORMAT_SQL_TYPE {
/**
* 根据主键更新全部
*/
updateAll,
/**
* 新增全部
*/
insertAll
}
public static final String PREFIX = "###";
public enum FORMAT_SQL_CONSTRAINT {
DELETE,
UPDATE,
WHERE,
SET,
INSERT,
VALUES
}
public static class init {
public static final String DATABASE = "DATABASE_NAME";
public static final String MYSQL_URL = "172.0.0.1:3306";
public static final String USER = "root";
public static final String PASSWORD = "PASSWORD";
public static final String TABLE_NAME = "TABLE_NAME";
//填写 "updateAll" 则根据主键更新全部,"insertAll" 则新增全部 ,否则自写要更新的语句
public static FORMAT_SQL_TYPE FORMAT_SQL = FORMAT_SQL_TYPE.insertAll;
public static final String BINLOG_FILE_PATH = "E://worklog//bin.000917.sql";
//POS点位置限制,可赋值
public static final String BINLOG_POS = "";
public static final String TARGET_FILE_PATH = "E://worklog//flushFormat.txt";
public static void printInit() {
checkParams();
System.out.println("======================================= 初始化数据 ==================================");
System.out.println(" 数据库名: " + init.DATABASE);
System.out.println(" 表名: " + init.TABLE_NAME);
System.out.println(" MYSQL主机端口: " + init.MYSQL_URL);
System.out.println(" 用户名: " + init.USER);
System.out.println(" 密码: " + init.PASSWORD);
System.out.println(" BINLOG文件路径: " + init.BINLOG_FILE_PATH);
System.out.println(" 目标文件路径: " + init.TARGET_FILE_PATH);
if (FORMAT_SQL_TYPE.updateAll.toString().equals(init.FORMAT_SQL.toString())) {
System.out.println(" 目的:将Binlog文件内容转换成 update 语句");
System.out.println(" 转化的SQL语句模板:UPDATE [table] SET ([Field =value,....]) WHERE id ='' ");
} else if (FORMAT_SQL_TYPE.insertAll.toString().equals(init.FORMAT_SQL.toString())) {
System.out.println(" 目的:将Binlog文件内容转换成 insert 语句");
System.out.println(" 转化的SQL语句模板:INSERT INTO [table] ([Field,..]) VALUES([value,....]) ");
} else {
System.out.println(" 转化的SQL语句模板: " + init.FORMAT_SQL);
}
}
}
public static void main(String[] args) throws Exception {
System.out.println("============================================ 主程序启动 =====================================");
init.printInit();
writeFilePath();
System.exit(0);
}
/**
* 获取对应的表结构
* 使用jdbc链接,DESCRIBE 进行解析表
*
* @return Map<String, Object> Key:主键 Field:字段列表
* @throws Exception jdbc相关操作引起
*/
private static Map<String, Object> getTableStructure() throws Exception {
Map<String, Object> resultMap = new HashMap<>();
List<String> result = new ArrayList<>();
Connection connection = null;
try {
String formatURL = "jdbc:mysql://%s/%s?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true";
String url = String.format(formatURL, init.MYSQL_URL, init.DATABASE);
String driver = "com.mysql.cj.jdbc.Driver";
Class.forName(driver);
//链接数据库
connection = DriverManager.getConnection(url, init.USER, init.PASSWORD);
} catch (ClassNotFoundException e) {
System.out.println("连接数据库失败:" + e);
} catch (SQLException e) {
e.printStackTrace();
}
// 根据查询获取列表签
Statement st = connection.createStatement();
String sql = "DESCRIBE %s;";
sql = String.format(sql, init.TABLE_NAME);
ResultSet rset = st.executeQuery(sql);
int i = 0;
try {
while (rset.next()) {
//只需要选一个主键就OK
if ("PRI".equals(rset.getString("Key")) && i < 1) {
resultMap.put("Key", rset.getString("Field"));
}
result.add(rset.getString("Field"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
rset.close();
resultMap.put("Field", result);
System.out.println("===================================== getTableStructure 成功=============================");
System.out.println(" 数据库(表):" + init.DATABASE + '(' + init.TABLE_NAME + ')');
System.out.println(" 主键:" + resultMap.get("Key"));
System.out.println(" 字段: " + resultMap.get("Field"));
}
return resultMap;
}
private static void writeFilePath() throws Exception {
String readPath = init.BINLOG_FILE_PATH;
String writePath = init.TARGET_FILE_PATH;
writeSQLToFile(writePath, readFileToSQL(readPath));
}
/**
* 读取mysqlbinlog工具解析后的sql文件
* 注意:读取文件使用BufferedReader的readLine逐行读取处理
*
* @param path 读取文件的路径(绝对路径)
* @return StringBuilder 拼接好的SQL
* @throws IOException 读取文件相关的操作引起
*/
private static StringBuilder readFileToSQL(String path) throws Exception {
Map<String, Object> tableStructureMap = getTableStructure();
List<String> fieldList = (List<String>) tableStructureMap.get("Field");
final String PRIMARY_KEY = tableStructureMap.get("Key").toString();
Map<String, String> fieldMap = new LinkedHashMap<>();
StringBuilder sql = new StringBuilder();
File file = new File(path);
//固定提取的字符串
String formatSQL = init.FORMAT_SQL.toString();
//修改前的值
FileInputStream fileInputStream = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"));
SQL_PROGRESS sqlProgress = SQL_PROGRESS.BEGIN;
//逐行读取
int i = 0;
boolean enablePos = false;
int a=0;
while (true) {
String line = reader.readLine();
if (Objects.isNull(line)) {
break;
}
String str = StringUtils.EMPTY;
//如果没有写POS点,则根据表名以及要求全部转译
//如果有POS点,则先寻找到POS点在进行下一步
if (StringUtils.isNotEmpty(init.BINLOG_POS)&&!enablePos) {
if (line.startsWith("# at")) {
str = line.substring(4);
str = str.replace(StringUtils.SPACE, "");
if (str.equals(init.BINLOG_POS)) {
enablePos = true;
a=4;
}
str = StringUtils.EMPTY;
}
continue;
}
if (a>0){
System.out.println(line);
a--;
}
if (enablePos&&line.startsWith("COMMIT/*!*/")){
break;
}
switch (sqlProgress) {
case BEGIN: {
//处理 ### databaseName.tableName
if (line.startsWith(PREFIX)) {
line = line.substring(3);
String rex = "`(.)*`";
Pattern pattern = Pattern.compile(rex);
Matcher matcher = pattern.matcher(line);
try {
matcher.find();
str = matcher.group();
str = str.substring(1, str.length() - 1);
} catch (Exception e) {
break;
}
}
if (StringUtils.isEmpty(str)) {
break;
}
//判断是否开头
boolean isDelete = line.replace(StringUtils.SPACE, StringUtils.EMPTY).startsWith(FORMAT_SQL_CONSTRAINT.DELETE.toString());
boolean isUpdate = line.replace(StringUtils.SPACE, StringUtils.EMPTY).startsWith(FORMAT_SQL_CONSTRAINT.UPDATE.toString());
if (isDelete || isUpdate) {
if (isDatabaseAndTableName(str)) {
sqlProgress = SQL_PROGRESS.OPERATION;
}
}
}
break;
case OPERATION: {
if (line.startsWith(PREFIX) && line.contains(FORMAT_SQL_CONSTRAINT.WHERE.toString())) {
sqlProgress = SQL_PROGRESS.CONDITION;
} else {
sqlProgress = SQL_PROGRESS.BEGIN;
}
}
break;
case CONDITION: {
int fieldIndex = -1;
String fieldValue = new String();
if (line.startsWith(PREFIX)) {
String rex = "@+?([1-9]+?[0-9]?)=";
Pattern pattern = Pattern.compile(rex);
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
str = matcher.group();
str = str.substring(1, str.length() - 1);
fieldIndex = Integer.valueOf(str) - 1;
fieldValue = line.substring(line.indexOf("=") + 1);
}
}
if (fieldIndex > -1) {
fieldMap.put(fieldList.get(fieldIndex), fieldValue);
} else {
//初始化
sqlProgress = SQL_PROGRESS.BEGIN;
fieldMap = new LinkedHashMap<>();
break;
}
if (fieldIndex == fieldList.size() - 1) {
sqlProgress = SQL_PROGRESS.COMMIT;
} else {
break;
}
}
//保留扩展
case COMMIT: {
//开始转译sql
String singleSql = null;
//更新
if (FORMAT_SQL_TYPE.updateAll.toString().equals(init.FORMAT_SQL.toString())) {
singleSql = FORMAT_SQL_CONSTRAINT.UPDATE.toString() + StringUtils.SPACE;
singleSql += "`" + init.DATABASE + "`" + ".`" + init.TABLE_NAME + "`" + StringUtils.SPACE;
singleSql += FORMAT_SQL_CONSTRAINT.SET.toString() + StringUtils.SPACE;
for (String key : fieldMap.keySet()) {
singleSql += "`" + key + "`=" + fieldMap.get(key) + ",";
}
singleSql = singleSql.substring(0, singleSql.length() - 1);
singleSql += StringUtils.SPACE + FORMAT_SQL_CONSTRAINT.WHERE + StringUtils.SPACE;
singleSql += PRIMARY_KEY + "=" + fieldMap.get(PRIMARY_KEY) + ";\r\n";
}//新增 INSERT INTO [table] ([Field,..]) VALUES([value,....])
else if (FORMAT_SQL_TYPE.insertAll.toString().equals(init.FORMAT_SQL.toString())) {
singleSql = FORMAT_SQL_CONSTRAINT.INSERT.toString() + StringUtils.SPACE + "INTO" + StringUtils.SPACE;
singleSql += "`" + init.DATABASE + "`" + ".`" + init.TABLE_NAME + "`" + StringUtils.SPACE;
singleSql += "(";
for (String key : fieldMap.keySet()) {
singleSql += "`" + key + "`" + ",";
}
singleSql = singleSql.substring(0, singleSql.length() - 1);
singleSql += ")" + StringUtils.SPACE + FORMAT_SQL_CONSTRAINT.VALUES + StringUtils.SPACE;
singleSql += "(";
for (String key : fieldMap.keySet()) {
singleSql += fieldMap.get(key) + ",";
}
singleSql = singleSql.substring(0, singleSql.length() - 1);
singleSql += ")" + ";\r\n";
} else {
}
if (StringUtils.isNotEmpty(singleSql)) {
sql.append(singleSql);
i++;
}
sqlProgress = SQL_PROGRESS.BEGIN;
fieldMap = new LinkedHashMap<>();
}
break;
default: {
throw new RuntimeException("SQL转译进程状态异常");
}
}
}
reader.close();
System.out.println("==================================== readFileToSQL 成功 ====================================");
System.out.println("====================================== 转译的sql语句总数:" + i + "==================================");
return sql;
}
/**
* 判断数据库名字与表名是否匹配
*
* @param str 读取的行数据
*/
private static Boolean isDatabaseAndTableName(String str) {
Boolean result = false;
List<String> names = Arrays.asList(str.split("`.`"));
try {
if (names != null) {
if (init.DATABASE.equals(names.get(0)) && init.TABLE_NAME.equals(names.get(1))) {
result = true;
}
}
} catch (Exception e) {
result = false;
}
return result;
}
/**
* 将SQL字符串写到某个文件上
*
* @param path 写入到的文件路径(绝对路径)
* @param sql 拼接好的SQL
* @throws IOException 写入文件操作引起
*/
public static void writeSQLToFile(String path, StringBuilder sql) throws IOException, InterruptedException {
if (StringUtils.isEmpty(sql)) {
System.out.println("没有可写入的SQL语句");
return;
}
try {
FileOutputStream file = new FileOutputStream(new File(path));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(file, "UTF-8"));
// bufferedWriter.write(sql.toString());
//防止超大数据
write(bufferedWriter, sql);
//刷新缓存后在关闭,防止还有数据在缓存里
bufferedWriter.flush();
bufferedWriter.close();
file.close();
} catch (IOException e) {
System.out.println("写入SQL失败:" + e);
}
System.out.println("======================================== writeSQLToFile 成功 ================================");
}
/**
* 将SQL字符串写到某个文件上
*
* @param sql 拼接好的SQL
* @throws IOException 写入文件操作引起
*/
public static void write(BufferedWriter file, StringBuilder sql) throws IOException {
if (sql.length() > WRITE_READ_UTF_MAX_LENGTH) {
for (int i = 1; i < sql.length() / WRITE_READ_UTF_MAX_LENGTH + 2; i++) {
file.write(sql.substring(WRITE_READ_UTF_MAX_LENGTH * (i - 1), WRITE_READ_UTF_MAX_LENGTH * i < sql.length() ? WRITE_READ_UTF_MAX_LENGTH * i : sql.length()));
}
} else {
//长度在0-65535默认写出
file.write(sql.toString());
}
}
public static void checkParams() {
if ("".equals(init.FORMAT_SQL)) {
throw new RuntimeException("FORMAT_SQL 不能为空");
}
if ("".equals(init.TARGET_FILE_PATH)) {
throw new RuntimeException("TARGET_FILE_PATH 不能为空");
}
if ("".equals(init.PASSWORD)) {
throw new RuntimeException("PASSWORD 不能为空");
}
if ("".equals(init.USER)) {
throw new RuntimeException("USER 不能为空");
}
if ("".equals(init.MYSQL_URL)) {
throw new RuntimeException("MYSQL_URL 不能为空");
}
if ("".equals(init.TABLE_NAME)) {
throw new RuntimeException("TABLE_NAME 不能为空");
}
if ("".equals(init.DATABASE)) {
throw new RuntimeException("不能为空");
}
}
}