深入Java日記——自己寫一個ORM框架(1)
阿新 • • 發佈:2019-02-17
眾所周知,ORM框架有很多,例如Hibernate,MyBatis,還有BeetlSQL等等,裡面獲取有很多我們不需要的功能,本系列部落格主要教大家如何寫一個簡單的ORM框架
這個ORM框架主要有以下功能:
1. 生成JavaBean程式碼
2. 通過JavaBean來實現增刪查改
我們這次先講如何生成JavaBean程式碼
主要有以下幾個步驟:
1. 獲取資料庫連線
2. 獲取表的資訊
3. 將資料庫的型別轉為Java型別
4. 生成程式碼檔案
1.獲取資料庫連線
先寫配置檔案
#資料庫驅動
driver=com.mysql.jdbc.Driver
#資料庫url
url=jdbc\:mysql\://localhost\:3306/shiro
#資料庫使用者名稱
user=root
#資料庫密碼
pwd=root
#使用資料庫型別
usingDB=mysql
#專案src地址
srcPath=/home/xjk/IdeaProjects/SORM/src
#生成JavaBean包名
poPackage=com.jk.po
我們還需要一個配置資訊的管理類
public class Configuration {
private String driver;
private String url;
private String user;
private String pwd;
private String usingDB;
private String srcPath;
private String poPackage;
public Configuration() {
}
/**
* 省略setter和getter
*/
}
然後需要一個數據庫管理類,來使用配置資訊生成資料庫連線
private static Configuration conf;
static {
Properties properties=new Properties();
try {
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
conf=new Configuration();
conf.setDriver(properties.getProperty("driver"));
conf.setPoPackage(properties.getProperty("poPackage"));
conf.setSrcPath(properties.getProperty("srcPath"));
conf.setPwd(properties.getProperty("pwd"));
conf.setUser(properties.getProperty("user"));
conf.setUrl(properties.getProperty("url"));
conf.setUsingDB(properties.getProperty("usingDB"));
}
public static Connection getConn(){
try {
//要求JVM查詢並載入指定的資料庫驅動
Class.forName(conf.getDriver());
return DriverManager.getConnection(conf.getUrl(),conf.getUser(),conf.getPwd());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static Configuration getConf(){
return conf;
}
}
2.獲取表的資訊
我們使用TableContext來裝載所有表,使用T**ableInfo**來裝載每個表的資訊,ColumnInfo來裝載每個欄位的資訊,我們先從最小單位開始
ColumnInfo
public class ColumnInfo {
/**
* 欄位名
*/
String name;
/**
* 欄位的資料型別
*/
String dataType;
/**
* 欄位的鍵型別(0:普通鍵,1:主鍵,2:外來鍵)
*/
int keyType;
public ColumnInfo() {
}
/**
* 省略setter和getter
*/
}
TableInfo
public class TableInfo {
/**
* 表名
*/
private String tname;
/**
* 所有欄位資訊
*/
private Map<String,ColumnInfo> columns;
/**
* 唯一主鍵(目前我們只能處理表中只有一個主鍵的情況)
*/
private ColumnInfo onlyPriKey;
/**
* 聯合主鍵
*/
private List<ColumnInfo> priKeys;
public TableInfo() {
}
/**
* 省略setter和getter
* /
}
TableContext
public class TableContext {
/**
* 表名為key,表結構為value
*/
public static Map<String,TableInfo> tables=new HashMap<>();
private TableContext(){}
static {
try {
Connection con=DBManager.getConn();
DatabaseMetaData dbmd=con.getMetaData();
//獲取所有表名
ResultSet tableRet=dbmd.getTables(null,"%","%",new String[]{"TABLE"});
while (tableRet.next()){
String tableName= (String) tableRet.getObject("TABLE_NAME");
TableInfo ti=new TableInfo(tableName,new HashMap<String,ColumnInfo>(),new ArrayList<ColumnInfo>());
tables.put(tableName,ti);
//獲取多個欄位和型別
ResultSet set=dbmd.getColumns(null,"%",tableName,"%");
while (set.next()){
ColumnInfo ci=new ColumnInfo(set.getString("COLUMN_NAME"),set.getString("TYPE_NAME"),0);
ti.getColumns().put(set.getString("COLUMN_NAME"),ci);
}
//獲取多個主鍵
ResultSet set2=dbmd.getPrimaryKeys(null,"%",tableName);
while (set2.next()){
String columnName=set2.getString("COLUMN_NAME");
System.out.println(columnName);
ColumnInfo ci2=ti.getColumns().get(columnName);
ci2.setKeyType(1);//設為主鍵
ti.getPriKeys().add(ci2);
}
if (ti.getPriKeys().size()>0){//取唯一主鍵,如果是聯合主鍵,則為空
ti.setOnlyPriKey(ti.getPriKeys().get(0));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.將資料庫的型別轉為Java型別
為了適配多種資料庫,我們先定義一個型別轉換器的介面
public interface TypeConvertor {
/**
* 將資料庫型別轉換為java資料型別
* @param column 資料庫欄位的型別
* @return java的資料型別
*/
public String databaseType2JavaType(String column);
/**
* 將java資料型別轉換為資料庫型別
* @param column java的資料型別
* @return 資料庫欄位的型別
*/
public String javaType2DatabaseType(String column);
}
我們這裡只展示MySQL型別轉換器的寫法
public class MySQLTypeConvertor implements TypeConvertor{
@Override
public String databaseType2JavaType(String column) {
switch (column.toLowerCase()){
case "varchar":
case "char":return "String";
case "smallint":
case "int":
case "tinyint":return "Integer";
case "bigint":return "Long";
case "double":return "Double";
case "float":return "Double";
case "clob":return "java.sql.Clob";
case "blob":return "java.sql.Blob";
case "date":return "java.sql.Date";
case "time":return "java.sql.Time";
case "timestamp":return "java.sql.Timestamp";
default:return null;
}
}
@Override
public String javaType2DatabaseType(String column) {
//這裡後面再完善
return null;
}
}
4.生成程式碼檔案
我們需要用一個JavaFieldGetSet來裝載MySQL每個欄位對應的屬性還有setter和getter
public class JavaFieldGetSet {
/**
* 屬性原始碼資訊,如:private int id;
*/
private String fieldInfo;
/**
* get方法的原始碼資訊,如:public int getId();
*/
private String getInfo;
/**
* set方法的原始碼資訊,如:public void setId(int id);
*/
private String setInfo;
public JavaFieldGetSet() {
}
/**
* 省略setter和getter
* /
}
在轉換的時候我們還要把名字格式從下劃線分隔轉為小駝峰,我們需要一個字元安轉換工具
public class StringUtils {
/**
* 將下劃線轉為大駝峰
* @param str 目標字串
* @return 變為大駝峰的字串
*/
public static String underlineToBigCamel(String str){
return underlineToSmallCamel(str.toUpperCase().substring(0,1)+str.substring(1));
}
/**
* 將下劃線轉為小駝峰
* @param str 目標字串
* @return 變為小駝峰的字串
*/
public static String underlineToSmallCamel(String str){
if (str==null||"".equals(str.trim())){
return "";
}
int len=str.length();
StringBuilder sb=new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c=str.charAt(i);
if (c=='_'){
if (++i<len){
sb.append(Character.toUpperCase(str.charAt(i)));
}
}else{
sb.append(c);
}
}
return sb.toString();
}
}
程式碼生成的工具
public class JavaFileUtils {
/**
* 根據欄位資訊生成java屬性資訊,如:var username-->private String username;相應的set和get方法原始碼
* @param column 欄位資訊
* @param convertor 型別轉換器
* @return java屬性的set/get方法
*/
public static JavaFieldGetSet createFieldGetSetSRC(ColumnInfo column, TypeConvertor convertor){
JavaFieldGetSet jfgs=new JavaFieldGetSet();
//將欄位轉為java屬性
String javaFiledType= convertor.databaseType2JavaType(column.getDataType());
String colunmName=StringUtils.underlineToSmallCamel(column.getName());
//生成欄位資訊
jfgs.setFieldInfo("\tprivate "+javaFiledType+" "+colunmName+";\n");
//生成getter
StringBuilder getSrc=new StringBuilder();
getSrc.append("\tpublic "+javaFiledType+" get"+StringUtils.underlineToBigCamel(column.getName())+"(){\n");
getSrc.append("\t\treturn "+colunmName+";\n");
getSrc.append("\t}\n");
jfgs.setGetInfo(getSrc.toString());
//生成setter
StringBuilder setSrc=new StringBuilder();
setSrc.append("\tpublic void set"+StringUtils.underlineToBigCamel(column.getName())+"("+javaFiledType+" "+colunmName+"){\n");
setSrc.append("\t\tthis."+colunmName+"="+colunmName+";\n");
setSrc.append("\t}\n");
jfgs.setSetInfo(setSrc.toString());
return jfgs;
}
/**
* 根據表資訊生成java原始碼
* @param tableInfo 表資訊
* @param convertor 資料型別轉換器
* @return java類的原始碼
*/
public static String createJavaSrc(TableInfo tableInfo,TypeConvertor convertor){
Map<String,ColumnInfo>columns=tableInfo.getColumns();
List<JavaFieldGetSet> javaFields=new ArrayList<>();
for (ColumnInfo columnInfo:columns.values()){
JavaFieldGetSet javaFieldGetSet=createFieldGetSetSRC(columnInfo,convertor);
javaFields.add(javaFieldGetSet);
}
StringBuilder src=new StringBuilder();
//生成package語句
src.append("package "+ DBManager.getConf().getPoPackage()+";\n");
//生成import語句
src.append("import java.sql.*;\n");
src.append("import java.util.*;\n\n");
//生成類宣告語句
src.append("public class "+StringUtils.underlineToBigCamel(tableInfo.getTname())+"{\n");
//生成屬性列表
for (JavaFieldGetSet javaFieldGetSet:javaFields){
src.append(javaFieldGetSet.getFieldInfo());
}
src.append("\n\n");
//生成get方法列表
for (JavaFieldGetSet javaFieldGetSet:javaFields){
src.append(javaFieldGetSet.getGetInfo());
}
src.append("\n\n");
//生成set方法列表
for (JavaFieldGetSet javaFieldGetSet:javaFields){
src.append(javaFieldGetSet.getSetInfo());
}
src.append("\n\n");
//生成結束符
src.append("}");
return src.toString();
}
/**
* 生成java檔案
* @param tableInfo 表資訊
* @param convertor 型別轉換器
*/
public static void createJavaPoFile(TableInfo tableInfo,TypeConvertor convertor){
//獲取原始碼
String src=createJavaSrc(tableInfo,convertor);
String srcPath=DBManager.getConf().getSrcPath()+"/";
//將包名轉換為檔名,然後和srcPath拼接
String packagePath=DBManager.getConf().getPoPackage().replace(".","/");
File f=new File(srcPath+packagePath);
if (!f.exists()){
f.mkdirs();
}
BufferedWriter bw=null;
try {
//將原始碼寫入檔案
bw=new BufferedWriter(new FileWriter(f.getAbsoluteFile()+"/"+StringUtils.underlineToBigCamel(tableInfo.getTname())+".java"));
bw.write(src);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bw!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 更新表結構
*/
public static void updateJavaPOFile(){
Map<String,TableInfo>tables=TableContext.tables;
for(TableInfo tableInfo:tables.values()){
createJavaPoFile(tableInfo,new MySQLTypeConvertor());
}
}
}
當我們呼叫JavaFileUtils的updateJavaPOFile方法時,便會在相應的目錄生成該資料庫裡所有表物件的JavaBean