gu.sql2java.BaseEmbeddedInitializer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sql2java-manager Show documentation
Show all versions of sql2java-manager Show documentation
sql2java manager class package for accessing database
package gu.sql2java;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import gu.sql2java.Constant.JdbcProperty;
import static com.google.common.base.Throwables.throwIfInstanceOf;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static gu.sql2java.Constant.JdbcProperty.*;
import static gu.sql2java.SimpleLog.log;
/**
* 嵌入式数据库初始化管理对象基类
* @author guyadong
*
*/
public abstract class BaseEmbeddedInitializer implements Closeable{
static final int DEFAULT_BACKUP_INTERVAL = 300;
protected final boolean runInMemory;
protected int backupIntervalSeconds = DEFAULT_BACKUP_INTERVAL;
/**
* 数据库所在的文件夹
*/
protected final File dbroot;
/**
* 数据库位置(文件或文件夹)
*/
protected final File db;
protected final Properties dbprops = new Properties();
/**
* 数据库持久化侦听器容器
*/
private final InterfaceContainer backuphooks = new InterfaceContainer(BackupHook.class);
private final URL createSql;
private long lastBackupBeginTimeMills = 0L;
private long lastBackupEndTimeMills = 0L;
private volatile boolean opened = false;
/**
* 构造方法
* @param db 数据库位置
* @param createSql 数据库建表语句SQL文件位置,数据库存在时可为{@code null},当数据库文件不存在时就用到.
* @param runInMemory
*/
protected BaseEmbeddedInitializer(File db, URL createSql, boolean runInMemory) {
try {
this.db = checkNotNull(db,"db is null").getCanonicalFile();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
checkArgument(this.db.exists() || null != createSql ,"createSql must not be null if %s not exists",this.db );
this.createSql = createSql;
this.dbroot = this.db.getParentFile() ;
dbroot.mkdirs();
this.runInMemory = runInMemory;
}
/**
* 将数据库回写到磁盘指定的目录
*/
protected abstract void doPersist() ;
/**
* 写入数据连接参数
* @param dbprops
*/
protected abstract void writeDbProps(Properties dbprops);
/**
* 检查已经存在的数据库是否可用,不可用抛出{@link EmbeddedInitException}异常
* @param db 数据库位置
* @throws EmbeddedInitException 初始化异常
*/
protected abstract void checkExistsDatabse(File db) throws EmbeddedInitException;
/**
* 子类可重写此方法实现内存运行时从磁盘文件恢复数据到内存
*/
protected void doInitMemory(){}
/**
* 子类可重写此方法执行对SQL语句的归一化处理
* @param runner
* @return always runner
*/
protected ScriptRunner normalize(ScriptRunner runner){
return runner;
}
/**
* 子类可重写此方法返回需要附加执行的SQL语句(每行一条语句),如果没有返回空表,不可返回{@code null}
* @param runner
* @throws SQLException
*/
protected List afterCreateTable(ScriptRunner runner) throws SQLException{
return Collections.emptyList();
}
/**
* 初始化数据库
* @return 当前对象
* @throws EmbeddedInitException
*/
public synchronized BaseEmbeddedInitializer init() throws EmbeddedInitException {
if(!opened){
try{
log("database location:[{}]", this.db.getAbsolutePath());
this.createManagerInstance();
if (this.db.exists()) {
checkExistsDatabse(db);
if(runInMemory){
doInitMemory();
}
} else {
// 如果没找到数据库文件,就新建数据库
log("Initializing database {}...", db);
ScriptRunner runner = new ScriptRunner(false, true)
.setClearComment(true)
.setAlias(dbprops.getProperty(JdbcProperty.ALIAS.key));
normalize(runner).runScript(createSql.openStream());
List sqls = afterCreateTable(runner);
if(!sqls.isEmpty()){
runner.runScript(sqls);
}
}
if(runInMemory){
startBackuper();
}
}catch (Exception e) {
throwIfInstanceOf(e, EmbeddedInitException.class);
throw new EmbeddedInitException(e);
}
opened = true;
}
return this;
}
@Override
public synchronized void close() {
if(opened){
persist();
opened = false;
}
}
public InterfaceContainer getBackuphookContainer() {
return backuphooks;
}
/**
* @return 备份间隔时间(秒)
*/
public int getBackupIntervalSeconds() {
return backupIntervalSeconds;
}
/**
* 设置备份间隔时间(秒)
* @param backupIntervalSeconds 小于等于0忽略
* @return 当前对象
*/
public BaseEmbeddedInitializer setBackupIntervalSeconds(int backupIntervalSeconds) {
if(backupIntervalSeconds > 0){
this.backupIntervalSeconds = backupIntervalSeconds;
}
return this;
}
public BaseEmbeddedInitializer addProperties(Properties properties) {
if(null != properties ){
for( String propertyName : properties.stringPropertyNames()){
dbprops.setProperty(propertyName, properties.getProperty(propertyName));
}
}
return this;
}
/**
* 将数据库回写到磁盘指定的目录
*/
private synchronized void persist() {
lastBackupBeginTimeMills = System.currentTimeMillis();
try {
this.backuphooks.container.onPersistDB();
if (this.runInMemory) {
log("wirte in-memory database to {}", db.getAbsolutePath());
doPersist();
log("write back success");
}
} catch (Exception e) {
log(e.getMessage(), e);
} finally {
lastBackupEndTimeMills = System.currentTimeMillis();
}
}
/**
* 根据数据库配置参数创建数据库连接实例
*/
private void createManagerInstance() {
writeDbProps(dbprops);
checkState(null != dbprops.getProperty(JDBC_URL.key),"%s not defined",JDBC_URL.key);
// 创建数据库连接实例
Managers.createInstance(dbprops);
}
/**
* 启动备份线程 定期将内存中的数据回写到数据库进行备份 当derby数据库在内存运行时,还要进行derby数据库备份
*
* @see #persist()
*/
private void startBackuper() {
Thread t = new Thread("edb-backuper") {
@Override
public void run() {
long intervalMills = TimeUnit.MILLISECONDS.convert(backupIntervalSeconds, TimeUnit.SECONDS);
log("backup thread start ,interval(s) :{}", backupIntervalSeconds);
try {
//通过线程中断状态来决定是否结束循环
while (true) {
Thread.sleep(intervalMills);
// 通过lastBackupBeginTimeMills是否小于 lastBackupEndTimeMills,来判断止次备份是否结束
if (lastBackupBeginTimeMills < lastBackupEndTimeMills) {// 正常备份
if ((System.currentTimeMillis() - lastBackupEndTimeMills) >= intervalMills)
persist();
} else if (0 == lastBackupBeginTimeMills && lastBackupBeginTimeMills == lastBackupEndTimeMills)// 第一次备份
persist();
else
// 上次备份还没结束
log("SKIP backup because of too short interval");
}
} catch (InterruptedException e) {
} finally {
log("{} end (结束)",Thread.currentThread().getName());
}
}
};
t.setDaemon(true);
t.start();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((db == null) ? 0 : db.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof BaseEmbeddedInitializer))
return false;
BaseEmbeddedInitializer other = (BaseEmbeddedInitializer) obj;
if (db == null) {
if (other.db != null)
return false;
} else if (!db.equals(other.db))
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getClass().getSimpleName() + " [runInMemory=");
builder.append(runInMemory);
builder.append(", opened=");
builder.append(opened);
builder.append(", db=");
builder.append(db);
builder.append(", dbprops=");
builder.append(dbprops);
builder.append(", createSql=");
builder.append(createSql);
builder.append("]");
return builder.toString();
}
private static final Map dbs = Maps.newHashMap();
/**
* 数据库初始化
* 反射方式实现构造子类对象并执行初始化({@link #init()}),如果之前已经创建过{@code db}指定的数据库实例,则返回已经创建的实例
* @param target 要构造的子类
* @param db 数据文件位置
* @param createSql 数据库建表语句(SQL)位置
* @param runInMemory 为{@code true}以内存方式运行
* @param properties 附加的配置参数
* @return T instance
*/
@SuppressWarnings("unchecked")
protected static synchronized T init(Classtarget, File db, URL createSql, boolean runInMemory, Properties properties){
try {
if(dbs.containsKey(db)){
return (T) dbs.get(db);
}
final T initializer = target.getConstructor(File.class, URL.class, boolean.class).newInstance(db, createSql, runInMemory);
initializer.addProperties(properties).init();
// JVM结束时自动执行关闭数据库
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
initializer.close();
}
}, initializer.getClass().getSimpleName() + "-closer"));
return initializer;
} catch (InvocationTargetException e) {
Throwables.throwIfUnchecked(e.getTargetException());
throw new RuntimeException(e.getTargetException());
} catch ( Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
/**
* 数据库初始化
* @param db 数据文件位置(File)
* @param createSqlURL 数据库建表语句(SQL)位置(URL)
* @param runInMemory 为{@code true}以内存方式运行
* @param properties 附加的配置参数
* @return T instance
*/
protected static T init(Classtarget, String db, String createSqlURL, boolean runInMemory, Properties properties){
try {
return init(target, new File(db), new URL(createSqlURL), runInMemory, properties);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}