一个需求
在实际生产开发中,数据库初始化、升级是没办法规避的,一般常见的方案是外挂一套初始化脚本加一堆SQL文件,其实可以把这个过程做到系统里,做到一个程序包内自带数据库的初始化,或者数据库升级,所以需求就是做一个数据库的初始化、升级的java功能,使用过flyway的同学应该能更明白这个需求,简单点说就是手搓一个flyway。
需求的分析与实现
因为这里主要是介绍设计模式,关于这个需求里制作SDK包或者Spring-starter不在这里复述,这里只针对需求的核心部分——调度SQL完成初始化。
根据需求,我们进一步拆解,该功能其实需要两部分,初始化/升级逻辑和预制的SQL文件。
逻辑梳理完毕,用最直接的方式实现,创建一个专门的流程类:
@Slf4j
public class InitDataBase implements InitDataBase {
public Connection commConn = null;
public Connection dbConn = null;
public JSONArray dbConfigFile = new JSONArray();
public DataBaseProperties dataBaseProperties;
public static Boolean flag = false;
public InitDataBase(DataBaseProperties dataBaseProperties,){
this.dataBaseProperties = dataBaseProperties;
}
public boolean isInitEd() {
return flag;
}
public void startCoreJob() throws SQLException {
//读取配置文件和SQL文件信息
reloadConfigFile();
//建立JDBC链接
initCommConn();
//验证链接是否正常
if(testConnection()){
Map<String,String> sqlcontent;
//验证数据库实例是否存在
if(databaseIsExitd(commConn)) {
initDbConn();
//是否存在版本表
if(!exitDbVersionTable(dbConn)){
createDbVersionTable();
}
//比对代码配置中所需数据库版本是否大于当前数据库中实际版本
if(getLatestVersion() > getCurrenDbVersion()) {
sqlcontent = getSqlFromFile(getCurrenDbVersion());
}else {
flag = true;
return;
}
}else{
//创建数据库实例
if(createDataBase()){
initDbConn();
createDbVersionTable();
}
sqlcontent = getSqlFromFile(0f);
}
//执行SQL
flag = excuteSQL(sqlcontent);
}else{
log.error("建立链接失败!");
}
close();
}
/**
* 初始化数据库公共连接
*/
public void initCommConn(){
try {
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:mysql://" + dataBaseProperties.getHost() + ":" + dataBaseProperties.getDbport() + "/mysql?characterEncoding=utf8&serverTimezone=GMT%2B8";
commConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getRootUser(), dataBaseProperties.getRootPass());
}catch (Exception e){
e.printStackTrace()
}
}
/**
* 初始化对应数据库连接
*/
public void initDbConn(){
try{
String jdbcUrl = "jdbc:mysql://"+ dataBaseProperties.getHost()+":"+ dataBaseProperties.getDbport()+"/"+ dataBaseProperties.getDbName()+"?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2B8";
dbConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getUsername(), dataBaseProperties.getPassword());
}catch (Exception e){
e.printStackTrace()
}
}
/**
* 验证数据库实例/模式是否存在
*/
public boolean databaseIsExitd(Connection connection){
try {
Statement stmt = connection.createStatement();
ResultSet res = stmt.executeQuery("SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name= \""+ dataBaseProperties.getDbName()+"\"");
if(res.next() && res.getInt(1) == 0){
stmt.close();
return false;
}
return true;
}catch (Exception e){
e.printStackTrace()
}
return false;
}
/**
* 建立指定数据库链接
*/
public void initDbConn(){
try {
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:mysql://" + dataBaseProperties.getHost() + ":" + dataBaseProperties.getDbport() + "/"+dataBaseProperties.getDbName()+"?characterEncoding=utf8&serverTimezone=GMT%2B8";
commConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getRootUser(), dataBaseProperties.getRootPass());
}catch (Exception e){
e.printStackTrace()
}
}
/**
* 数据库链接是否正常
*/
public boolean testConnection() {
return null != commConn;
}
/**
* 获取程序配置中最新版本号
* @return Float
*/
private Float getLatestVersion() {
int item = dbConfigFile.size()-1;
JSONObject json = dbConfigFile.getJSONObject(item);
return Float.parseFloat(json.getStr("version"));
}
public Float getCurrenDbVersion(){
float version = 0f;
//读取数据库中存放版本的表中的版本号
return version;
}
public boolean excuteSQL(Map<String,String> sqlcontent) throws SQLException {
// 批量执行sqlcontent中的SQL语句
return false;
}
public boolean createDataBase() {
//创建数据库实例
return true;
}
public void updateDbVersion(Object version) throws SQLException {
//更新版本表中的版本号state.execute("UPDATE "+conscriptProperties.getTableName()+" SET version = "+version+" , create_time = '"+ DateUtil.now()+"'");
state.close();
}
/**
* 确认版本表是否存在
* @param connection 连接通道
* @return Boolean
*/
private Boolean exitDbVersionTable(Connection connection) throws SQLException {
//确认版本表是否存在
}
public void close() throws SQLException{
if(null != commConn){
commConn.close();
commConn = null;
}
if(null != dbConn){
dbConn.close();
dbConn = null;
}
}
/**
* 创建版本库表
*/
private void createDbVersionTable(){
//创建版本表
}
/**
* 根据版本号获取需要执行的sql文件
* @param ver 数据库内版本号
* @return Map<String,String>
*/
private Map<String,String> getSqlFromFile(Float ver) {
//根据版本号,获取需要执行的SQL文件集合
}
/**
* 加载数据库升级相关配置
*/
private void reloadConfigFile() {
String content = readFileContent(dataBaseProperties.getDbConfigFileUrl());
if(null != content) {
dbConfigFile = JSONUtil.parseArray(content);
}
}
}
接下来需求扩展,不同现场的数据库服务不同,有的使用Mysql,有的使用国产数据库、有的使用Oracle数据库……
为了响应需求的改变,开始分析不同类型数据库的差异,例如,建立JDBC的方式不同,数据库实例、模式概念的差异,权限的差异,SQL语句的差异等,与之对应的原本的逻辑就不得不进行修改调整,逻辑就变成了
针对此需求,最简单粗暴的就是再新建一个类,来实现新的数据库类型:
@Slf4j
public class InitDataBaseForKingBase implements InitDataBase {
public Connection commConn = null;
public Connection dbConn = null;
public JSONArray dbConfigFile = new JSONArray();
public DataBaseProperties dataBaseProperties;
public InitDataBaseForKingBase(DataBaseProperties dataBaseProperties,){
this.dataBaseProperties = dataBaseProperties;
}
public void startCoreJob() throws SQLException {
//读取配置文件和SQL文件信息
reloadConfigFile();
//建立JDBC链接
initCommConn();
//验证链接是否正常
if(testConnection()){
Map<String,String> sqlcontent;
//验证数据库模式是否存在
if(schemaIsExitd(commConn)) {
initDbConn();
//是否存在版本表
if(!exitDbVersionTable(dbConn)){
createDbVersionTable();
}
//比对代码配置中所需数据库版本是否大于当前数据库中实际版本
if(getLatestVersion() > getCurrenDbVersion()) {
sqlcontent = getSqlFromFile(getCurrenDbVersion());
}else {
flag = true;
return;
}
}else{
//创建数据库实例
if(createDataBase()){
initDbConn();
createDbVersionTable();
}
sqlcontent = getSqlFromFile(0f);
}
//执行SQL
flag = excuteSQL(sqlcontent);
}else{
log.error("建立链接失败!");
}
close();
}
/**
* 初始化数据库公共连接
*/
public void initCommConn(){
try{
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:kingbase8://" + dataBaseProperties.getHost() + ":"+dataBaseProperties.getDbport();
commConn = DriverManager.getConnection(jdbcUrl,dataBaseProperties.getRootUser(),dataBaseProperties.getRootPass());
commConn.setAutoCommit(true);
}catch (Exception e){
e.printStackTrace()
}
}
/**
* 初始化对应数据库连接
*/
public void initDbConn(){
try{
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:kingbase8://" + dataBaseProperties.getHost() + ":"+dataBaseProperties.getDbport()+"/"+dataBaseProperties.getDbName()+"??currentSchema="+dataBaseProperties.getSchema()+"&characterEncoding=utf-8";
dbConn = DriverManager.getConnection(jdbcUrl,dataBaseProperties.getUsername(),dataBaseProperties.getPassword());
dbConn.setAutoCommit(true);
}catch (Exception e){
log.error("【Conscript】Database initialization :data base is not connection.....");
}
}
/**
* 验证数据库实例/模式是否存在
*/
public boolean schemaIsExitd(Connection connection){
try {
Statement stmt = connection.createStatement();
ResultSet res = stmt.executeQuery("SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name= \""+ dataBaseProperties.getDbName()+"\"");
if(res.next() && res.getInt(1) == 0){
stmt.close();
return false;
}
return true;
}catch (Exception e){
e.printStackTrace()
}
return false;
}
/**
* 建立指定数据库链接
*/
public void initDbConn(){
try {
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:mysql://" + dataBaseProperties.getHost() + ":" + dataBaseProperties.getDbport() + "/"+dataBaseProperties.getDbName()+"?characterEncoding=utf8&serverTimezone=GMT%2B8";
commConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getRootUser(), dataBaseProperties.getRootPass());
}catch (Exception e){
e.printStackTrace()
}
}
/**
* 数据库链接是否正常
*/
public boolean testConnection() {
return null != commConn;
}
/**
* 获取程序配置中最新版本号
* @return Float
*/
private Float getLatestVersion() {
int item = dbConfigFile.size()-1;
JSONObject json = dbConfigFile.getJSONObject(item);
return Float.parseFloat(json.getStr("version"));
}
public Float getCurrenDbVersion(){
float version = 0f;
//读取数据库中存放版本的表中的版本号
return version;
}
public boolean excuteSQL(Map<String,String> sqlcontent) throws SQLException {
// 批量执行sqlcontent中的SQL语句
return false;
}
public boolean createDataBase() {
try {
Statement stmt = commConn.createStatement();
//这里的创建数据库语句和MYSQL有不同
stmt.execute("CREATE DATABASE "+dataBaseProperties.getDbName()+" OWNER "+dataBaseProperties.getUsername()+" encoding 'utf8' CONNECTION LIMIT 10");
stmt.execute(this.createDataBaseSQL());
stmt.close();
}catch (Exception e){
return false;
}
return true;
}
public void updateDbVersion(Object version) throws SQLException {
//更新版本表中的版本号state.execute("UPDATE "+conscriptProperties.getTableName()+" SET version = "+version+" , create_time = '"+ DateUtil.now()+"'");
state.close();
}
/**
* 确认版本表是否存在
* @param connection 连接通道
* @return Boolean
*/
private Boolean exitDbVersionTable(Connection connection) throws SQLException {
//确认版本表是否存在
}
public void close() throws SQLException{
if(null != commConn){
commConn.close();
commConn = null;
}
if(null != dbConn){
dbConn.close();
dbConn = null;
}
}
/**
* 创建版本库表
*/
private void createDbVersionTable(){
//创建版本表
}
/**
* 根据版本号获取需要执行的sql文件
* @param ver 数据库内版本号
* @return Map<String,String>
*/
private Map<String,String> getSqlFromFile(Float ver) {
//根据版本号,获取需要执行的SQL文件集合
}
/**
* 加载数据库升级相关配置
*/
private void reloadConfigFile() {
String content = readFileContent(dataBaseProperties.getDbConfigFileUrl());
if(null != content) {
dbConfigFile = JSONUtil.parseArray(content);
}
}
}
又或者不在乎开闭原则,直接采用面向过程的方式垒逻辑,对关键差异的地方进行if else:
/**
* 初始化数据库公共连接
*/
public void initCommConn(){
if("mysql".equals(dataBaseProperties.getDbType())){
try {
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:mysql://" + dataBaseProperties.getHost() + ":" + dataBaseProperties.getDbport() + "/mysql?characterEncoding=utf8&serverTimezone=GMT%2B8";
commConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getRootUser(), dataBaseProperties.getRootPass());
}catch (Exception e){
e.printStackTrace()
}
}else if("dm".equals(dataBaseProperties.getDbType())) {
//对应的类型链接
}
}
/**
* 初始化对应数据库连接
*/
public void initDbConn(){
if("mysql".equals(dataBaseProperties.getDbType())){
try{
String jdbcUrl = "jdbc:mysql://"+ dataBaseProperties.getHost()+":"+ dataBaseProperties.getDbport()+"/"+ dataBaseProperties.getDbName()+"?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2B8";
dbConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getUsername(), dataBaseProperties.getPassword());
}catch (Exception e){
e.printStackTrace()
}
}else if("dm".equals(dataBaseProperties.getDbType())) {
//对应的类型链接
}
}
/**
* 验证数据库实例/模式是否存在
*/
public boolean databaseIsExitd(Connection connection){
if("mysql".equals(dataBaseProperties.getDbType())){
try {
Statement stmt = connection.createStatement();
ResultSet res = stmt.executeQuery("SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name= \""+ dataBaseProperties.getDbName()+"\"");
if(res.next() && res.getInt(1) == 0){
stmt.close();
return false;
}
return true;
}catch (Exception e){
e.printStackTrace()
}
}else if("dm".equals(dataBaseProperties.getDbType())) {
//对应的类型链接
}
return false;
}
/**
* 建立指定数据库链接
*/
public void initDbConn(){
if("mysql".equals(dataBaseProperties.getDbType())){
try {
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:mysql://" + dataBaseProperties.getHost() + ":" + dataBaseProperties.getDbport() + "/"+dataBaseProperties.getDbName()+"?characterEncoding=utf8&serverTimezone=GMT%2B8";
commConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getRootUser(), dataBaseProperties.getRootPass());
}catch (Exception e){
e.printStackTrace()
}
}else if("dm".equals(dataBaseProperties.getDbType())) {
//对应的类型链接
}
}
其实不论哪种实现方式,都在代码量和扩展性或者设计原则上有问题:
新开一个类,虽然不违背开闭原则,但是会发现两种数据库初始化太相似了,现在完全分开,当作两个独立的类来实现,如果以后需要扩展其他能力,比如对SQL进行语法校验,那么两个类都需要再次修改,这是很麻烦的。
而if else的写法不用多说,违背开闭原则、代码臃肿、维护性降低,随着需求的越来越多,if else 会以熵增式的暴涨,最后沦为依托答辩。
模板模式
模板模式的定义:定义一个操作中的算法的骨架,而将一些步骤延展到子类中,模板方法使得子类可以不改变一个算法的结构就可以重定义该算法的某些特定步骤。
下面是模板模式的类图结构:
AbstractClass:抽象类,用来定义算法骨架和原始操作,由子类通过重新定义这些原始操作来实现一个算法的各个步骤。类中还可以提供算法中通用的实现
ConcreteClass: 具体实现类,用来实现算法骨架中的某些步骤,完成特定子类相关功能
代码样例:
//携带算法骨架的抽象父类
public abstract class AbstractClass{
//操作流程1
abstract void startJob();
//操作流程2
abstract void endJob();
public void runJob(){
//定义算法流程
startJob();
endJob();
}
public void commOp(){
//公共操作具体代码
}
}
//具体子类
public class ConcreteClass extend AbstractClass {
public void startJob(){
System.out.print("操作1")
}
public void endJob(){
System.out.print("操作2")
}
}
模板模式实现数据库升级
根据需求,对于不同类型数据库来看,数据库初始化和升级流程是一致的,而差异性主要体现在
- 初始化JDBC链接
- 数据库实例判定
- 创建数据库实例
可以在父类中定义不变的流程,而差异性让子类去实现,通过模板模式建立的类图关系:
而数据库的差异性体现在:
/**
* 初始化数据库公共连接
*/
public abstract void initCommConn();
/**
* 初始化对应数据库连接
*/
public abstract void initDbConn();
/**
* 建库语句
* @return String
*/
public abstract String createDataBaseSQL();
/**
* 数据库实例是否存在
* @return String
*/
public abstract boolean databaseIsExitd(Connection connection);
将以上方法延申到子类中去执行
首先定义抽象父类,类中定义数据库初始化的核心流程骨架,以及不同类型需要做的具体操作(抽象方法):
@Slf4j
public class AbstractInitDataBase implements InitDataBase {
public Connection commConn = null;
public Connection dbConn = null;
public JSONArray dbConfigFile = new JSONArray();
public DataBaseProperties dataBaseProperties;
public AbstractInitDataBase (DataBaseProperties dataBaseProperties,){
this.dataBaseProperties = dataBaseProperties;
}
public void startCoreJob() throws SQLException {
//读取配置文件和SQL文件信息
reloadConfigFile();
//建立JDBC链接
initCommConn();
//验证链接是否正常
if(testConnection()){
Map<String,String> sqlcontent;
//验证数据库模式是否存在
if(dataBaseIsExitd(commConn)) {
initDbConn();
//是否存在版本表
if(!exitDbVersionTable(dbConn)){
createDbVersionTable();
}
//比对代码配置中所需数据库版本是否大于当前数据库中实际版本
if(getLatestVersion() > getCurrenDbVersion()) {
sqlcontent = getSqlFromFile(getCurrenDbVersion());
}else {
flag = true;
return;
}
}else{
//创建数据库实例
if(createDataBase()){
initDbConn();
createDbVersionTable();
}
sqlcontent = getSqlFromFile(0f);
}
//执行SQL
flag = excuteSQL(sqlcontent);
}else{
log.error("建立链接失败!");
}
close();
}
/**
* 定义初始化JDBC链接,交由具体的数据库类型子类去实现
*/
public abstract void initCommConn();
/**
* 定义初始化数据库连接,交由具体的子类去实现
*/
public abstract void initDbConn();
/**
* 定义数据库实例判定标准,交由具体的子类去实现
*/
public abstract boolean dataBaseIsExitd(Connection connection);
/**
* 定义初始化具体数据库连接,交由具体的子类去实现
*
*/
public abstract void initDbConn();
/**
* 数据库链接是否正常
*/
public boolean testConnection() {
return null != commConn;
}
/**
* 获取程序配置中最新版本号
* @return Float
*/
private Float getLatestVersion() {
int item = dbConfigFile.size()-1;
JSONObject json = dbConfigFile.getJSONObject(item);
return Float.parseFloat(json.getStr("version"));
}
public Float getCurrenDbVersion(){
float version = 0f;
//读取数据库中存放版本的表中的版本号
return version;
}
public boolean excuteSQL(Map<String,String> sqlcontent) throws SQLException {
// 批量执行sqlcontent中的SQL语句
return false;
}
public boolean createDataBase() {
try {
Statement stmt = commConn.createStatement();
//这里的创建数据库语句和MYSQL有不同
stmt.execute("CREATE DATABASE "+dataBaseProperties.getDbName()+" OWNER "+dataBaseProperties.getUsername()+" encoding 'utf8' CONNECTION LIMIT 10");
stmt.execute(this.createDataBaseSQL());
stmt.close();
}catch (Exception e){
return false;
}
return true;
}
public void updateDbVersion(Object version) throws SQLException {
//更新版本表中的版本号state.execute("UPDATE "+conscriptProperties.getTableName()+" SET version = "+version+" , create_time = '"+ DateUtil.now()+"'");
state.close();
}
/**
* 确认版本表是否存在
* @param connection 连接通道
* @return Boolean
*/
private Boolean exitDbVersionTable(Connection connection) throws SQLException {
//确认版本表是否存在
}
public void close() throws SQLException{
if(null != commConn){
commConn.close();
commConn = null;
}
if(null != dbConn){
dbConn.close();
dbConn = null;
}
}
//其他公用方法不变
}
再根据实际的需求,去实现不同类型数据库的子类,由子类去完成因为模式概念、SQL语法、链接语法差异性的地方:
//针对Mysql数据库的子类
@Slf4j
public class MySqlDataBase extends AbstractInitDataBase {
public MySqlDataBase(DataBaseProperties dataBaseProperties) {
super(dataBaseProperties);
}
@Override
public void initCommConn() {
try {
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:mysql://" + dataBaseProperties.getHost() + ":" + dataBaseProperties.getDbport() + "/mysql?characterEncoding=utf8&serverTimezone=GMT%2B8";
commConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getRootUser(), dataBaseProperties.getRootPass());
}catch (Exception e){
log.error("【Conscript】Database initialization :data base is not connection.....");
}
}
@Override
public void initDbConn() {
try{
String jdbcUrl = "jdbc:mysql://"+ dataBaseProperties.getHost()+":"+ dataBaseProperties.getDbport()+"/"+ dataBaseProperties.getDbName()+"?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2B8";
dbConn = DriverManager.getConnection(jdbcUrl, dataBaseProperties.getUsername(), dataBaseProperties.getPassword());
}catch (Exception e){
log.warn("");
}
}
@Override
public boolean databaseIsExitd(Connection connection){
try {
Statement stmt = connection.createStatement();
ResultSet res = stmt.executeQuery("SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name= \""+ dataBaseProperties.getDbName()+"\"");
if(res.next() && res.getInt(1) == 0){
stmt.close();
return false;
}
return true;
}catch (Exception e){
log.error("【Conscript】Database initialization :database base query is error");
}
return false;
}
@Override
public String createDataBaseSQL() {
return "CREATE DATABASE IF NOT EXISTS "+ dataBaseProperties.getDbName()+" DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci";
}
}
//kongBase的类型子类
@Slf4j
public class KingBase8DataBase extends AbstractInitDataBase {
public KingBase8DataBase(DataBaseProperties dataBaseProperties, ConscriptProperties conscriptProperties) {
super(dataBaseProperties, conscriptProperties);
}
@Override
public void initCommConn() {
try{
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:kingbase8://" + dataBaseProperties.getHost() + ":"+dataBaseProperties.getDbport()+"/TEST";
commConn = DriverManager.getConnection(jdbcUrl,dataBaseProperties.getRootUser(),dataBaseProperties.getRootPass());
commConn.setAutoCommit(true);
}catch (Exception e){
e.printStack()
}
}
@Override
public void initDbConn() {
try{
Class.forName(dataBaseProperties.getDriverClassName());
String jdbcUrl = "jdbc:kingbase8://" + dataBaseProperties.getHost() + ":"+dataBaseProperties.getDbport()+"/"+dataBaseProperties.getDbName()+"??currentSchema="+dataBaseProperties.getSchema()+"&characterEncoding=utf-8";
dbConn = DriverManager.getConnection(jdbcUrl,dataBaseProperties.getUsername(),dataBaseProperties.getPassword());
dbConn.setAutoCommit(true);
}catch (Exception e){
e.printStack()
}
}
@Override
public boolean createDataBase() {
try {
Statement stmt = commConn.createStatement();
stmt.execute("CREATE DATABASE "+dataBaseProperties.getDbName()+" OWNER "+dataBaseProperties.getUsername()+" encoding 'utf8' CONNECTION LIMIT 10");
stmt.execute(this.createDataBaseSQL());
stmt.close();
}catch (Exception e){
return false;
}
return true;
}
@Override
public String createDataBaseSQL() {
return "CREATE SCHEMA \""+dataBaseProperties.getSchema()+"\" AUTHORIZATION \""+dataBaseProperties.getUsername()+"\" ";
}
@Override
public boolean databaseIsExitd(Connection connection) {
try {
Statement stmt = connection.createStatement();
ResultSet res = stmt.executeQuery("SELECT DATNAME FROM sys_database WHERE DATNAME = '"+dataBaseProperties.getDbName()+"'");
if(!res.next()){
stmt.close();
return false;
}
return true;
}catch (Exception e){
e.printStack()
}
return false;
}
}
此后,如果再新增其他类型数据库,只需要继承AbstractInitDataBase 类,然后分别实现具体的
initCommConn、databaseIsExitd等方法即可完成扩展,如果需要进行一些通用的改变,例如对SQL文件中的SQL语句进行语法验证,则可以修改AbstractInitDataBase 类中的通用方法完成。
完整项目git代码路径:Conscript: java数据库自动初始化,项目已经实现了stater化且支持达梦、金仓、pg等数据库的兼容,有兴趣可以参考一下。
模板模式的理解
模板模式的核心要义就是固定算法框架,从而让具体的算法实现可扩展,是面向对象中继承用法的标准应用之一,在实际应用中多用在框架级功能的实现:即定义好框架流程,在适合的点让具体的研发人员去扩展。
为什么是抽象类而不是接口?
在工厂方法模式那一篇文章中我一直在说面向接口编程,而这里为什么使用抽象类而不用接口,首先抽象类相比接口,最大的特点在于可以实现具体的方法,这一点在模板的算法框架定义是必须的,单纯的使用接口,那只是定义原子操作,而无法定义流程骨架之类的内容。另一方面,把模板类定义为抽象类,还可以给所有子类提供公共方法,在约束子类的前提下,又进一步的限定了子类的职责和操作范围。
程序设计的一个重要平衡点就是“变与不变”,也就是分析功能中哪些是变化的,哪些是不变的,把不变的部分进行公共实现或者用接口来封装隔离,把变化的分离出去,交给子类,以类文件的形式进行扩展,规避去修改已有类;
模板模式很好的体现了“不要找我们,我们会联系你”,作为父类的模板会在需要的时候,调用子类相应方法,也就是由父类来找子类,而不是让子类来找父类;这其实也是一种反向控制结构,按正常思路是子类找父类才对。也就是应该子类来调用父类的方法,因为父类根本不知道子类,而子类是知道父类的。但是模板模式种是父类根据约束来调度子类的,所以是一种反向控制。其理论依据就是Java的动态绑定采用的是“后期绑定”技术,对于出现子类覆盖父类的方法的情况,在编译时是看数据类型,运行时则是看实际的对象类型,即:new 谁就调用谁的方法。模板模式用的数据类型是模板类型,但是在创建实例的时候是子类的实例。在调用的时候,会被动态的绑定到子类方法上,从而实现反向控制。
Java种典型的模板模式应用:排序
在实际Java中,也存在模板应用的影子,基于Comparator的集合排序大家一定使用过,例如:
public class User {
private int id;
private String name;
private int age;
// User类的构造函数、getter和setter省略
}
public class Main {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
// 填充User列表
users.add(new User(1, "Alice", 25));
users.add(new User(2, "Bob", 30));
users.add(new User(3, "Charlie", 20));
// 使用Lambda表达式对User列表按年龄排序
Collections.sort(users, (u1, u2) -> Integer.compare(u1.getAge(), u2.getAge()));
// 打印排序后的列表
for (User user : users) {
System.out.println(user.getName() + " - " + user.getAge());
}
}
}
通过Lambda表达式进行集合的排序,单看这里的语句,可能看不到模板模式的影子,那把这段Lambda表达式的实现原理拆解一下:
public class Main {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
// 填充User列表
users.add(new User(1, "Alice", 25));
users.add(new User(2, "Bob", 30));
users.add(new User(3, "Charlie", 20));
// Lambda表达式的翻译即使用内部类实现Comparator接口
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return Integer.compare(u1.getAge(), u2.getAge());
}
});
// 打印排序后的列表
for (User user : users) {
System.out.println(user.getName() + " - " + user.getAge());
}
}
}
再进一步翻译:
//将内部类以传统的JAVA实现摘出来,独立成一个类
public class UserAgeComparator implements Comparator<User> {
@Override
public int compare(User u1, User u2) {
return Integer.compare(u1.getAge(), u2.getAge());
}
}
public class Main {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
// 填充User列表
users.add(new User(1, "Alice", 25));
users.add(new User(2, "Bob", 30));
users.add(new User(3, "Charlie", 20));
// 使用自定义的比较器对User列表按年龄排序
Collections.sort(users, new UserAgeComparator());
// 打印排序后的列表
for (User user : users) {
System.out.println(user.getName() + " - " + user.getAge());
}
}
}
看排序会发现,列表究竟依赖什么标准来排序,完全是依赖于Comparator的具体实现,也就是说,排序的算法步骤是固定的,只是进行排序比较这一操作存在差异性,把这一步骤交给子类或者说外部去自行定义,从而实现灵活的排序扩展,这就是策略模式的体现。