我去年做了個內存數據庫,自以為功能很強大。內存數據庫是獨立運行的程序,客戶端通過socket訪問,傳送SQL語句並得到結果,為此我提供了一個C接口的客戶端API。
但如果要做到更好的通用性,必須照顧IT世界數量最多的兩類人:java程序員和c#程序員。c#我的同事封裝了ado.net驅動。這幾天我實現了jdbc的驅動,本文記錄實現過程的一些心得。
本人較為擅長的是C/C++,對java的啰嗦語法實在不太感冒,前陣子用java寫過一個規模不大的android程序,才敢挑戰寫一個jdbc驅動的任務。
我的開發環境是比較小眾的mac os x,正好考驗一下java所謂的跨平台。
第一步,先將C接口的客戶端API封裝成一個java類:LxjDbApi.java
package com.lxjdb.jdbc; public class LxjDbApi { public LxjDbApi(){ System.loadLibrary("LxjDbJdbcApi"); } public native long Open(String host, int port, String user, String pwd); public native void Close(long conn); public native int Exec(long conn, String sql, String[] dbInfo); // 執行一個sql語句 public native int Rows(long conn); // 總行數 public native int Cols(long conn); // 總列數 // 得到列信息 public native int GetColInfoByIndex(long conn, int col, String[] retName, int[] lenTypePos); public native int GetColInfoByName(long conn, String name, int[] lenTypePos); public native int Next(long conn); // 到下一條記錄,成功返回1,到記錄集結束,則為0,錯誤返回負數 public native int GotoRec(long conn, int recNo); // 記錄號從1開始 // 取字段值:1.總是返回字符串結果,2.字符串變量要預先分配至少2048大小的長度 public native int LxjDbGetValByName(long conn, String fieldName, String[] retVal); // 列編號從0開始 public native int GetValByIndex(long conn, int col, String[] retVal); }
javah -classpath bin -d jni com.lxjdb.jdbc.LxjDbApi
下面是cpp代碼,是調用我們的c語言api:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include "com_lxjdb_jdbc_LxjDbApi.h" /* Header for class com_lxjdb_jdbc_LxjDbApi */ #include "LxjDbApi.h" /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: Open * Signature: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)Ljava/lang/Long; */ JNIEXPORT jlong JNICALL Java_com_lxjdb_jdbc_LxjDbApi_Open (JNIEnv * env, jobject obj, jstring host, jint port, jstring user, jstring pwd) { LxjDbInit(); const char* pHost = env->GetStringUTFChars(host, 0); const char* pUser = env->GetStringUTFChars(user, 0); const char* pPwd = env->GetStringUTFChars(pwd, 0); void* conn = LxjDbOpen(pHost, port, "", pUser, pPwd); env->ReleaseStringUTFChars(host, pHost); env->ReleaseStringUTFChars(pwd, pPwd); env->ReleaseStringUTFChars(user, pUser); return((jlong)conn); } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: Close * Signature: (Ljava/lang/Long;)Ljava/lang/Long; */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_Close (JNIEnv * env, jobject obj, jlong conn) { void* pConn = (void*)conn; int ret = LxjDbClose(pConn); return(ret); } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: Exec * Signature: (JLjava/lang/String;[Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_Exec (JNIEnv * env, jobject obj, jlong conn, jstring sql, jobjectArray dbInfo) { void* pConn = (void*)conn; const char* pSql = env->GetStringUTFChars(sql, 0); int retCode = 0; char pDbInfo[512]; pDbInfo[0] = '\0'; int ret = LxjDbExec(pConn, pSql, retCode, pDbInfo); env->ReleaseStringUTFChars(sql, pSql); env->SetObjectArrayElement(dbInfo, 0, env->NewStringUTF(pDbInfo)); return(ret>0 ? retCode : ret); } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: Rows * Signature: (J)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_Rows (JNIEnv * env, jobject obj, jlong conn) { void* pConn = (void*)conn; return( LxjDbRows(pConn) ); // 總行數 } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: Cols * Signature: (J)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_Cols (JNIEnv * env, jobject obj, jlong conn) { void* pConn = (void*)conn; return(LxjDbCols(pConn)); // 總列數 } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: GetColInfoByIndex * Signature: (JI[Ljava/lang/String;[I)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_GetColInfoByIndex (JNIEnv * env, jobject obj, jlong conn, jint col, jobjectArray retName, jintArray lenTypePos) { void* pConn = (void*)conn; char name[200]; name[0] = '\0'; int len=0; int type=0; int pos=0; int ret = LxjDbGetColInfoByIndex(pConn, col, name, len, type, pos); // 根據列索引(從0開始)找到字段信息 env->SetObjectArrayElement(retName, 0, env->NewStringUTF(name)); jint *pArr = env->GetIntArrayElements(lenTypePos, NULL); pArr[0] = len; pArr[1] = type; pArr[2] = pos; env->ReleaseIntArrayElements(lenTypePos, pArr, NULL); return(ret); } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: GetColInfoByName * Signature: (JLjava/lang/String;[I)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_GetColInfoByName (JNIEnv * env, jobject obj, jlong conn, jstring name, jintArray lenTypePos) { void* pConn = (void*)conn; const char* pName = env->GetStringUTFChars(name, 0); int len = 0; int type = 0; int pos = 0; int ret = LxjDbGetColInfoByName(pConn, pName, len, type, pos); env->ReleaseStringUTFChars(name, pName); jint *pArr = env->GetIntArrayElements(lenTypePos, NULL); pArr[0] = len; pArr[1] = type; pArr[2] = pos; env->ReleaseIntArrayElements(lenTypePos, pArr, NULL); return(ret); } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: Next * Signature: (J)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_Next (JNIEnv * env, jobject obj, jlong conn) { void* pConn = (void*)conn; return(LxjDbNext(pConn)); // 下一行 } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: GotoRec * Signature: (JI)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_GotoRec (JNIEnv * env, jobject obj, jlong conn, jint recNo) { void* pConn = (void*)conn; return(LxjDbGotoRec(pConn, recNo)); // 到指定行 } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: LxjDbGetValByName * Signature: (JLjava/lang/String;[Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_LxjDbGetValByName (JNIEnv * env, jobject obj, jlong conn, jstring name, jobjectArray retVal) { void* pConn = (void*)conn; const char* pName = env->GetStringUTFChars(name, 0); char val[2048]; val[0] = '\0'; int ret = LxjDbGetValByName(pConn, pName, val); env->ReleaseStringUTFChars(name, pName); env->SetObjectArrayElement(retVal, 0, env->NewStringUTF(val)); return(ret); } /* * Class: com_lxjdb_jdbc_LxjDbApi * Method: GetValByIndex * Signature: (JI[Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_lxjdb_jdbc_LxjDbApi_GetValByIndex (JNIEnv * env, jobject obj, jlong conn, jint col, jobjectArray retVal) { void* pConn = (void*)conn; char val[2048]; val[0] = '\0'; int ret = LxjDbGetValByIndex(pConn, col, val); env->SetObjectArrayElement(retVal, 0, env->NewStringUTF(val)); return(ret); }
LIBFLAG= -lstdc++ -lpthread -ldl CPPFLAGS = -c -fPIC -I /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/include -I /System/Library/Frameworks/JavaVM.framework/Versions/A/Headers COMMON_OBJs = com_lxjdb_jdbc_LxjDbApi.o SHARELIB = -lLxjDbApi libLxjDbJdbcApi: $(COMMON_OBJs) gcc $(COMMON_OBJs) $(LIBFLAG) $(SHARELIB) -rdynamic -dynamiclib -fPIC -install_name /usr/lib/libLxjDbJdbcApi.dylib -o libLxjDbJdbcApi.dylib
libLxjDbJdbcApi.dylib注意,因為java的jni在mac下使用共享庫比較特別,需要將該文件改名為libLxjDbJdbcApi.jnilib並拷貝到/usr/lib/java目錄下。
第二步,實現各接口類:
1、Driver類
注意務必要重寫jdbcCompliant()方法並返回false,表示我們不需要全面兼容實現所有的jdbc接口,只要實現必須的部分。
主要實現connect()方法,代碼如下:
package com.lxjdb.jdbc; import java.sql.*; import java.util.*; import java.util.logging.Logger; public class Driver implements java.sql.Driver{ public LxjDbApi lxjdb; static{ try{ java.sql.DriverManager.registerDriver(new Driver()); } catch(SQLException e){ // System.err.println(e); throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { // Required for Class.forName().newInstance() lxjdb = new LxjDbApi(); } public boolean acceptsURL(String url) throws SQLException{ return url.startsWith("jdbc:lxjdb://"); } public Connection connect(String url, Properties info) throws SQLException{ if( !acceptsURL( url)){ return null; } // 要分解url得到主機地址和端口號,並從info得到用戶名和密碼 try { String[] arr=url.split("//"); String url2 = arr[1]; String[] arr2=url2.split(":"); String host = arr2[0]; int port = Integer.parseInt(arr2[1]); String user = info.getProperty("user"); String pwd = info.getProperty("password"); return new LxjDbConnection(lxjdb, host, port, user, pwd); } catch(Exception e){ throw new SQLException(e.getMessage()); } } public boolean jdbcCompliant(){ return false; } public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { // TODO Auto-generated method stub return new DriverPropertyInfo[0]; } public int getMajorVersion() { // TODO Auto-generated method stub return 1; } public int getMinorVersion() { // TODO Auto-generated method stub return 0; } public Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO Auto-generated method stub return null; } }
主要是實現createStatement()方法:
package com.lxjdb.jdbc; import java.sql.*; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; public class LxjDbConnection implements java.sql.Connection{ public long conn=0; public LxjDbApi lxjdb; public LxjDbConnection(LxjDbApi plxjdb, String host, int port, String user, String pwd) throws SQLException { lxjdb = plxjdb; conn = lxjdb.Open(host, port, user, pwd); if(conn==0){ String info = "Open Err: "+host+":"+port+",user="+user; throw new SQLException(info); } } public Statement createStatement() throws SQLException { return new LxjDbStatement(this); } }3、Statement類
二是執行select查詢語句的ResultSet executeQuery(String sql)方法,顯然,這個方法將返回結果集。
package com.lxjdb.jdbc; import java.sql.*; public class LxjDbStatement implements java.sql.Statement { public LxjDbConnection conntion; public LxjDbApi lxjdb; public long conn; public LxjDbStatement(LxjDbConnection pconn){ conntion = pconn; lxjdb = pconn.lxjdb; conn = pconn.conn; } public ResultSet executeQuery(String sql) throws SQLException { String[] dbInfo = new String[1]; int ret = lxjdb.Exec(conn, sql, dbInfo); if(ret<0){ throw new SQLException(dbInfo[0]); } return new LxjDbResultSet(conntion); } public int executeUpdate(String sql) throws SQLException { String[] dbInfo = new String[1]; int ret = lxjdb.Exec(conn, sql, dbInfo); if(ret<0){ throw new SQLException(dbInfo[0]); } return ret; } }4、ResultSet類
當然,還要實現移動結果集游標行指針的一些方法。
package com.lxjdb.jdbc; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; import java.net.URL; import java.sql.*; import java.util.Calendar; import java.util.Map; public class LxjDbResultSet implements java.sql.ResultSet { public LxjDbApi lxjdb; public long conn; public LxjDbResultSet(LxjDbConnection pconn){ lxjdb = pconn.lxjdb; conn = pconn.conn; } public boolean next() throws SQLException { int ret = lxjdb.Next(conn); return ret>0 ? true : false; } public boolean first() throws SQLException { if(lxjdb.Rows(conn)<1) return false; int ret = lxjdb.GotoRec(conn, 1); return ret>0 ? true : false; } public boolean last() throws SQLException { int r = lxjdb.Rows(conn); if(r<1) return false; int ret = lxjdb.GotoRec(conn, r); return ret>0 ? true : false; } public String getString(int columnIndex) throws SQLException { String[] retVal = new String[1]; lxjdb.GetValByIndex(conn, columnIndex-1, retVal); // columnIndex是從1開始的 return retVal[0]; } public String getString(String columnLabel) throws SQLException { String[] retVal = new String[1]; lxjdb.LxjDbGetValByName(conn, columnLabel, retVal); return retVal[0]; } public int getFetchSize() throws SQLException { return lxjdb.Rows(conn); } }
在Eclipse下對准項目右鍵,選擇“Export...”,導出LxjDbJdbc.jar。
這樣就成功地實現了jdbc驅動。
下面再就可以進行測試了。
第四步,測試:
測試代碼比較簡單,不用過多解釋:
import java.sql.*; public class JdbcTest { public static void main(String[] args) { String driver = "com.lxjdb.jdbc.Driver"; String userName = "sa"; String passwrod = "********"; String url = "jdbc:lxjdb://192.168.0.106:2013"; String sql = "select * from OnlineUser"; try { System.out.println("path:["+System.getProperties().get("java.library.path")+"]"); Class.forName(driver); Connection conn = DriverManager.getConnection(url, userName, passwrod); Statement stmt = conn.createStatement() ; int ret = stmt.executeUpdate("insert into OnlineUser(UserId, DevId, Addr, LastTime, Expires) values('9999','mac','192.168.0.106:888',getdate(),2000)"); System.out.println("executeUpdate:"+ret); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { System.out.println("userId : " + rs.getString(1) + " dev : " + rs.getString(2) + " addr : " + rs.getString("Addr")+ " time : " + rs.getString(4)); } // 關閉記錄集 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } // 關閉鏈接對象 if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } }