Java实现程序运行时创建指定数据库

在我们的产品中,一个公司对应着一个数据库。除了名称不同,这些数据库有着相同的存储过程和数据库表信息。所以我们可以将创建存储过程和数据库表的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包:

将项目导出成一个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了,配置好登录账号和密码,就可以进行启动和关闭服务了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值