前言:
时隔一年多再次回顾数据库数据导出,在这里对博文做一个完善,以及所遇到的问题。
并附上我最近的demo地址:myGithub
完善要点:
1. 对相关DOS命令进行完善以及应用
2. 对dbTask类中添加相应注释
3. 添加dbTask类中的JdbcBean对象实体
4. 添加数据导入数据库的业务方法
注:更新处统一加“//实例20200509” 注释标记
开发需求:
做一个定时任务,每天凌晨备份一下MySQL数据库数据到指定目录。
在网上查了很多资料,最后终于搞出来了,网上查了都是通过DOS命令来导出MySQL数据库数据的;
dos命令执行导出备份命令格式:
mysqldump -P port -h ip -u username -ppassWord dbName > d:\db.sql
//实例20200509
D:\softwares\mysql\mysql-8.0.18-winx64\bin>mysqldump -P 3306 -h 127.0.0.1 -u root -proot demo > d:\data\peng\dbfiles\demo_db1.sql
//实例20200509
dos命令执行数据导入数据库命令格式:
要注意的是该命令必须在MySQL数据库安装目录下bin下执行。因此数据库必须要安装才行。
说来无奈,这项目服务器上原来没有装数据库,导致一直不行,最后装了个才搞好了。。。
好了,进入正题:在项目中首先要引入定时任务,【对了,提前说下我这个项目用springboot框架,因此有很多都是注解,也是meavn项目;】
方法是:
1.在项目启动类上加注解@EnableScheduling——表示启用定时任务。
package com.zhang;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableScheduling;
import com.zhang.datasources.DynamicDataSourceConfig;
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@Import({DynamicDataSourceConfig.class})
@EnableScheduling//启用定时任务注解**************************************************************
public class ZhangApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ZhangApplication .class, args);
System.out.println("运行完毕!!!!");
System.out.println("启动");
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ZhangApplication .class);
}
}
//实例20200509
该注解也可以写在定时任务配置类上,自定义一个配置类,添加 @EnableScheduling // 启用定时任务,这样就不用写在启动类上了,已测试OK。如下图:
2.在要调用的方法上加注解
@Scheduled(cron="59 59 23 * * ? ") //表示每天晚上23点59分59秒执行一次 其中表达式cron来设置定时任务的时间
附上定时任务时间设置链接:https://www.cnblogs.com/thomas12112406/p/6183946.html
这里当时写的是@Scheduled(cron="0 0 24 * * ? ") ,最后上传服务器,发现项目都无法部署了,报这个表达式格式错误,最后网上一查才知道,这个格式的确错了,就没有零晨24点的写法,无奈之下,直接改为@Scheduled(cron="59 59 23 * * ? ") ,最后测试成功。
//实例20200509
3.JdbcBean.java对象
package com.zhang.demo.job.entity;
/**
* 数据库信息
* @author zhang
* @date 2020-05-08 15:56:00
*/
public class JdbcBean {
private String ip;
private String db;
private int port;
private String username;
private String password;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getDb() {
return db;
}
public void setDb(String db) {
this.db = db;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4. 编写数据备份/数据导入业务DbTask.java
//实例20200509
package com.zhang.demo.job.task;
import com.zhang.demo.common.CommonResult;
import com.zhang.demo.job.entity.JdbcBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
/**
* @author zhang
* @date 2020-05-08 15:56:00
*/
@RestController
public class DbTask {
public static final String WINDOWS_UPLOAD_PATH = "D:/data/peng/dbfiles/";
public static final String LINUX_UPLOAD_PATH = "/data/peng/dbfiles/";
public static final String SQL_BACKUP_PREFIX_FORMAT = "yyyyMMddHHmmss";
private Logger logger = LoggerFactory.getLogger(getClass());
static String filePath;
/**
* 数据库备份 导出.sql文件到本地
* @return
*/
@Scheduled(cron="59 59 23 * * ? ")//表示每天晚上23点59分59秒执行一次
public CommonResult backup(){
try {
String fileName = new SimpleDateFormat(SQL_BACKUP_PREFIX_FORMAT).format(new Date())+"_backup";
filePath = getFilePath(fileName+".sql");
/*
* 动态获取数据库配置
*/
JdbcBean j = new JdbcBean();
Properties prop = new Properties();//读取properties 文件所用的类
try {
InputStream in = DbTask.class.getResourceAsStream("/application.properties");
// InputStream in = DbTask.class.getResourceAsStream("/application.yml");
prop.load(in); // /加载属性列表
String [] a = prop.getProperty("spring.datasource.druid.first.url").split("/");
j.setIp(a[2].split(":")[0]);
j.setDb(a[3].trim().split("[?]")[0]);
j.setPort(Integer.parseInt(a[2].split(":")[1]));
j.setUsername(prop.getProperty("spring.datasource.druid.first.username"));
j.setPassword(prop.getProperty("spring.datasource.druid.first.password"));
in.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
boolean exportFlag = executeExportCommond(j,filePath);
if(exportFlag){
logger.info("数据库数据备份本地成功!");
return CommonResult.success("数据库数据备份本地成功!");
}else{
logger.info("数据库数据备份失败");
return CommonResult.failed("数据库数据备份失败");
}
} catch (Exception e) {
logger.info("运行异常,数据库数据备份失败!");
e.printStackTrace();
return CommonResult.failed("备份失败");
}
}
/**
* 导出数据
* dos命令执行备份 mysqldump -P port -h ip -u username -ppassword dbName > d:\db.sql
* (注意命令中-ppassWord ———— -p和密码之间不能有空格(导出命令中未输入密码的跳过该错),
* 否则会提示输入密码,输入之后回车会报错【报错信息:
* mysqldump: Got error: 1049: Unknown database 'root' when selecting the database】)
* DOS命令如下:
* D:\softwares\mysql\mysql-8.0.18-winx64\bin>
* mysqldump -P 3306 -h 127.0.0.1 -u root -proot demo > d:\data\peng\dbfiles\demo_db.sql
* @param jdbcBean
* @param filePath
* @return
* @throws IOException
* @throws InterruptedException
*/
//执行导出命令
public static boolean executeExportCommond(JdbcBean jdbcBean,String filePath) throws IOException, InterruptedException{
//动态获取本地MySQL数据库安装目录bin下的目录
String C=getMysqlPath();
//静态获取本地MySQL数据库安装目录下bin的目录
// String C="C:\\Program Files\\MySQL\\MySQL Server 5.6\\bin\\";
String sql ="mysqldump -P "+jdbcBean.getPort()+" -h "+jdbcBean.getIp()+
" -u "+jdbcBean.getUsername()+" -p"+jdbcBean.getPassword()+
" "+jdbcBean.getDb()+" --default-character-set=utf8 "+
"--lock-tables=false --result-file="+filePath;
System.out.println(sql);
Process process = Runtime.getRuntime().exec(sql);
if(process.waitFor()==0){//0 表示线程正常终止。
return true;
}
return false;//异常终止
}
/**
* 导入指定sql文件数据到指定数据库
* sql_1 ———— 表示登录mysql数据库中。命令:mysql -P 3306 -h localhost -u root -proot --default-character-set=utf8
* sql_2 ———— 表示使用mysql数据库中的哪一个数据库。命令:use test
* sql_3 ———— 表示执行导入该路径下的sql文件。命令:source d:\data\peng\dbfiles\demo_db1.sql
*
* @date 2020-05-09 09:56:56
* @param jdbcBean
* @param filePath
* @return
* @throws Exception
*/
//执行导入命令
public static boolean executeImportCommond(JdbcBean jdbcBean,String filePath) throws Exception {
//动态获取本地MySQL数据库安装目录bin下的目录
String C=getMysqlPath();
//静态获取本地MySQL数据库安装目录下bin的目录
// String C="C:\\Program Files\\MySQL\\MySQL Server 5.6\\bin\\";
String sql_1 ="mysql -P "+jdbcBean.getPort()+" -h "+jdbcBean.getIp()+
" -u "+jdbcBean.getUsername()+" -p"+jdbcBean.getPassword()+
" --default-character-set=utf8";
String sql_2 = "use "+jdbcBean.getDb();
String sql_3 = "source "+filePath;
Process process = Runtime.getRuntime().exec(sql_1);
OutputStream os = process.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os);
writer.write(sql_2 + "\r\n" + sql_3);
writer.flush();
writer.close();
os.close();
if(process.waitFor()==0){
return true;
}
return false;
}
/**
* 动态获取mysql安装路径(bin下的路径)
*【注】:本地MySQL数据库配置环境变量(新建MYSQL_HOME变量,值为“MySQL安装路径下【bin的上一层】”,
* 在Path变量中配置MYSQL_HOME变量,值为【%MYSQL_HOME%bin】)。
* @return
*/
public static String getMysqlPath(){
@SuppressWarnings("rawtypes")
Map m=System.getenv();
String s2=(String) m.get("MYSQL_HOME");//获取本计算机环境变量中PATH的内容
String mySqlPath=s2+"\\bin\\";
System.out.println("MySQL本地安装路径:"+mySqlPath);
return mySqlPath;
}
/**
* 获得文件路径
* @param fileName
* @return
*/
public static String getFilePath(String fileName){
String os = System.getProperty("os.name"); //获取操作系统的名称
String rootPath;
String filPath;
if(os.toLowerCase().startsWith("win") || os.toLowerCase().startsWith("Win")){
rootPath = WINDOWS_UPLOAD_PATH;
}else{
rootPath = LINUX_UPLOAD_PATH;
}
if(!new File(rootPath).exists()){//判断文件是否存在
new File(rootPath).mkdirs();//可以在不存在的目录中创建文件夹。诸如:a\\b,既可以创建多级目录。
}
if(fileName==null){
filPath = rootPath+new SimpleDateFormat(SQL_BACKUP_PREFIX_FORMAT).format(new Date())+"_backup.sql";
}else{
filPath = rootPath+fileName;
}
return filPath;
}
}
5.直接启动项目,然后就根据你设置的时间来进行调用这个方法。
【这目前只是导出MySQL数据库的Java写法,导入还没整出来,欢迎大神们指点一二,我会再接再励的】
**********完善博文20210417**********
返回格式CommonResult对象和自定义状态码对象(状态码大家可以自行修改):
/**
* 通用返回格式
* @author zhang
* @date 2021-04-17 21:41:00
*/
public class CommonResult<T> {
private String code;
private String message;
private T data;
public CommonResult() {
}
protected CommonResult(String code, String message) {
this.code = code;
this.message = message;
}
protected CommonResult(String code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 成功 无data数据
*
*/
public static <T> CommonResult<T> success() {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage());
}
/**
* 成功返回结果
*
* @param data 获取的数据
*/
public static <T> CommonResult<T> success(T data) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 成功返回message
*
* @param message 提示信息
*/
public static <T> CommonResult<T> success(String message) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, (T) new ArrayList<T>());
}
/**
* 成功返回结果
*
* @param data 获取的数据
* @param message 提示信息
*/
public static <T> CommonResult<T> success(T data, String message) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 成功返回结果
*
* @param message 提示信息
*/
public static <T> CommonResult<T> success(String message, String resultData) {
Map<String, String> resultDataMap = new HashMap<String, String>();
resultDataMap.put("url", resultData);
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, (T) resultDataMap);
}
/**
* 失败返回结果
*
* @param errorCode 错误码
*/
public static <T> CommonResult<T> failed(ResultCode errorCode) {
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), (T) new ArrayList<T>());
}
/**
* 失败返回结果
*
* @param errorCode 错误码
* @param message 错误信息
*/
public static <T> CommonResult<T> failed(ResultCode errorCode, String message) {
return new CommonResult<T>(errorCode.getCode(), message, null);
}
/**
* 失败返回结果
*
* @param code 错误状态码
* @param message 错误信息
*/
public static <T> CommonResult<T> failed(Integer code, String message) {
return new CommonResult<T>(Integer.toString(code), message, null);
}
/**
* 失败返回结果
*
* @param message 提示信息
*/
public static <T> CommonResult<T> failed(String message) {
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, (T) new ArrayList<T>());
}
/**
* 失败返回结果
*/
public static <T> CommonResult<T> failed() {
return failed(ResultCode.FAILED);
}
/**
* 参数验证失败返回结果
*/
public static <T> CommonResult<T> validateFailed() {
return failed(ResultCode.VALIDATE_FAILED);
}
/**
* 参数验证失败返回结果
*
* @param message 提示信息
*/
public static <T> CommonResult<T> validateFailed(String message) {
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, (T) new ArrayList<T>());
}
/**
* 未登录返回结果
*/
public static <T> CommonResult<T> unauthorized(T data) {
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
}
/**
* 未授权返回结果
*/
public static <T> CommonResult<T> forbidden(T data) {
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
}
/**
* 未登录返回结果
*/
public static <T> CommonResult<T> repeatSubmitFailed(String code, String message) {
return new CommonResult<T>(code, message, (T) new ArrayList<T>());
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
// 自定义状态码
/**
* 枚举了一些常用API操作码
* @author zhang
* @date 2021-04-17 21:41:00
*/
@Getter
public enum ResultCode {
/**
* 操作成功
*/
SUCCESS("Z200", "操作成功"),
/**
* 操作失败
*/
FAILED("Z500", "操作失败"),
/**
* 参数校验失败
*/
VALIDATE_FAILED("Z404", "参数检验失败"),
/**
* 暂未登录或token已经过期
*/
UNAUTHORIZED("Z401", "暂未登录或token已经过期"),
/**
* 没有相关权限
*/
FORBIDDEN("Z403", "没有相关权限"),
/**
* 密码错误
*/
PASSWORD_ERROR("Z455", "用户名或密码错误"),
/**
* 账号被禁用
*/
LOCK_ERROR("Z456", "账号已被锁定~"),
/**
* 限流访问失败提示
*/
LIMITING("Z001", "请求频繁,请稍后再试~"),
/**
* 网络异常
*/
SERVER_ERROR("Z999","服务器错误"),
/**
* 版本号错误
*/
VERSION_ERROR("Z601","版本号错误"),
/**
* 用户名或密码错误
*/
USER_PASSWORD_ERROR("Z452","用户名或密码错误"),
/**
* 刷新Token错误
*/
REFRESH_TOKEN_ERROR("Z402","refresh token error");
private String code;
private String message;
private ResultCode(String code, String message) {
this.code = code;
this.message = message;
}
}