代碼來源於高淇JAVA教學視頻 謝謝高淇老師的教學。
因為自己在學習的過程中發現了很多困難點,總結下希望對自己接下來學框架提升。給像我一樣得初學者方便。
SORM框架是一個簡單的ORM,關系對象映射,可以通過這個框架方便的更改和操作一些數據庫的東西,在框架運行的時候也會根據數據庫中的表生成相應的Javabean在po包下。通過直接對po包下的操作在運用query中的一些操作就可以實現對數據庫的操作。思路會在代碼中注釋。
QueryFactory直接生產出Query給用的人調用,Query作為一個接口是更好的讓多種數據庫提供各自的操作。實現mySQLQuery或者OracleQuery,conventor是個轉換不同語言數據類型的工具
TableContext就是核心的程序連接數據庫表的類。DBManager鍾放的是一些配置資源文件和一些加載處理。
明確主要思路,就是通過factory制作一個query,然后直接set方法然后將對象傳入query的方法實現功能。查詢的話也是通過queryrows方法傳入select語句進行查詢,因為查詢語句的多樣性簡單的框架沒有進一步封裝。
先從最底層的tablecontext和DBManage說起:
解釋會在代碼中標識
public class DBManager { private static Configuration conf; //這是在bean包下定義的一個類用來把從配置文件提取出來方便用get set private static List<Connection> pool=new ArrayList<>(); //這是連接池的定義,我在老師的基礎上做了些更改,我把連接池全部放到了這個類里 static{ //靜態代碼塊初始化資源文件中的數據, 注意靜態塊是和類一起加載的,大量用靜態影響內存 Properties pro=new Properties(); //這個類是用來從資源文件中提取數據的類 try { pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties")); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } conf=new Configuration(); conf.setDriver(pro.getProperty("driver")); conf.setPoPackage(pro.getProperty("poPackage")); conf.setPwd(pro.getProperty("pwd")); conf.setScrPath(pro.getProperty("srcPath")); conf.setUser(pro.getProperty("user")); conf.setUrl(pro.getProperty("url")); conf.setUsingDB(pro.getProperty("usingDB")); conf.setQueryClass(pro.getProperty("queryClass")); conf.setMAX_POOL(Integer.parseInt(pro.getProperty("max_pool"))); conf.setMIN_POOL(Integer.parseInt(pro.getProperty("min_pool"))); System.out.println(TableContext.class);//加載類 //這是用來加載表信息的,用於表連接也可以用反射來加載,但是要trycatch選擇輸出加載 initPool(); //加載連接池 } public static void initPool(){ //連接池的初始化 if(pool==null){ pool=new ArrayList<Connection>(); } while(pool.size()<conf.getMIN_POOL()){ pool.add(DBManager.createConn()); //初始化 } } public static Connection getConn(){ //連接池取連接 int last_index=pool.size()-1; Connection conn=pool.get(last_index); pool.remove(last_index); return conn; } public static void CloseConn(Connection conn){ //連接池關閉 if(pool.size()>conf.getMAX_POOL()){ try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else { pool.add(conn); } } public static Connection createConn(){ try { //真正的建立一個連接 Class.forName(conf.getDriver()); return DriverManager.getConnection(conf.getUrl(),conf.getUser(),conf.getPwd()); //直接建立連接 } catch (Exception e) { // TODO: handle exception e.printStackTrace(); return null; } } //get用於配置信息的哪個類,很好理解 public static Configuration getConf(){ return conf; } //之后的關閉就不說了,主要是要記得把關閉改變成從線程池中假關閉 public static void close(ResultSet rs,Statement ps,Connection conn){ try { if(rs!=null){ rs.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(ps!=null){ ps.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(conn!=null){ CloseConn(conn); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void close(Statement ps,Connection conn){ try { if(ps!=null){ ps.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(conn!=null){ CloseConn(conn); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
然后現在有配置文件和連接了
接下來是表連接tablecontect
/** * * 管理數據庫所有表結構和類結構的關系,並可以根據表結構生成類結構 * @author NodeRed * */ public class TableContext { //最核心的 可以根據表生成類。 /** * 表名為key,信息對對象為value */ public static Map<String,TableInfo> tables=new HashMap<>(); //這個表中的tableInfo也是定義的一個類大概意思就是能夠記錄下整張表,這里這個map就能記錄整個database
/** * 將po的class對象和表關聯 */ public static Map<Class, TableInfo> poClassTableMap=new HashMap<>(); //這個表用來記錄這個表后來會產生哪個類,這后面會說 private TableContext(){}; //無參構造很重要 static{ try { Connection con=DBManager.getConn(); //這里就獲得了和數據庫的連接了 DatabaseMetaData dbmd=con.getMetaData(); //這里原視頻中沒有說,大概是 可以從連接中搜索獲取一些元數據,%是類似所有 ResultSet tableRet=dbmd.getTables(null, "%", "%",new String[]{"TABLE"}); //TABLE那就是要查詢的就是表了,但是這里出現了問題,意外的搜索出了創建表的時間 System.out.println(tableRet); //記錄的那個表,捯飭了半天也沒解決。 然后之后是放到了一個結果集里 while(tableRet.next()){ //遍歷查找出來的結果 String tableName=(String)tableRet.getObject("TABLE_NAME"); //獲得表名 TableInfo ti=new TableInfo(tableName, new ArrayList<ColumnInFo>(), // 然后把表名先把上說的可以記錄下整個表的new出來,具體的后面再說 new HashMap<String,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()){ //這里加載主鍵 ColumnInFo ci2=(ColumnInFo)ti.getColumns().get(set2.getObject("COLUMN_NAME")); ci2.setKeyType(1); ti.getPriKeys().add(ci2); } if(ti.getPriKeys().size()>0){ ti.setOnlyPrivate(ti.getPriKeys().get(0)); } } //如果這里沒有懂得話,不要急,后面結合javabean //這里就可以理解為,你在java中建立了一個表格,不是完全填好數據得表格,只是單純得有每個數據類型在哪個表是不是主鍵得表示
有了這個標識以后可以在query中找出數據庫要操作得是哪個,再從query中操作數據庫。 } catch (Exception e) { // TODO: handle exception } updataJavaPOFile(); //因為我們要再數據庫中操作,程序事先是不知道你表中得每一項叫什么名字就沒辦法根據數據可定義好類,在用類來操作數據庫 loadPOTables(); //這里我們就可以根據數據庫中獲取的表框架,獲取表的名字,表中字段類型,生成這個數據庫的java類
//然后是每次加載這個類的時候更新 } //一下是實現方法 public static void updataJavaPOFile(){ Map<String,TableInfo> map= TableContext.tables; 這里就通過這個tables表然后用后面的一個java類實現了在項目中構造java類 // TableInfo t=map.get("new_table"); for(TableInfo t:map.values()){ JavaFileUtils.createJavaPOFile(t, new MysqlTypeConventor()); } } public static void loadPOTables(){ // Class c=Class.forName("com.cy.sorm.New_table"); // poClassTableMap.put(c, TableInfo); //這里就是用反射把這個類和產生自哪個表放在了一個表里,在后面的操作有用 for(TableInfo tableInfo:tables.values()){ try{ Class c=Class.forName(DBManager.getConf().getPoPackage()+ "."+StringUTils.firstChar2UpCase(tableInfo.getTname())); poClassTableMap.put(c, tableInfo); }catch(Exception e){ e.printStackTrace(); } } } // public static void main(String[] args) { // Map<String, TableInfo> tables=TableContext.tables; // System.out.println(tables); // } }
這里也是orm的核心了,從數據庫中復制了一個映射表過來。但是老師說在工作中用的很少。接下來說一下字段映射和表映射吧,可以從以上的javabean包中可以看出,除了很容易理解的配置configuration,javasetget也就是在上面說的拼接我們自己產生的java類的簡單結構。
private String name; private String Datatype; private int keyType;
字段就是在mysql建表時候的每一列
private String tname; /** * key 字段名 value字段類 */ private Map<String,ColumnInFo> columns; private ColumnInFo onlyPrivate; private List<ColumnInFo> priKeys;
這個表呢也就可以記錄下整個表了 map中也就是整個表,記錄下主鍵 很好理解這樣我們只要有足夠多的數據傳入我們就可以在java中改了整張表,但是我們必去得做一個query類,用來我把這個數據類傳入就可以在數據庫中同步更改。
然后大概思路也就成型了,用戶可以提供數據庫 ,我自動給他生成他表中得類,他調用query中方法,對數據庫進行更改。但是怎么通過我生成類對這個映射相聯系,然后再映射到數據庫呢,我們藏了一個map。
先說一點簡單得把tables中獲得得數據進行生成java類
這里實現了一個接口,可以處理不同數據庫到java得翻譯數據類型這里很好懂
public class MysqlTypeConventor implements TypeConvertor{ @Override public String databaseType2JavaType(String columnType) { if("varchar".equalsIgnoreCase(columnType)||"char".equalsIgnoreCase(columnType)){ return "String"; }else if("int".equalsIgnoreCase(columnType) ||"tinyint".equalsIgnoreCase(columnType)){ return "Integer"; }else if("double".equalsIgnoreCase(columnType)){ return "double"; }else if("timestamp".equalsIgnoreCase(columnType)){ return "Timestamp"; } return null; } @Override public String javaType2databaseType(String javaDataType) { // TODO Auto-generated method stub return null; } }
然后我們把轉換器和傳進去的字段封裝成一個java生成類的工具,就是上面代碼提到的哪個工具類
public class JavaFileUtils { public static JavaFieldGetSet createFieldGetSetSRC(ColumnInFo column,TypeConvertor convertor){ JavaFieldGetSet jfgs= new JavaFieldGetSet(); String javaFieldType = convertor.databaseType2JavaType(column.getDatatype()); jfgs.setFieldInfo("\tprivate "+javaFieldType+" "+column.getName()); StringBuilder getSrc=new StringBuilder(); getSrc.append("\tpublic "+javaFieldType+" get"+StringUTils.firstChar2UpCase(column.getName())+"(){\n"); getSrc.append("\t\treturn "+column.getName()+";\n"); getSrc.append("\t}\n"); jfgs.setGetInfo(getSrc.toString()); StringBuilder setSrc=new StringBuilder(); setSrc.append("\tpublic void set"+StringUTils.firstChar2UpCase(column.getName())+"("); setSrc.append(javaFieldType+" "+column.getName()+"){\n"); setSrc.append("\t\tthis."+column.getName()+"="+column.getName()+";\n"); setSrc.append("\t}\n"); jfgs.setSetInfo(setSrc.toString()); return jfgs; } public static String createJavaSrc(TableInfo tableInfo,TypeConvertor convertor){ Map<String,ColumnInFo> columns=tableInfo.getColumns();//表裝入map List<JavaFieldGetSet> javaFields=new ArrayList<>(); for(ColumnInFo c:columns.values()){ javaFields.add(createFieldGetSetSRC(c, convertor)); } StringBuilder src=new StringBuilder(); src.append("package "+DBManager.getConf().getPoPackage()+";\n\n"); src.append("import java.util.*;\n\n"); src.append("import java.sql.*;\n\n"); src.append("public class "+StringUTils.firstChar2UpCase(tableInfo.getTname())+"{\n\n"); for(JavaFieldGetSet f:javaFields){ src.append(f.getFieldInfo()+";\n"); } src.append("\n\n"); for(JavaFieldGetSet f:javaFields){ src.append(f.getSetInfo()); } src.append("\n"); for(JavaFieldGetSet f:javaFields){ src.append(f.getGetInfo()); } src.append("}\n"); // System.out.println(src); return src.toString(); } public static void createJavaPOFile(TableInfo tableInfo,TypeConvertor convertor){ String src=createJavaSrc(tableInfo, convertor); BufferedWriter bw=null; String srcPath=DBManager.getConf().getScrPath()+"\\"; String packagePath=DBManager.getConf().getPoPackage().replaceAll("\\.", "\\\\"); File f=new File(srcPath+packagePath.trim()); System.out.println(f); if(!f.exists()){ f.mkdirs(); } try { bw=new BufferedWriter(new FileWriter(f.getAbsoluteFile()+"\\"+StringUTils.firstChar2UpCase(tableInfo.getTname())+".java")); bw.write(src); } catch (Exception e) { // TODO: handle exception }finally { if(bw!=null){ try { bw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static void main(String[] args) { // ColumnInFo ci=new ColumnInFo("username","varchar",0); // JavaFieldGetSet f=createFieldGetSetSRC(ci, new MysqlTypeConventor()); // System.out.println(f); Map<String,TableInfo> map= TableContext.tables; // TableInfo t=map.get("new_table"); for(TableInfo t:map.values()){ JavaFileUtils.createJavaPOFile(t, new MysqlTypeConventor()); } } }
第一個方法就是把從字段類中獲取的名字類型轉換成一個String,這里沒有表名所以拼接了的是getset,便於拼接是用stringbuilder拼接,
第二個方法有了表映射以后我們就可以拼接表名,然后從表映射中獲取有那些字段,再拼接上字段的setget方法,最后呢就是通過配置文件中獲得我們項目路徑,然后建立java類
這里注意把“ . ”換成" \\ "的操作java中一個變兩個
然后就是核心的實現query了,這里本來是想實現接口 然后方便不同的數據庫連接不同的,然后后來因為方法重用的很多就改成了抽象類。
public abstract class Query { public List executeQueryTemplate(String sql, Object[] params,Class clazz,CallBack back){ Connection conn=DBManager.getConn(); PreparedStatement ps=null; ResultSet rs=null; try { ps=conn.prepareStatement(sql); JDBCUtils.handleParams(ps, params); //這個是一個回調模板,通過callback,callback是一個簡單的接口,很常用,實現的作用就是,我實現一個接口,接口中是我要實現的功能 rs=ps.executeQuery(); //然后我可以接着寫下去,這成為了一個模板,只是中間這個方法的實現不一樣,當我要使用這個模板的時候調用這個方法,然后匿名內部類實現back return back.doExecute(conn, ps, rs); //中的方法但是匿名內部類又涉及到用外部變量 final的問題,我用過的也很少希望在之后的學習有更深的理解。 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; }finally { DBManager.close(rs,ps,conn); } } public int excuteDML(String sql,Object[] params){ Connection conn=DBManager.getConn(); int count=0; //這是個執行sql語句的封裝,object參數是傳入要操作的數因為用的是preparestatement PreparedStatement ps=null; try { ps=conn.prepareStatement(sql); JDBCUtils.handleParams(ps, params); count=ps.executeUpdate(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ DBManager.close(ps,conn); } return count; } public void insert(Object obj){ Class c=obj.getClass(); List<Object> params=new ArrayList<>();//儲存sql的參數對象 int countNotNull=0;//計算不為空的屬性值 TableInfo tableInfo = TableContext.poClassTableMap.get(c);//////////////////這就是map記錄了class和映射table的關系,然后返回映射表就可以獲得表名拼接sql語句了 StringBuilder sql=new StringBuilder("insert into "+tableInfo.getTname()+" ("); Field[] fs=c.getDeclaredFields(); //利用反射將對象的的名字和操作值然后拼接字符串 for(Field f:fs){ String fieldName=f.getName(); Object fieldValue=ReflectUtils.invokeGet(fieldName, obj); //這里是利用反射 獲取get方法 get到我們設置的值 然后拼接到sql語句中,
//這樣一來最開始的思路也解決了 if(fieldValue!=null){ countNotNull++; sql.append(fieldName+","); params.add(fieldValue); } } sql.setCharAt(sql.length()-1, ')'); sql.append(" value ("); for(int i=0;i<countNotNull;i++){ sql.append("?,"); } sql.setCharAt(sql.length()-1, ')'); excuteDML(sql.toString(), params.toArray()); } public void delete(Class clazz,Object id){ /** * table.class->delete from New_table where id=2; */ TableInfo tableInfo=TableContext.poClassTableMap.get(clazz); ColumnInFo onlyPriKey=tableInfo.getOnlyPrivate(); //mysql刪除宇語句中我們只要獲取表主鍵的編號就可以刪除了 String sql="delete from "+tableInfo.getTname()+" where "+onlyPriKey.getName()+"=? "; excuteDML(sql, new Object[]{id}); } public void delete(Object obj){ Class c=obj.getClass(); TableInfo tableInfo=TableContext.poClassTableMap.get(c); //同理反射獲得get方法 然后用第一個delete方法刪除 ColumnInFo onlyPriKey= tableInfo.getOnlyPrivate(); Object priKeyValue=ReflectUtils.invokeGet(onlyPriKey.getName(), obj); delete(c,priKeyValue); } public int update(Object obj,String[] fieldNames){ //obj("uname",pwd)--->update 表名 set uname=?,pwd=? where id =? Class c=obj.getClass(); List<Object> params=new ArrayList<>(); TableInfo tableInfo=TableContext.poClassTableMap.get(c); ColumnInFo priKey =tableInfo.getOnlyPrivate(); StringBuilder sql=new StringBuilder("update "+tableInfo.getTname()+" set "); for(String fname:fieldNames){ Object fvalue=ReflectUtils.invokeGet(fname, obj); params.add(fvalue); sql.append(fname+"=?,"); //和insert很像加了遍歷要更新的列 然后list記錄問好處要更新的值 反射獲取值 } sql.setCharAt(sql.length()-1, ' '); sql.append(" where "); sql.append(priKey.getName()+"=? "); params.add(ReflectUtils.invokeGet(priKey.getName(), obj)); return excuteDML(sql.toString(), params.toArray()); } //返回多行 public List queryRows(String sql, Class clazz, Object[] params){ return executeQueryTemplate(sql,params,clazz,new CallBack(){ ///接上回調方法的實現 public List doExecute(Connection conn, PreparedStatement ps, ResultSet rs) { List list=null; try { ResultSetMetaData metaData=rs.getMetaData(); while(rs.next()){ if(list==null){ list=new ArrayList(); } Object rowObj=clazz.newInstance();//調用javabeen的無參構造器 // select username,pwd,age from user where id>? and age<18 for(int i=0;i<metaData.getColumnCount();i++){ //getcolumnCount為查詢要差的列如 username pwd age String columnName=metaData.getColumnLabel(i+1); Object columnValue=rs.getObject(i+1); ReflectUtils.invokeSet(rowObj, columnName, columnValue); } list.add(rowObj); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } return list; } }); } //返回一行 public Object queryUniqueRows(String sql, Class clazz,Object[] params){ List list=queryRows(sql, clazz, params); return (list==null&&list.size()>0)? null:list.get(0); } public Object queryValue(String sql,Object[] params){ Connection conn=DBManager.getConn(); Object value=null; PreparedStatement ps=null; ResultSet rs=null; try { ps=conn.prepareStatement(sql); JDBCUtils.handleParams(ps, params); rs=ps.executeQuery(); // rs中為查到的行數 while(rs.next()){ value=rs.getObject(1); //rs結果集中不是以0開頭的 } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return value; } public Number queryNumber(String sql,Object[] params){ return (Number)queryValue(sql, params); } public abstract Object queryPagenate(int PageNum,int size); //抽象方法,不同子類不同實現 }
之后就可以繼承query實現不同的數據庫的不同方法然后重寫和修改實現多態
然后實現了query后就可以工廠設計模式,用factory單例,然后生產query
public class QueryFactory { private static QueryFactory factory=new QueryFactory(); private static Class c; static{ try { c=Class.forName(DBManager.getConf().getQueryClass()); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private QueryFactory() { // TODO Auto-generated constructor stub } public Query creatFactory(){ try { return (Query) c.newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } }