在我们的产品中,一个公司对应着一个数据库。除了名称不同,这些数据库有着相同的存储过程和数据库表信息。所以我们可以将创建存储过程和数据库表的sql语句放在一个地方,当创建公司时,运行这些sql脚本语句,就可以实现动态创建数据库。创建数据库,运行sql脚本的工作使用了Windows服务来做。服务器主要通过文件与Windows服务进行进程间的通信。本文将介绍:“服务器通知Windows服务需要创建数据库和执行sql脚本”、“Windows服务收到服务器请求,执行sql脚本语句”、“Java如何创建Window服务”。
1、 服务器通知Windows服务需要创建数据库和执行sql脚本。
创建好公司后,需要创建对应数据库:
private boolean createPrivateDB(String dbName) {
logger.info("正在创建公司的私有DB:" + dbName);
boolean isCreated = false;
try {
final String sharedMemoryPath = "D:\\QueenService\\ProcessMessage\\";// 进程间通信文件所在目录
@SuppressWarnings("resource")
FileChannel fcClient = new RandomAccessFile(sharedMemoryPath + "client.mm", "rw").getChannel();
@SuppressWarnings("resource")
FileChannel fcServer = new RandomAccessFile(sharedMemoryPath + "server.mm", "rw").getChannel();
ProcessUtil.writeSharedMemory(fcClient, dbName);
int timeOut = 20;
String processMsg = "";
while (timeOut-- > 0) {
processMsg = ProcessUtil.readJSON(ProcessUtil.readSharedMemory(fcServer), BaseAction.JSON_PROCESS_KEY);
if ("SUCCESS".equals(processMsg)) {// ... hardcode
logger.info("数据库创建完成!!!");
isCreated = true;
break;
} else if ("FAIL".equals(processMsg)) {
logger.info("数据库创建失败,请查看QueenService日志查找原因!!!");
break; // ...是否使用事务?
}
Thread.sleep(2000 * 2);// 设置创建数据库超时时间...
}
if ("".equals(processMsg) || processMsg == null) {
logger.info("数据库创建超时...");
// ...是否需要将上面创建的门店删除?
}
} catch (Exception e) {
logger.info("创建公司数据库异常:" + e.getMessage());
}
return isCreated;
}
创建数据库需要与Windows服务(服务名称为QueenService)进行进程间的通信。
定义线程间通信的目录:
final String sharedMemoryPath = "D:\\QueenService\\ProcessMessage\\";// 进程间通信文件所在目录
在该目录下创建两个文件:client.mm,server.mm。Java程序主要向client.mm写入数据库名称,请求windows服务根据数据库名称创建数据库。Windows服务负责读取client.mm文件,创建对应数据库。创建成后向server.mm文件写入创建成功或失败信息。Java程序可以从server.mm文件中判断是否成功创建数据库。
与client.mm和server.mm建立连接:
@SuppressWarnings("resource")
FileChannel fcClient = new RandomAccessFile(sharedMemoryPath + "client.mm", "rw").getChannel();
@SuppressWarnings("resource")
FileChannel fcServer = new RandomAccessFile(sharedMemoryPath + "server.mm", "rw").getChannel();
往client.mm文件中写入要创建的数据库名称:
ProcessUtil.writeSharedMemory(fcClient, dbName);
public static void writeSharedMemory(FileChannel fc, String message) throws Exception {
logger.info("发送消息之前清除共享内存,防止干扰...");
int size = (int) fc.size();
MappedByteBuffer buffer = fc.map(MapMode.READ_WRITE, 0, size);
for (int i = 0; i < size; i++) {
buffer.put(i, (byte) 0);
}
logger.info("发送进程消息:" + message);
byte[] bytes = message.getBytes();
buffer = fc.map(MapMode.READ_WRITE, 0, bytes.length);
for (int i = 0; i < bytes.length; i++) {
buffer.put(i,bytes[i]);
}
}
每隔两秒钟读取server.mm文件的内容,读取20次:
int timeOut = 20;
String processMsg = "";
while (timeOut-- > 0) {
processMsg = ProcessUtil.readJSON(ProcessUtil.readSharedMemory(fcServer), BaseAction.JSON_PROCESS_KEY);
if ("SUCCESS".equals(processMsg)) {// ... hardcode logger.info("数据库创建完成!!!");
isCreated = true;
break;
} else if ("FAIL".equals(processMsg)) {
logger.info("数据库创建失败,请查看QueenService日志查找原因!!!");
break; // ...是否使用事务?
}
Thread.sleep(2000 * 2);// 设置创建数据库超时时间...
}
if ("".equals(processMsg) || processMsg == null) {
logger.info("数据库创建超时...");
// ...是否需要将上面创建的门店删除?
}
public static String readSharedMemory(FileChannel fc) throws Exception {
int size = (int) fc.size();
MappedByteBuffer buffer = fc.map(MapMode.READ_WRITE, 0, size);
byte[] bytes = new byte[size];
buffer.get(bytes);
String msg = new String(bytes, 0, size).trim();
if (!"".equals(msg) && null != msg) {
logger.info("读取进程消息:" + msg);
logger.info("读取进程消息完毕,清除消息...");
for (int i = 0; i < size; i++) {
buffer.put(i, (byte) 0);
}
}
return msg;
}
2、Windows服务收到服务器请求,执行sql脚本语句。
QueenService Windows服务代码。
创建一个Queen类:
public class Queen {
private static Thread thread = null;
private static Service service = null;
Windows服务需要定义两个方法:StartService和StopService方法:
/** 退出服务方法(该方法必须有参数 String [] args)
*
* @param args
*/
public static void StopService(String[] args) {
Service.log("停止服务");
service.setRunFlag(false);
}
/** 启动服务方法(该方法必须有参数 String [] args)
*
* @param args
*/
public static void StartService(String[] args) {
// 产生服务线程
service = new Service();
thread = new Thread(service);
Service.log("-----------启动服务-----------");
try {
// 将服务线程设定为用户线程,以避免StartService方法结束后线程退出
thread.setDaemon(false);
if (!thread.isDaemon()) {
Service.log("成功设定线程为用户线程!");
}
// 启动服务线程
thread.start();
} catch (SecurityException se) {
Service.log("线程类型设定失败!");
}
}
Queen里面创建一个内部类Service继承于Runnable接口,它的run方法负责创建数据库:
class Service implements Runnable
run方法每隔3秒钟读取一次client.mm文件:
RandomAccessFile rafClient = new RandomAccessFile(sProcessMessage + "client.mm", "rw");
RandomAccessFile rafServer = new RandomAccessFile(sProcessMessage + "server.mm", "rw");
FileChannel fcClient = rafClient.getChannel();
FileChannel fcServer = rafServer.getChannel();
String dbName = readSharedMemory(fcClient);
如果发现dbName不为空,则准备创建dbName数据库:
String dbName = readSharedMemory(fcClient);
if (dbName != null && !"".equals(dbName)) {
log("-----------------------------------------------------------------------------------------------");
log("-----------------------------------------皇后初级版-----------------------------------------------");
log("-----------------------------------------------------------------------------------------------");
log("门店创建成功,正在创建数据库...");
dbName = dbName.trim();
if (doSVNCleanup() && doSVNRevert() && doSVNCleanup() && doSVNUpdate() && refreshScripts(dbName) && startWAMP() && createPrivateDB(dbName)) {
// 发送进程消息告诉Action已经创建了DB
log("¥¥¥¥¥¥¥¥¥¥¥¥¥创建私有DB:" + dbName + "成功!");
writeSharedMemory(fcServer, Process_Success);
} else {
log("¥¥¥¥¥¥¥¥¥¥¥¥¥创建私有DB:" + dbName + "失败!");
writeSharedMemory(fcServer, Process_Fail);
}
}
rafClient.close();
rafServer.close();
doSVNCleanup方法负责执行svn cleanup命令:
private static boolean doSVNCleanup() {
log("正在svn cleanup " + sPathSqlDir);
List<String> cleanupCmds = new ArrayList<>();
cleanupCmds.add("svn.exe");
cleanupCmds.add("cleanup");
cleanupCmds.add(sPathSqlDir);
log("命令=" + cleanupCmds);
ProcessBuilder pb = new ProcessBuilder(cleanupCmds);
try {
byte[] ba = new byte[1024];
pb.redirectErrorStream(true);
Process p = pb.start();
log("正在等待进程完成...");
if (p.waitFor() == 0) {
log("执行 svn cleanup 进程完成:" + new String(ba).trim());
Thread.sleep(1000); // 等待文件系统就绪
} else {
p.getErrorStream().read(ba);
p.getErrorStream().close();
log("执行 svn cleanup 进程错误:" + new String(ba).trim());
return false;
}
} catch (Exception e) {
log("doSVNCleanup异常:" + e.getMessage());
return false;
}
return true;
}
doSVNRevert执行svn revert命令:
private static boolean doSVNRevert() {
log("正在svn revert " + sPathSqlDir);
List<String> restoreCmds = new ArrayList<>();
restoreCmds.add("svn.exe");
restoreCmds.add("revert");
restoreCmds.add("-R");
restoreCmds.add(sPathSqlDir);
log("命令=" + restoreCmds);
ProcessBuilder pb = new ProcessBuilder(restoreCmds);
try {
byte[] ba = new byte[1024];
pb.redirectErrorStream(true);
Process p = pb.start();
log("正在等待进程完成...");
if (p.waitFor() == 0) {
log("执行 svn revert 进程完成:" + new String(ba).trim());
Thread.sleep(5000); // 等待文件系统就绪
} else {
p.getErrorStream().read(ba);
p.getErrorStream().close();
log("执行 svn revert 进程错误:" + new String(ba).trim());
return false;
}
} catch (Exception e) {
log("doSVNRevert异常:" + e.getMessage());
return false;
}
return true;
}
doSVNUpdate方法执行svn update命令:
private static boolean doSVNUpdate() {
if ("".equals(sCurrentReleaseSvnVersionNO)) {
log("正在svn update " + sPathSqlDir + "...到最新版本");
} else {
log("正在svn update " + sPathSqlDir + "...到特定版本:" + sCurrentReleaseSvnVersionNO);
}
List<String> restoreCmds = new ArrayList<>();
restoreCmds.add("svn.exe");
restoreCmds.add("update");
if (!"".equals(sCurrentReleaseSvnVersionNO)) {
restoreCmds.add("-r");
restoreCmds.add(sCurrentReleaseSvnVersionNO);
}
restoreCmds.add(sPathSqlDir);
log("命令=" + restoreCmds);
ProcessBuilder pb = new ProcessBuilder(restoreCmds);
try {
byte[] ba = new byte[2048];
pb.redirectErrorStream(true);
Process p = pb.start();
log("正在等待进程完成...");
if (p.waitFor() == 0) {
log("执行 svn update 进程完成:" + new String(ba).trim());
Thread.sleep(1000); // 等待文件系统就绪
} else {
p.getErrorStream().read(ba);
p.getErrorStream().close();
log("执行 svn update 进程错误:" + new String(ba).trim());
return false;
}
} catch (Exception e) {
e.printStackTrace();
log("doSVNUpdate异常:" + e.getMessage());
return false;
}
return true;
}
refreshScripts方法刷新数据库脚本语句:
private static boolean refreshScripts(String dbName) {
log("正在刷新脚本数据库...");
File autoImportDBPrivate_bat = new File(sAutoImportSqlDir + BAT_AutoImportDBPrivate);
File autoImportDBPrivate_sql = new File(sAutoImportSqlDir + SQL_AutoImportDBPrivate);
File database_sql = new File(sAutoImportSqlDir + SQL_CreateDatabase);
if (autoImportDBPrivate_bat.exists() && autoImportDBPrivate_sql.exists() && database_sql.exists()) {
// 修改AutoImportDBPrivate.bat文件
String str_bat = readFileToString(autoImportDBPrivate_bat);
str_bat = str_bat.replace(DIR_MysqlBin, sPathMysqlToReplace.replace("mysql.exe", ""));
// 修改AutoImportDBPrivate.sql文件
String str_sql = readFileToString(autoImportDBPrivate_sql);
str_sql = str_sql.replace(sStringInSqlScriptToReplace, sPathSqlDir);
str_sql = str_sql.replace("{nbr_xxx}", dbName);
// 修改Database.sql文件
String str_db = readFileToString(database_sql);
str_db = str_db.replace("{nbr_xxx}", dbName);
try {
OutputStreamWriter write1 = new OutputStreamWriter(new FileOutputStream(autoImportDBPrivate_bat), "utf-8");
BufferedWriter writer1 = new BufferedWriter(write1);
writer1.write(str_bat);
writer1.close();
OutputStreamWriter write2 = new OutputStreamWriter(new FileOutputStream(autoImportDBPrivate_sql), "utf-8");
BufferedWriter writer2 = new BufferedWriter(write2);
writer2.write(str_sql);
writer2.close();
OutputStreamWriter write3 = new OutputStreamWriter(new FileOutputStream(database_sql), "utf-8");
BufferedWriter writer3 = new BufferedWriter(write3);
writer3.write(str_db);
writer3.close();
} catch (Exception e) {
e.printStackTrace();
log("refreshScripts异常:" + e.getMessage());
return false;
}
} else {
System.err.println("Can't Find " + autoImportDBPrivate_bat + " or " + autoImportDBPrivate_sql + " or " + database_sql);
log("刷新数据库脚本失败!");
return false;
}
log("刷新数据库脚本成功!");
return true;
}
startWAMP方法启动WAMP:
private static boolean startWAMP() {
log("正在启动WAMP...");
log("正在等待WAMP/mysql启动...");
Socket socket = null;
try {
ProcessBuilder pr = new ProcessBuilder(sWAMP);
pr.redirectErrorStream(true);// 重定向Error流到输出流
pr.start();
} catch (Exception e) {
log(e.getMessage());
log("wamp启动失败!");
return false;
}
int floor = 2;
while (floor-- > 0) {
try {
Thread.sleep(5000);
InetAddress address = InetAddress.getByName("127.0.0.1");
socket = new Socket(address, 3306); // 建立一个Socket连接...暂定3306端口一定给mysql连接
if (socket.isConnected()) {
socket.close();
log("3306端口已连接WWWWWWWWWWWWWWWWW");
log("WAMP成功启动!");
break;
} else {
Thread.sleep(1000);
}
} catch (Exception e) {
log("startWAMP异常:" + e.getMessage());
log("3306端口未连接");
return false;
}
}
return true;
}
createPrivateDB方法连接mysql,创建数据库、数据库表和存储过程信息:
private static boolean createPrivateDB(String dbName) {
log("正在创建DB...");
String mysqlCommand = "-e source " + sAutoImportSqlDir + SQL_AutoImportDBPrivate;
List<String> restoreCmds = new ArrayList<>();
restoreCmds.add("mysql");
restoreCmds.add("--login-path=root");
restoreCmds.add("--database=nbr");
restoreCmds.add(mysqlCommand);
ProcessBuilder pb = new ProcessBuilder(restoreCmds);
try {
Process process = pb.start();
String sInfoOutput = getProcessStream(process.getInputStream());
String sErrorOuput = getProcessStream(process.getErrorStream());
if (sErrorOuput.length() > 0) {
log(SQL_AutoImportDBPrivate + " 执行错误,错误信息为:" + sErrorOuput);
log("@.@ 创建DB失败!!!!");
log("请检查错误信息,以进行修正。");
return false;
} else {
log(SQL_AutoImportDBPrivate + " 执行成功");
if (sInfoOutput.length() > 0) {
log("进程输出:" + sInfoOutput);
}
}
if (process.waitFor() != 0) {
log("进程没有正常结束!");
return false;
}
} catch (Exception e) {
log("createPrivateDB异常:" + e.getMessage());
return false;
}
try {
FileWriter fw = new FileWriter(sLastCreatedPrivateDBList, true);
String sqlDropDB = "Drop database if exists " + dbName + ";";
fw.write(sqlDropDB);
fw.flush();
fw.close();
} catch (IOException e) {
log("生成删除DB的SQL脚本(为了小王子运行时能够自动删除创建的私有DB)失败,信息:" + e.getMessage());// 这个不是严重错误,所以不需要return false;
}
return true;
}
3、Java如何创建Window服务。
创建一个Java项目,并且创建上面提到的Queen类,两个关键方法是StopService和StartService,是启动和关闭服务必须的。我们可以在StartService方法启动一个线程,执行我们需要的任务。然后将项目导出成一个jar包:
下载JavaService.exe的程序,可以帮助我们根据jar包创建Windows服务。
新建bat文件,编写批处理命令创建Windows服务:
set DirOfQueen=D:/QueenService/
echo Queen项目所在的位置:%DirOfQueen%
echo 正在卸载Queen服务...
%DirOfQueen%Bin/JavaService.exe -uninstall QueenService
echo 正在安装Queen服务...
%DirOfQueen%Bin/JavaService.exe -install QueenService "%JAVA_HOME%/jre/bin/server/jvm.dll" -Xmx128m -Djava.class.path="%JAVA_HOME%/lib/tools.jar;%DirOfQueen%Bin/QueenService.jar" -start com.bx.queen.Queen -method StartService -stop com.bx.queen.Queen -method StopService -out "%DirOfQueen%Log/QsInfo.log" -err "%DirOfQueen%Log/QsError.log" -current "%DirOfQueen%Bin/" -auto
pause
这样就可以在服务看到QueenService了,配置好登录账号和密码,就可以进行启动和关闭服务了。