手寫開源ORM框架介紹
簡介
前段時間利用空閒時間,參照mybatis的基本思路手寫了一個ORM框架。一直沒有時間去補充相應的文件,現在正好抽時間去整理下。通過思路歷程和程式碼註釋,一方面重溫下知識,另一方面準備後期去完善這個框架。
傳統JDBC連線
參照傳統的JDBC連線資料庫過程如下,框架所做的事情就是把這些步驟進行封裝。
// 1. 註冊 JDBC 驅動 Class.forName(JDBC_DRIVER); // 2. 開啟連結 conn = DriverManager.getConnection(DB_URL,USER,PASS); // 3. 例項化statement stmt = conn.prepareStatement(sql); // 4. 填充資料 stmt.setString(1,id); // 5. 執行Sql連線 ResultSet rs = stmt.executeQuery(); // 6. 結果查詢 while(rs.next()){ // 7. 通過欄位檢索 int id = rs.getInt("id"); String name = rs.getString("name"); String url = rs.getString("url"); }
思路歷程
上述是執行Java連線資料庫通用語句,ORM框架就是物件關係對映框架。我們需要做的重點是步驟4和7,將物件欄位填充至資料庫,將資料庫的內容取出作為物件返回。瞭解了基本的方法後,開始動手寫一個框架了。
資料庫驅動
開始,需要構建一個驅動註冊類,這個類主要用來對資料庫驅動進行構造。詳見程式碼塊1
程式碼塊1
資料庫驅動類(com.simple.ibatis.dirver)
/** * @Author xiabing * @Desc 驅動註冊中心,對資料庫驅動進行註冊的地方 **/ public class DriverRegister { /* * mysql的driver類 * */ private static final String MYSQLDRIVER = "com.mysql.jdbc.Driver"; /* * 構建driver快取,儲存已經註冊了的driver的型別 * */ private static final Map<String,Driver> registerDrivers = new ConcurrentHashMap<>(); /* * 初始化,此處是將DriverManager中已經註冊了驅動放入自己快取中。當然,你也可以在這個方法內註冊 常見的資料庫驅動,這樣後續就可以直接使用,不用自己手動註冊了。 * */ static { Enumeration<Driver> driverEnumeration = DriverManager.getDrivers(); while (driverEnumeration.hasMoreElements()){ Driver driver = driverEnumeration.nextElement(); registerDrivers.put(driver.getClass().getName(),driver); } } /* * 載入mysql驅動,此個方法可以寫在靜態程式碼塊內,代表專案啟動時即註冊mysql驅動 * */ public void loadMySql(){ if(! registerDrivers.containsKey(MYSQLDRIVER)){ loadDriver(MYSQLDRIVER); } } /* * 載入資料庫驅動通用方法,並註冊到registerDrivers快取中,此處是註冊資料庫驅動的方法 * */ public void loadDriver(String driverName){ Class<?> driverType; try { // 註冊驅動,返回驅動類 driverType = Class.forName(driverName); // 將驅動例項放入驅動快取中 registerDrivers.put(driverType.getName(),(Driver)driverType.newInstance()); }catch (ClassNotFoundException e){ throw new RuntimeException(e); }catch (Exception e){ throw new RuntimeException(e); } } }
驅動註冊後,需要建立資料庫連線。但是框架如果一個請求建一個連線,那木勢必耗費大量的資源。所以該框架引入池化的概念,對連線進行池化管理。詳情見程式碼2
程式碼塊2
資料來源類(com.simple.ibatis.datasource)
PoolConnection
/** * @Author xiabing * @Desc 連線代理類,是對Connection的一個封裝。除了提供基本的連線外,還想記錄 這個連線的連線時間,因為有的連線如果一直連線不釋放,那木我可以通過檢視 這個連線已連線的時間,如果超時了,我可以主動釋放。 **/ public class PoolConnection { // 真實的資料庫連線 public Connection connection; // 資料開始連線時間 private Long CheckOutTime; // 連線的hashCode private int hashCode = 0; public PoolConnection(Connection connection) { this.connection = connection; this.hashCode = connection.hashCode(); } public Long getCheckOutTime() { return CheckOutTime; } public void setCheckOutTime(Long checkOutTime) { CheckOutTime = checkOutTime; } // 判斷兩個PoolConnection物件是否相等,其實是判斷其中真實連線的hashCode是否相等 @Override public boolean equals(Object obj) { if(obj instanceof PoolConnection){ return connection.hashCode() == ((PoolConnection) obj).connection.hashCode(); }else if(obj instanceof Connection){ return obj.hashCode() == hashCode; }else { return false; } } @Override public int hashCode() { return hashCode; } }
NormalDataSource
/** * @Author xiabing * @Desc 普通資料來源,這個資料來源是用來產生資料庫連線的。來一個請求就會建立一個資料庫連線,沒有池化的概念 **/ public class NormalDataSource implements DataSource{ // 驅動名稱 private String driverClassName; // 資料庫連線URL private String url; // 資料庫使用者名稱 private String userName; // 資料庫密碼 private String passWord; // 驅動註冊中心 private DriverRegister driverRegister = new DriverRegister(); public NormalDataSource(String driverClassName, String url, String userName, String passWord) { // 初始化時將驅動進行註冊 this.driverRegister.loadDriver(driverClassName); this.driverClassName = driverClassName; this.url = url; this.userName = userName; this.passWord = passWord; } // 獲取資料庫連線 @Override public Connection getConnection() throws SQLException { return DriverManager.getConnection(url,userName,passWord); } // 移除資料庫連線,此方法沒有真正移除,只是將連線中未提交的事務進行回滾操作。 public void removeConnection(Connection connection) throws SQLException{ if(!connection.getAutoCommit()){ connection.rollback(); } } // 獲取資料庫連線 @Override public Connection getConnection(String username, String password) throws SQLException { return DriverManager.getConnection(url,username,password); } // 後續方法因為沒有用到,所有沒有進行重寫了 @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } }
PoolDataSource
/** * @Author xiabing * @Desc 池化執行緒池,對連線進行管理 **/ public class PoolDataSource implements DataSource{ private Integer maxActiveConnectCount = 10; // 最大活躍執行緒數,此處可根據實際情況自行設定 private Integer maxIdleConnectCount = 10; // 最大空閒執行緒數,此處可根據實際情況自行設定 private Long maxConnectTime = 30*1000L; // 連線最長使用時間,在自定義連線中配置了連線開始時間,在這裡來判斷該連線是否超時 private Integer waitTime = 2000; // 執行緒wait等待時間 private NormalDataSource normalDataSource; // 使用normalDataSource來產生連線 private Queue<PoolConnection> activeConList = new LinkedList<>(); // 存放活躍連線的佇列 private Queue<PoolConnection> idleConList = new LinkedList<>(); // 存放空閒連線的佇列 public PoolDataSource(String driverClassName, String url, String userName, String passWord) { this(driverClassName,url,userName,passWord,10,10); } public PoolDataSource(String driverClassName, String url, String userName, String passWord,Integer maxActiveConnectCount,Integer maxIdleConnectCount) { // 初始化normalDataSource,因為normalDataSource已經封裝了新建連線的方法 this.normalDataSource = new NormalDataSource(driverClassName,url,userName,passWord); this.maxActiveConnectCount = maxActiveConnectCount; this.maxIdleConnectCount = maxIdleConnectCount; } /** * @Desc 獲取連線時先從空閒連線列表中獲取。若沒有,則判斷現在活躍連線是否已超過設定的最大活躍連線數,沒超過,new一個 * 若超過,則判斷第一個連線是否已超時,若超時,則移除掉在新建。若未超時,則wait()等待。 **/ @Override public Connection getConnection(){ Connection connection = null; try { connection = doGetPoolConnection().connection; }catch (SQLException e){ throw new RuntimeException(e); } return connection; } public void removeConnection(Connection connection){ PoolConnection poolConnection = new PoolConnection(connection); doRemovePoolConnection(poolConnection); } private PoolConnection doGetPoolConnection() throws SQLException{ PoolConnection connection = null; while (connection == null){ // 加鎖 synchronized (this){ // 判斷是否有空閒連線 if(idleConList.size() < 1){ // 判斷活躍連線數是否已經超過預設的最大活躍連線數 if(activeConList.size() < maxActiveConnectCount){ // 如果還可以新建連線,就新建一個連線 connection = new PoolConnection(normalDataSource.getConnection()); }else { // 走到這一步,說明沒有空閒連線,並且活躍連線已經滿了,則只能看哪些連線超時,判斷第一個連線是否超時 PoolConnection poolConnection = activeConList.peek(); // 這就是我為啥要給資料庫連線加連線時間了 if(System.currentTimeMillis() - poolConnection.getCheckOutTime() > maxConnectTime){ // 若第一個連線已經超時了,移除第一個活躍連線 PoolConnection timeOutConnect = activeConList.poll(); if(!timeOutConnect.connection.getAutoCommit()){ // 如果該連線設的是非自動提交,則對事物進行回滾操作 timeOutConnect.connection.rollback(); } // 置為空,讓垃圾收集器去收集 timeOutConnect.connection.close(); timeOutConnect = null; // 新建一個連線 connection = new PoolConnection(normalDataSource.getConnection()); }else { // 走到這一步,代表所有連線都沒有超時,只能等了 try{ // 於是等一會 this.wait(waitTime); }catch (InterruptedException e){ // ignore錯誤,並退出 break; } } } }else { // 這一步代表空閒連線佇列裡面有連線,則直接取出 connection = idleConList.poll(); } if(connection != null){ // 設定這個連線的連線時間 connection.setCheckOutTime(System.currentTimeMillis()); // 然後放入活躍連線佇列中 activeConList.add(connection); } } } return connection; } // 移除連線 private void doRemovePoolConnection(PoolConnection connection){ // 加鎖 synchronized (this){ // 移除連線,這裡需要重寫PoolConnection的equal方法,因為只要真實連線相同,就可以代表這個封裝的連線相等了 activeConList.remove(connection); if(idleConList.size() < maxIdleConnectCount){ // 加入空閒連線佇列中 idleConList.add(connection); } // 通知其他等待執行緒,讓他們繼續獲取連線 this.notifyAll(); } } @Override public Connection getConnection(String username, String password) throws SQLException { return getConnection(); } // 後面的方法沒有用到就沒有重寫了 @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } }
使用資料庫連線池獲取連線後,接下來就是關鍵了,生成SQL預處理語句,並進行資料填充。這讓我想到了mybatis使用方法。
@Select("select * from users")
List<User> getUsers();
上面是mybatis基於註解的配置方式。除了註解,還支援xml。於是框架準備先使用基於註解的方式進行程式碼配置。首先,需要知道這些介面類的類名。於是提供一個配置,根據指定包名獲取包下所有類的類名詳情見程式碼3
程式碼塊3
方法類(com.simple.ibatis.util)
PackageUtil
/** * @Author xiabing * @Desc 獲取指定包下所有類的名稱 **/ public class PackageUtil { private static final String CLASS_SUFFIX = ".class"; private static final String CLASS_FILE_PREFIX = "classes" + File.separator; private static final String PACKAGE_SEPARATOR = "."; /** * 查詢包下的所有類的名字 * @param packageName * @param showChildPackageFlag 是否需要顯示子包內容 * @return List集合,內容為類的全名 */ public static List<String> getClazzName(String packageName, boolean showChildPackageFlag){ List<String> classNames = new ArrayList<>(); String suffixPath = packageName.replaceAll("\\.", "/"); // 獲取類載入器 ClassLoader loader = Thread.currentThread().getContextClassLoader(); try{ Enumeration<URL> urls = loader.getResources(suffixPath); while(urls.hasMoreElements()) { URL url = urls.nextElement(); if(url != null){ if ("file".equals(url.getProtocol())) { String path = url.getPath(); classNames.addAll(getAllClassName(new File(path),showChildPackageFlag)); } } } }catch (IOException e){ throw new RuntimeException("load resource is error , resource is "+packageName); } return classNames; } // 獲取所有類的名稱 private static List<String> getAllClassName(File file,boolean flag){ List<String> classNames = new ArrayList<>(); if(!file.exists()){ return classNames; } if(file.isFile()){ String path = file.getPath(); if(path.endsWith(CLASS_SUFFIX)){ path = path.replace(CLASS_SUFFIX,""); String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length()).replace(File.separator, PACKAGE_SEPARATOR); classNames.add(clazzName); } }else { File[] listFiles = file.listFiles(); if(listFiles != null && listFiles.length > 0){ for (File f : listFiles){ if(flag) { classNames.addAll(getAllClassName(f, flag)); }else { if(f.isFile()){ String path = f.getPath(); if(path.endsWith(CLASS_SUFFIX)) { path = path.replace(CLASS_SUFFIX, ""); String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length()).replace(File.separator,PACKAGE_SEPARATOR); classNames.add(clazzName); } } } } } } return classNames; } }
在知道介面類存放在哪些類後,我需要解析這些類中的各個方法,包括每個方法的註解,入參,和返回值。同時,我需要使用一個全域性配置類,用於存放我解析後生成的物件,並且一些資料來源配置資訊也可以放在這個全域性類中。詳情見程式碼4
程式碼塊4
核心類(com.simple.ibatis.core)
Config
/** * @Author xiabing * @Desc 框架的核心配置類 **/ public class Config { // 資料來源 private PoolDataSource dataSource; // mapper包地址(即待解析的包名),後續改為List<String>,能同時載入多個mapper包 todo private String daoSource; // mapper核心檔案(存放類解析後的物件) private MapperCore mapperCore; // 是否啟用事務 private boolean openTransaction; // 是否開啟快取 private boolean openCache; public Config(String mapperSource,PoolDataSource dataSource){ this.dataSource = dataSource; this.daoSource = mapperSource; this.mapperCore = new MapperCore(this); } // 生成SQL語句執行器 public SimpleExecutor getExecutor(){ return new SimpleExecutor(this,this.getDataSource(),openTransaction,openCache); } // 後文都是基本的get,set方法 public PoolDataSource getDataSource() { return dataSource; } public void setDataSource(PoolDataSource dataSource) { this.dataSource = dataSource; } public String getDaoSource() { return daoSource; } public void setDaoSource(String daoSource) { this.daoSource = daoSource; } public MapperCore getMapperCore() { return mapperCore; } public void setMapperCore(MapperCore mapperCore) { this.mapperCore = mapperCore; } public boolean isOpenTransaction() { return openTransaction; } public void setOpenTransaction(boolean openTransaction) { this.openTransaction = openTransaction; } public boolean isOpenCache() { return openCache; } public void setOpenCache(boolean openCache) { this.openCache = openCache; } }
SqlSource
/** * @Author xiabing5 * @Desc 將sql語句拆分為sql部分和引數部分,因為我們需要使用java物件對資料庫預處理物件進行注入。具體看下面例子 * @example select * from users where id = {user.id} and name = {user.name} * -> sql = select * from users where id = ? and name = ? * -> paramResult = {user.id,user.name} **/ public class SqlSource { /**sql語句,待輸入欄位替換成?*/ private String sql; /**待輸入欄位*/ private List<String> param; /**select update insert delete*/ private Integer executeType; public SqlSource(String sql){ this.param = new ArrayList<>(); this.sql = sqlInject(this.param,sql); } /**sql注入,對要注入的屬性,必須使用{} 包裹,並替換為? 見例子 */ private String sqlInject(List<String> paramResult, String sql){ String labelPrefix = "{"; String labelSuffix = "}"; // 將語句的所有的{}全部都解析成?,並將{xxx}中的xxx提取出來。 while (sql.indexOf(labelPrefix) > 0 && sql.indexOf(labelSuffix) > 0){ String sqlParamName = sql.substring(sql.indexOf(labelPrefix),sql.indexOf(labelSuffix)+1); sql = sql.replace(sqlParamName,"?"); paramResult.add(sqlParamName.replace("{","").replace("}","")); } return sql; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } public List<String> getParam() { return param; } public void setParam(List<String> param) { this.param = param; } public Integer getExecuteType() { return executeType; } public void setExecuteType(Integer executeType) { this.executeType = executeType; } }
MapperCore
/** * @Author xiabing5 * @Desc mapper方法解析核心類,此處是將介面中的方法進行解析的關鍵類 **/ public class MapperCore { // select方法 private static final Integer SELECT_TYPE = 1; // update方法 private static final Integer UPDATE_TYPE = 2; // delete方法 private static final Integer DELETE_TYPE = 3; // insert方法 private static final Integer INSERT_TYPE = 4; /**mapper檔案解析類快取,解析後都放在裡面,避免重複對一個mapper檔案進行解析*/ private static Map<String,MethodDetails> cacheMethodDetails = new ConcurrentHashMap<>(); /** * 全域性配置 * */ private Config config; public MapperCore(Config config){ this.config = config; load(config.getDaoSource()); } /* * 載入,解析指定包名下的類 * */ private void load(String source){ /**載入mapper包下的檔案*/ List<String> clazzNames = PackageUtil.getClazzName(source,true); try{ for(String clazz: clazzNames){ Class<?> nowClazz = java.lang.Class.forName(clazz); // 不是介面跳過,只能解析介面 if(!nowClazz.isInterface()){ continue; } /**介面上沒有@Dao跳過。我們對介面類上加了@Dao後,就代表要解析*/ boolean skip = false; Annotation[] annotations = nowClazz.getDeclaredAnnotations(); for(Annotation annotation:annotations){ // 該介面中的註釋中是否有帶@Dao if(annotation instanceof Dao) { skip = true; break; } } if(!skip){ continue; } // 呼叫反射介面,獲取所有介面中的方法 Method[] methods = nowClazz.getDeclaredMethods(); for( Method method : methods){ // 解析方法詳情 MethodDetails methodDetails = handleParameter(method); // 解析@SELECT()等註解中的SQL內容 methodDetails.setSqlSource(handleAnnotation(method)); // 解析完成後放入快取 cacheMethodDetails.put(generateStatementId(method),methodDetails); } } }catch (ClassNotFoundException e){ throw new RuntimeException(" class load error,class is not exist"); } } /** * 獲得方法詳情,從方法快取池中獲取,因為每個方法事先已經完成解析 * */ public MethodDetails getMethodDetails(Method method){ String statementId = generateStatementId(method); if(cacheMethodDetails.containsKey(statementId)){ return cacheMethodDetails.get(statementId); } return new MethodDetails(); } /** * 獲得方法對應的sql語句 * */ public SqlSource getStatement(Method method){ String statementId = generateStatementId(method); if(cacheMethodDetails.containsKey(statementId)){ return cacheMethodDetails.get(statementId).getSqlSource(); } throw new RuntimeException(method + " is not sql"); } /** * 獲得方法對應的引數名 * */ public List<String> getParameterName(Method method){ String statementId = generateStatementId(method); if(cacheMethodDetails.containsKey(statementId)){ return cacheMethodDetails.get(statementId).getParameterNames(); } return new ArrayList<>(); } /** * 獲取方法返回型別 * */ public Class getReturnType(Method method){ String statementId = generateStatementId(method); if(cacheMethodDetails.containsKey(statementId)){ return cacheMethodDetails.get(statementId).getReturnType(); } return null; } /** * 獲得方法對應的引數型別 * */ public Class<?>[] getParameterType(Method method) { String statementId = generateStatementId(method); if(cacheMethodDetails.containsKey(statementId)){ return cacheMethodDetails.get(statementId).getParameterTypes(); } return new Class<?>[]{}; } /** * 獲得方法是SELECT UPDATE DELETE INSERT * */ public Integer getMethodType(Method method){ String statementId = generateStatementId(method); if(cacheMethodDetails.containsKey(statementId)){ return cacheMethodDetails.get(statementId).getSqlSource().getExecuteType(); } return null; } /** * 獲得方法是否返回集合型別list * */ public boolean getHasSet(Method method){ String statementId = generateStatementId(method); if(cacheMethodDetails.containsKey(statementId)){ return cacheMethodDetails.get(statementId).isHasSet(); } return false; } /** * 解析方法內的註解 * */ private MethodDetails handleParameter(Method method){ MethodDetails methodDetails = new MethodDetails(); // 獲取方法輸入引數數量 int parameterCount = method.getParameterCount(); // 獲取方法輸入各引數型別 Class<?>[] parameterTypes = method.getParameterTypes(); // 獲取方法輸入各引數名稱 List<String> parameterNames = new ArrayList<>(); Parameter[] params = method.getParameters(); for(Parameter parameter:params){ parameterNames.add(parameter.getName()); } /* * 獲得方法引數的註解值替代預設值,如果使用了@Param註解,則預設使用註解中的值作為引數名 * */ for(int i = 0; i < parameterCount; i++){ parameterNames.set(i,getParamNameFromAnnotation(method,i,parameterNames.get(i))); } methodDetails.setParameterTypes(parameterTypes); methodDetails.setParameterNames(parameterNames); /** 獲取方法返回型別*/ Type methodReturnType = method.getGenericReturnType(); Class<?> methodReturnClass = method.getReturnType(); if(methodReturnType instanceof ParameterizedType){ /** 返回是集合類 目前僅支援List todo*/ if(!List.class.equals(methodReturnClass)){ throw new RuntimeException("now ibatis only support list"); } Type type = ((ParameterizedType) methodReturnType).getActualTypeArguments()[0]; /** 設定 返回集合中的物件型別 */ methodDetails.setReturnType((Class<?>) type); /** 標註該方法返回型別是集合型別 */ methodDetails.setHasSet(true); }else { /** 不是集合型別,就直接設定返回型別 */ methodDetails.setReturnType(methodReturnClass); /** 標註該方法返回型別不是集合型別 */ methodDetails.setHasSet(false); } return methodDetails; } /** * 解析@select,@update等註解,獲取SQL語句,並封裝成物件 * */ private SqlSource handleAnnotation(Method method){ SqlSource sqlSource = null; String sql = null; /** 獲取方法上所有註解 */ Annotation[] annotations = method.getDeclaredAnnotations(); for(Annotation annotation : annotations){ /** 如果有@select註解 */ if(Select.class.isInstance(annotation)){ Select selectAnnotation = (Select)annotation; sql = selectAnnotation.value(); /** 語句封裝成sqlSource物件 */ sqlSource = new SqlSource(sql); /** 設定執行語句型別 */ sqlSource.setExecuteType(SELECT_TYPE); break; }else if(Update.class.isInstance(annotation)){ Update updateAnnotation = (Update)annotation; sql = updateAnnotation.value(); sqlSource = new SqlSource(sql); sqlSource.setExecuteType(UPDATE_TYPE); break; }else if(Delete.class.isInstance(annotation)){ Delete deleteAnnotation = (Delete) annotation; sql = deleteAnnotation.value(); sqlSource = new SqlSource(sql); sqlSource.setExecuteType(DELETE_TYPE); break; }else if(Insert.class.isInstance(annotation)){ Insert insertAnnotation = (Insert) annotation; sql = insertAnnotation.value(); sqlSource = new SqlSource(sql); sqlSource.setExecuteType(INSERT_TYPE); break; } } if(sqlSource == null){ throw new RuntimeException("method annotation not null"); } return sqlSource; } /** * 獲取@Param註解內容 * */ private String getParamNameFromAnnotation(Method method, int i, String paramName) { final Object[] paramAnnos = method.getParameterAnnotations()[i]; for (Object paramAnno : paramAnnos) { if (paramAnno instanceof Param) { paramName = ((Param) paramAnno).value(); } } return paramName; } /** * 生成唯一的statementId * */ private static String generateStatementId(Method method){ return method.getDeclaringClass().getName() + "." + method.getName(); } /** * 每個mapper方法的封裝類 * */ public static class MethodDetails{ /**方法返回型別,若是集合,則代表集合的物件類,目前集合類僅支援返回List */ private Class<?> returnType; /**方法返回型別是否是集合*/ private boolean HasSet; /**執行型別,SELECT,UPDATE,DELETE,INSERT*/ private Integer executeType; /**方法輸入引數型別集合*/ private Class<?>[] parameterTypes; /**方法輸入引數名集合*/ private List<String> parameterNames; /**sql語句集合*/ private SqlSource sqlSource; public Class<?> getReturnType() { return returnType; } public void setReturnType(Class<?> returnType) { this.returnType = returnType; } public boolean isHasSet() { return HasSet; } public void setHasSet(boolean hasSet) { HasSet = hasSet; } public Integer getExecuteType() { return executeType; } public void setExecuteType(Integer executeType) { this.executeType = executeType; } public Class<?>[] getParameterTypes() { return parameterTypes; } public void setParameterTypes(Class<?>[] parameterTypes) { this.parameterTypes = parameterTypes; } public List<String> getParameterNames() { return parameterNames; } public void setParameterNames(List<String> parameterNames) { this.parameterNames = parameterNames; } public SqlSource getSqlSource() { return sqlSource; } public void setSqlSource(SqlSource sqlSource) { this.sqlSource = sqlSource; } } }
SQL語句已經解析完畢,後面開始執行SQL語句。在之前,我想給執行SQL語句時加點功能,如提供select快取,事務功能,詳情見程式碼塊5,程式碼塊6
程式碼塊5
快取類(com.simple.ibatis.cache)
Cache
/** * @author xiabing * @description: 自定義快取介面,主要提供增刪改查功能 */ public interface Cache { /**放入快取*/ void putCache(String key,Object val); /**獲取快取*/ Object getCache(String key); /**清空快取*/ void cleanCache(); /**獲取快取健數量*/ int getSize(); /**移除key的快取*/ void removeCache(String key); }
SimpleCache
/** * @author xiabing * @description: 快取的簡單實現 */ public class SimpleCache implements Cache{ /**用HashMap來實現快取的增刪改查*/ private static Map<String,Object> map = new HashMap<>(); @Override public void putCache(String key, Object val) { map.put(key,val); } @Override public Object getCache(String key) { return map.get(key); } @Override public void cleanCache() { map.clear(); } @Override public int getSize() { return map.size(); } @Override public void removeCache(String key) { map.remove(key); } }
LruCache
/** * @author xiabing * @description: 快取包裝類沒,使快取具有LRU功能 */ public class LruCache implements Cache{ /**快取大小*/ private static Integer cacheSize = 2000; /**HashMap負載因子,可以自己指定*/ private static Float loadFactory = 0.75F; /**真實快取*/ private Cache trueCache; /**使用LinkedHashMap來實現Lru功能*/ private Map<String,Object> linkedCache; /**待移除的元素*/ private static Map.Entry removeEntry; public LruCache(Cache trueCache){ this(cacheSize,loadFactory,trueCache); } public LruCache(Integer cacheSize, Float loadFactory, Cache trueCache) { this.trueCache = trueCache; /**初始化LinkedHashMap,並重寫removeEldestEntry() 方法,不瞭解的可以看我之前的部落格*/ this.linkedCache = new LinkedHashMap<String, Object>(cacheSize,loadFactory,true){ @Override protected boolean removeEldestEntry(Map.Entry eldest) { // 當快取容量超過設定的容量時,返回true,並記錄待刪除的物件 if(getSize() > cacheSize){ removeEntry = eldest; return true; } return false; } }; } // 放入快取,當待刪除的元素不為空時,就在真實快取中刪除該元素 @Override public void putCache(String key, Object val) { // 真實快取中放入 this.trueCache.putCache(key,val); // linkedHashMap中放入,此處放入會呼叫重寫的removeEldestEntry()方法 this.linkedCache.put(key,val); // 當removeEldestEntry()執行後,如果有待刪除元素,就開始執行刪除操作 if(removeEntry != null){ removeCache((String)removeEntry.getKey()); removeEntry = null; } } @Override public Object getCache(String key) { // 因為呼叫linkedHashMap中的get()方法會對連結串列進行再一次排序。見之前關於快取的部落格 linkedCache.get(key); return trueCache.getCache(key); } @Override public void cleanCache() { trueCache.cleanCache(); linkedCache.clear(); } @Override public int getSize() { return trueCache.getSize(); } @Override public void removeCache(String key) { trueCache.removeCache(key); } }
程式碼塊6
事務類(com.simple.ibatis.transaction)
Transaction
/** * @Author xiabing * @Desc 封裝事務功能 **/ public interface Transaction { /**獲取資料庫連線*/ Connection getConnection() throws SQLException; /**事務提交*/ void commit() throws SQLException; /**事務回滾*/ void rollback() throws SQLException; /**關閉連線*/ void close() throws SQLException; }
SimpleTransaction
/** * @Author xiabing * @Desc 事務的簡單實現,封裝了資料庫的事務功能 **/ public class SimpleTransaction implements Transaction{ private Connection connection; // 資料庫連線 private PoolDataSource dataSource; // 資料來源 private Integer level = Connection.TRANSACTION_REPEATABLE_READ; // 事務隔離級別 private Boolean autoCommmit = true; // 是否自動提交 public SimpleTransaction(PoolDataSource dataSource){ this(dataSource,null,null); } public SimpleTransaction(PoolDataSource dataSource, Integer level, Boolean autoCommmit) { this.dataSource = dataSource; if(level != null){ this.level = level; } if(autoCommmit != null){ this.autoCommmit = autoCommmit; } } /**獲取資料庫連線,並設定該連線的事務隔離級別,是否自動提交*/ @Override public Connection getConnection() throws SQLException{ if(connection != null){ return connection; } this.connection = dataSource.getConnection(); this.connection.setAutoCommit(autoCommmit); this.connection.setTransactionIsolation(level); return this.connection; } @Override public void commit() throws SQLException{ if(this.connection != null){ this.connection.commit(); } } @Override public void rollback() throws SQLException{ if(this.connection != null){ this.connection.rollback(); } } /**關閉連線*/ @Override public void close() throws SQLException{ /**如果該連線設定自動連線為false,則在關閉前進行事務回滾一下*/ if(autoCommmit != null && !autoCommmit){ connection.rollback(); } if(connection != null){ dataSource.removeConnection(connection); } this.connection = null; } }
TransactionFactory
/** * @Author xiabing * @Desc 事務工廠,封裝獲取事務的功能 **/ public class TransactionFactory { public static SimpleTransaction newTransaction(PoolDataSource poolDataSource, Integer level, Boolean autoCommmit){ return new SimpleTransaction(poolDataSource,level,autoCommmit); } }
程式碼真正執行SQL的地方,見程式碼塊7
程式碼塊7
語句執行類(com.simple.ibatis.execute)
Executor
/** * @Author xiabing * @Desc 執行介面類,後續執行器需要實現此介面 **/ public interface Executor { /**獲取Mapper介面代理類,使用了動態代理技術,見實現類分析*/ <T> T getMapper(Class<T> type); void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
SimpleExecutor
/** * @Author xiabing * @Desc 簡單執行器類,對於Sql的執行和處理都依賴此類 **/ public class SimpleExecutor implements Executor{ /**全域性配置類*/ public Config config; /**mapper核心類,解析mapper介面類*/ public MapperCore mapperCore; /**資料庫源*/ public PoolDataSource poolDataSource; /**事務*/ public Transaction transaction; /**快取*/ public Cache cache; public SimpleExecutor(Config config,PoolDataSource poolDataSource){ this(config,poolDataSource,false,false); } public SimpleExecutor(Config config,PoolDataSource poolDataSource,boolean openTransaction,boolean openCache){ this.config = config; this.mapperCore = config.getMapperCore(); this.poolDataSource = poolDataSource; if(openCache){ // 設定快取策略為LRU this.cache = new LruCache(new SimpleCache()); } if(openTransaction){ // 關閉自動提交 this.transaction = TransactionFactory.newTransaction(poolDataSource,Connection.TRANSACTION_REPEATABLE_READ,false); }else { // 開啟自動提交 this.transaction = TransactionFactory.newTransaction(poolDataSource,null,null); } } /** * @Desc 獲取mapper介面的代理類 為什麼要使用代理類,因為我們寫mapper介面類只寫了介面,卻沒有提供它的實現。於是 使用動態代理機制對這些介面進行代理,在代理類中實現sql執行的方法。此處是參照 mybatis的設計。 **/ @Override public <T> T getMapper(Class<T> type){ /**MapperProxy代理類分析見詳情8.1*/ MapperProxy mapperProxy = new MapperProxy(type,this); return (T)mapperProxy.initialization(); } /*select 語句執行流程分析**/ public <E> List<E> select(Method method,Object[] args) throws Exception{ /**PreparedStatementHandle 生成器見9.1 */ PreparedStatementHandle preparedStatementHandle = new PreparedStatementHandle(mapperCore,transaction,method,args); PreparedStatement preparedStatement = preparedStatementHandle.generateStatement(); /**執行查詢介面,此處是真實呼叫sql語句的地方 */ preparedStatement.executeQuery(); ResultSet resultSet = preparedStatement.getResultSet(); /**查詢方法的返回引數值,若是void型別,就不用返回任務東西 */ Class returnClass = mapperCore.getReturnType(method); if(returnClass == null || void.class.equals(returnClass)){ return null; }else { /**ResultSetHandle 結果處理器見9.2 */ ResultSetHandle resultSetHandle = new ResultSetHandle(returnClass,resultSet); return resultSetHandle.handle(); } } /*update 語句執行流程分析,update,delete,insert都是呼叫的這個方法**/ public int update(Method method,Object[] args)throws SQLException{ PreparedStatementHandle preparedStatementHandle = null; PreparedStatement preparedStatement = null; Integer count = null; try{ /**PreparedStatementHandle 生成器見9.1 */ preparedStatementHandle = new PreparedStatementHandle(mapperCore,transaction,method,args); preparedStatement = preparedStatementHandle.generateStatement(); /**返回受影響的行數 */ count = preparedStatement.executeUpdate(); }finally { if(preparedStatement != null){ preparedStatement.close(); } } return count; } /**後續方法直接呼叫transaction相關方法*/ @Override public void commit() throws SQLException{ transaction.commit(); } @Override public void rollback() throws SQLException{ transaction.rollback(); } @Override public void close() throws SQLException{ transaction.close(); } }
因為我們mapper介面類只有介面,沒有實現。但為什麼可以呼叫這些介面方法?此處參照mybatis原始碼,自己通過動態代理技術實現介面的動態代理,在代理方法裡寫我們自己執行Sql的邏輯。代理類見程式碼塊8
程式碼塊8
mapper介面代理類(com.simple.ibatis.execute)
MapperProxy
/** * @Author xiabing * @Desc mapper介面代理類 **/ public class MapperProxy<T> implements InvocationHandler{ /**要代理的mapper介面類*/ private Class<T> interfaces; /**具體的執行器,執行mapper中的一個方法相當於執行一條sql語句*/ private SimpleExecutor executor; public MapperProxy(Class<T> interfaces,SimpleExecutor executor) { this.interfaces = interfaces; this.executor = executor; } /**反射方法,該方法自定義需要代理的邏輯。不瞭解可以去看下JAVA動態代理技術*/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; /**object方法直接代理*/ if(Object.class.equals(method.getDeclaringClass())){ result = method.invoke(this,args); }else { // 獲取方法是select還是非select方法 Integer methodType = executor.mapperCore.getMethodType(method); if(methodType == null){ throw new RuntimeException("method is normal sql method"); } if(methodType == 1){ /**呼叫執行器的select方法,返回集合類*/ List<Object> list = executor.select(method,args); result = list; /**檢視介面的返回是List,還是單個物件*/ if(!executor.mapperCore.getHasSet(method)){ if(list.size() == 0){ result = null; }else { /**單個物件就直接取第一個*/ result = list.get(0); } } }else{ /**返回受影響的行數*/ Integer count = executor.update(method,args); result = count; } } return result; } /**構建代理類*/ public T initialization(){ return (T)Proxy.newProxyInstance(interfaces.getClassLoader(),new Class[] { interfaces },this); } }
程式碼塊7中執行SQL其實包含3個過程,生成SQL預處理語句,執行SQL,將SQL結果轉為JAVA物件。詳情見程式碼塊9
程式碼塊9
各生成器類(com.simple.ibatis.statement)
PreparedStatementHandle
/** * @Author xiabing * @Desc PreparedStatement生成器 **/ public class PreparedStatementHandle { /** * 全域性核心mapper解析類 */ private MapperCore mapperCore; /** * 待執行的方法 */ private Method method; private Transaction transaction; private Connection connection; /** * 方法輸入引數 */ private Object[] args; public PreparedStatementHandle(MapperCore mapperCore, Transaction transaction,Method method, Object[] args)throws SQLException { this.mapperCore = mapperCore; this.method = method; this.transaction = transaction; this.args = args; connection = transaction.getConnection(); } /** * @Author xiabing5 * @Desc 引數處理核心方法 todo **/ public PreparedStatement generateStatement() throws SQLException{ // 獲取已經解析方法的sqlSource類,已經將待注入欄位變為?,後續直接填充就好 SqlSource sqlSource = mapperCore.getStatement(method); // 呼叫connection方法生成預處理語句 PreparedStatement preparedStatement = connection.prepareStatement(sqlSource.getSql()); // 獲取方法輸入引數的型別 Class<?>[] clazzes = mapperCore.getParameterType(method); List<String> paramNames = mapperCore.getParameterName(method); List<String> params = sqlSource.getParam(); // 詳見typeInject方法,注入SQL引數 preparedStatement = typeInject(preparedStatement,clazzes,paramNames,params,args); return preparedStatement; } /** * @Author xiabing * @Desc preparedStatement構建,將引數注入到SQL語句中 * @Param preparedStatement 待構建的preparedStatement * @Param clazzes 該方法中引數型別陣列 * @Param paramNames 該方法中引數名稱列表,若有@Param註解,則為此註解的值,預設為類名首字母小寫 * @Param params 待注入的引數名,如user.name或普通型別如name * @Param args 真實引數值 **/ private PreparedStatement typeInject(PreparedStatement preparedStatement,Class<?>[] clazzes,List<String> paramNames,List<String> params,Object[] args)throws SQLException{ for(int i = 0; i < paramNames.size(); i++){ // 第i個引數名稱 String paramName = paramNames.get(i); // 第i個引數型別 Class type = clazzes[i]; if(String.class.equals(type)){ // 原始SQL中需要注入的引數名中是否有此引數名稱 // example: select * from users where id = {id} and name = {name} 則{id}中的id和name就是如下的params裡面內容 int injectIndex = params.indexOf(paramName); /**此處是判斷sql中是否有待注入的名稱({name})和方法內輸入物件名(name)相同,若相同,則直接注入*/ if(injectIndex >= 0){ preparedStatement.setString(injectIndex+1,(String)args[i]); } }else if(Integer.class.equals(type) || int.class.equals(type)){ int injectIndex = params.indexOf(paramName); if(injectIndex >= 0){ preparedStatement.setInt(injectIndex+1,(Integer)args[i]); } }else if(Float.class.equals(type) || float.class.equals(type)){ int injectIndex = params.indexOf(paramName); if(injectIndex >= 0){ preparedStatement.setFloat(injectIndex+1,(Float)args[i]); } }else { /** 若待注入的是物件。example: @SELECT(select * from users where id = {user.id} and name = {user.name}) List<User> getUser(User user) */ // 物件工廠,獲取物件包裝例項,見程式碼塊10 ObjectWrapper objectWrapper = ObjectWrapperFactory.getInstance(args[i]); for(int j = 0; j < params.size(); j++){ /**此處是判斷物件的屬性 如user.name,需要先獲取user物件,在呼叫getName方法獲取值*/ if((params.get(j).indexOf(paramName)) >= 0 ){ try{ String paramProperties = params.get(j).substring(params.get(j).indexOf(".")+1); Object object = objectWrapper.getVal(paramProperties); Class childClazz = object.getClass(); if(String.class.equals(childClazz)){ preparedStatement.setString(j+1,(String)object); }else if(Integer.class.equals(childClazz) || int.class.equals(childClazz)){ preparedStatement.setInt(j+1,(Integer)object); }else if(Float.class.equals(childClazz) || float.class.equals(childClazz)){ preparedStatement.setFloat(j+1,(Float)object); }else { /**目前不支援物件中包含物件,如dept.user.name todo*/ throw new RuntimeException("now not support object contain object"); } }catch (Exception e){ throw new RuntimeException(e.getMessage()); } } } } } return preparedStatement; } public void closeConnection() throws SQLException{ transaction.close(); } }
ResultSetHandle
/** * @Author xiabing * @Desc ResultSet結果處理器,執行之後返回的json物件,轉為java物件的過程 **/ public class ResultSetHandle { /**轉換的目標型別*/ Class<?> typeReturn; /**待轉換的ResultSet*/ ResultSet resultSet; Boolean hasSet; public ResultSetHandle(Class<?> typeReturn,ResultSet resultSet){ this.resultSet = resultSet; this.typeReturn = typeReturn; } /** * ResultSet處理方法,目前僅支援String,int,Float,不支援屬性是集合類 todo * */ public <T> List<T> handle() throws Exception{ List<T> res = new ArrayList<>(resultSet.getRow()); Object object = null; ObjectWrapper objectWrapper = null; Set<ClazzWrapper.FiledExpand> filedExpands = null; // 返回型別若不是基本資料型別 if(!TypeUtil.isBaseType(typeReturn)){ // 生成物件 object = generateObj(typeReturn); // 將物件封裝成包裝類 objectWrapper = ObjectWrapperFactory.getInstance(object); /** 獲取物件屬性 */ filedExpands = objectWrapper.getMapperFiledExpands(); } while (resultSet.next()){ /** 若返回是基礎資料型別,則直接將結果放入List中並返回 */ if(String.class.equals(typeReturn)){ String val = resultSet.getString(1); if(val != null){ res.add((T)val); } }else if(Integer.class.equals(typeReturn) || int.class.equals(typeReturn)){ Integer val = resultSet.getInt(1); if(val != null){ res.add((T)val); } }else if(Float.class.equals(typeReturn) || float.class.equals(typeReturn)){ Float val = resultSet.getFloat(1); if(val != null){ res.add((T)val); } }else { // 若返回的是物件(如User這種) // 查詢物件屬性,一個個注入 for(ClazzWrapper.FiledExpand filedExpand:filedExpands){ // 如果物件屬性是String型別,例如User.name是String型別 if(String.class.equals(filedExpand.getType())){ // resultSet中獲取該屬性 String val = resultSet.getString(filedExpand.getPropertiesName()); if(val != null){ // 填充到物件包裝類中 objectWrapper.setVal(filedExpand.getPropertiesName(),val); } }else if(Integer.class.equals(filedExpand.getType()) || int.class.equals(filedExpand.getType())){ Integer val = resultSet.getInt(filedExpand.getPropertiesName()); if(val != null){ objectWrapper.setVal(filedExpand.getPropertiesName(),val); } }else if(Float.class.equals(filedExpand.getType()) || float.class.equals(filedExpand.getType())){ Float val = resultSet.getFloat(filedExpand.getPropertiesName()); if(val != null){ objectWrapper.setVal(filedExpand.getPropertiesName(),val); } }else { continue; } } // 後續將物件包裝類轉為真實物件,放入List中並返回。物件包裝類見程式碼10 res.add((T)objectWrapper.getRealObject()); } } return res; } // 根據型別,根據反射,例項化物件 private Object generateObj(Class<?> clazz) throws Exception{ Constructor[] constructors = clazz.getConstructors(); Constructor usedConstructor = null; // 獲取無參構造器,若物件沒有無參構造方法,則失敗 for(Constructor constructor:constructors){ if(constructor.getParameterCount() == 0){ usedConstructor = constructor; break; } } if(constructors == null) { throw new RuntimeException(typeReturn + " is not empty constructor"); } // 利用反射生成例項 return usedConstructor.newInstance(); } }
程式碼塊9中大量用到了物件增強,類增強。使用這些原因是 在編寫框架程式碼時,因為並不知道每個屬性名,不可能把每個get或者set方法寫死,只能通過將物件封裝成包裝類。然後利用反射,要填充某個物件,就呼叫setVal(屬性名),獲取物件類似。詳情見程式碼塊10
程式碼塊10
類增強(com.simple.ibatis.reflect)
ClazzWrapper
/** * @Author xiabing * @Desc clazz解析類 **/ public class ClazzWrapper { /** * 待解析類 * */ private Class<?> clazz; /** * 該類儲存的屬性名 * */ private Set<String> propertiesSet = new HashSet<>(); /** * 該類儲存的屬性名及屬性類 * */ private Set<FiledExpand> filedExpandSet = new HashSet<>(); /** * 該類儲存的get方法。key為屬性名,value為getxxx方法 * */ private Map<String,Method> getterMethodMap = new HashMap<>(); /** * 該類儲存的set方法。key為屬性名,value為setxxx方法 * */ private Map<String,Method> setterMethodMap = new HashMap<>(); /** * 快取,避免對同一個類多次解析 * */ private static Map<String,ClazzWrapper> clazzWrapperMap = new ConcurrentHashMap<>(); public ClazzWrapper(Class clazz){ this.clazz = clazz; // 對類進行解析,如果已經解析了,則不用二次解析 if(!clazzWrapperMap.containsKey(clazz.getName())){ // 獲取該類的所有屬性 Field[] fields = clazz.getDeclaredFields(); for(Field field : fields){ // 獲取屬性的名稱,屬性型別 FiledExpand filedExpand = new FiledExpand(field.getName(),field.getType()); filedExpandSet.add(filedExpand); propertiesSet.add(field.getName()); } // 獲取該類的方法 Method[] methods = clazz.getMethods(); for(Method method:methods){ String name = method.getName(); // 如果是get方法 if(name.startsWith("get")){ name = name.substring(3); if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) { name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1); } // 放入get方法快取中 if(propertiesSet.contains(name)){ getterMethodMap.put(name,method); } }else if(name.startsWith("set")){ name = name.substring(3); if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) { name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1); } if(propertiesSet.contains(name)){ setterMethodMap.put(name,method); } } else { continue; } } // 放入快取,代表該類已經完成解析 clazzWrapperMap.put(clazz.getName(),this); } } // 查詢該類是否包含指定屬性的get方法 public boolean hasGetter(String properties){ ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName()); return clazzWrapper.getterMethodMap.containsKey(properties); } // 查詢該類是否包含指定屬性的set方法 public boolean hasSetter(String properties){ ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName()); return clazzWrapper.setterMethodMap.containsKey(properties); } // 獲取該類指定屬性的set方法 public Method getSetterMethod(String properties){ if(!hasSetter(properties)){ throw new RuntimeException("properties " + properties + " is not set method") ; } ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName()); return clazzWrapper.setterMethodMap.get(properties); } // 獲取該類指定屬性的get方法 public Method getGetterMethod(String properties){ if(!hasGetter(properties)){ throw new RuntimeException("properties " + properties + " is not get method") ; } ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName()); return clazzWrapper.getterMethodMap.get(properties); } // 獲取該類所有屬性 public Set<String> getProperties(){ ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName()); return clazzWrapper.propertiesSet; } // 獲取該類所有屬性增強 public Set<FiledExpand> getFiledExpandSet(){ ClazzWrapper clazzWrapper = clazzWrapperMap.get(clazz.getName()); return clazzWrapper.filedExpandSet; } /** * 屬性增強類 * */ public static class FiledExpand{ // 屬性名稱 String propertiesName; // 屬性型別 Class type; public FiledExpand() { } public FiledExpand(String propertiesName, Class type) { this.propertiesName = propertiesName; this.type = type; } public String getPropertiesName() { return propertiesName; } public void setPropertiesName(String propertiesName) { this.propertiesName = propertiesName; } public Class getType() { return type; } public void setType(Class type) { this.type = type; } @Override public int hashCode() { return propertiesName.hashCode(); } @Override public boolean equals(Object obj) { if(obj instanceof FiledExpand){ return ((FiledExpand) obj).propertiesName.equals(propertiesName); } return false; } } }
ObjectWrapper
/** * @Author xiabing * @Desc 物件增強,封裝了get,set方法 **/ public class ObjectWrapper { // 真實物件 private Object realObject; // 該物件的類的增強 private ClazzWrapper clazzWrapper; public ObjectWrapper(Object realObject){ this.realObject = realObject; this.clazzWrapper = new ClazzWrapper(realObject.getClass()); } // 呼叫物件指定屬性的get方法 public Object getVal(String property) throws Exception{ return clazzWrapper.getGetterMethod(property).invoke(realObject,null); } // 呼叫物件指定屬性的set方法 public void setVal(String property,Object value) throws Exception{ clazzWrapper.getSetterMethod(property).invoke(realObject,value); } public Set<String> getProperties(){ return clazzWrapper.getProperties(); } public Set<ClazzWrapper.FiledExpand> getMapperFiledExpands(){ return clazzWrapper.getFiledExpandSet(); } public Object getRealObject(){ return realObject; } }
ObjectWrapperFactory
/** * @Author xiabing5 * @Desc 物件增強類。使用物件增強是因為我們不知道每個物件的getXXX方法,只能利用反射獲取物件的屬性和方法,然後 統一提供getVal或者setVal來獲取或者設定指定屬性,這就是物件的增強的重要性 **/ public class ObjectWrapperFactory { public static ObjectWrapper getInstance(Object o){ return new ObjectWrapper(o); } }
總結感悟
上述是我在寫這個框架時的一些思路歷程,程式碼演示請見我github。通過自己手寫原始碼,對mybatis也有了個深刻的見解。當然有很多問題,後面也會進一步去完善,如果你對這個開源專案感興趣,不妨star一下呀!