關於高淇JAVA中SORM總結學習筆記詳細個人解釋
程式碼來源於高淇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; } } }
&n