1. 程式人生 > >android——仿mybatis的半自動資料庫對映的實現

android——仿mybatis的半自動資料庫對映的實現

               由於一直是做web端,最近接觸android,操作資料庫的時候懷念mybatis,因此很簡單的做了個功能有限的仿造。在此來探討一下。

               mybatis對於sql還是需要寫的,只是自動對映欄位到結果物件,所以是半自動,以此來體現其靈活性。而對於sql的管控完全分離到了xml之中。其中最令人印象深刻的地方之一應該是通過一個mapper介面去和xml進行管理。而這個mapper介面你只需要定義方法,系統會自動實現你的介面。

               動態實現介面,其實原理是動態代理。通過把該介面代理為一個操作資料庫的工具類,並通過xml解析,把sql傳入到工具類執行sql,再通過反射進行物件對映即可。

               我們來看一下我的實現吧,為了理解,先看一下xml

               1、用來建表,刪除表的xml

<db>
    
    <table name="t_task">
	    <colunm name="id" type="varchar(64)" primaryKey="true"/>
	    <colunm name="title" type="varchar(30)"/>
	    <colunm name="brief" type="text"/>
	    <colunm name="beginTime" type="datetime"/>
	    <colunm name="repeat" type="varchar(30)"/>
	    <colunm name="requestCode" type="varchar(30)"/>
	    <colunm name="isRun" type="varchar(8)"/>
	</table>
    
</db>

               2、sql操作的xml
<mapper namespace="com.xf.ztime.task.mapper.TaskMapper">

	<select id="findTask" resultType="com.xf.ztime.task.model.TaskVo">
	    select id,title,brief,isRun,beginTime,repeat,requestCode
	    from t_task
	    limit ${rows} offset ${offset}
	</select>
	
	<select id="findById" resultType="com.xf.ztime.task.model.TaskVo">
	    select id,title,brief,isRun,beginTime,repeat,requestCode
	    from t_task
		where id='${id}'
	</select>
	
	<select id="findByRequestCode" resultType="com.xf.ztime.task.model.TaskVo">
	    select id,title,brief,isRun,beginTime,repeat,requestCode
	    from t_task
		where requestCode='${requestCode}'
	</select>
	
	<insert id="insert">
	    insert into t_task
	    (id,title,brief,beginTime,repeat,requestCode,isRun)
	    values('${id}','${title}','${brief}','${beginTime}','${repeat}','${requestCode}','${isRun}')
	</insert>
	
	<update id="update">
	    update t_task
	    set title='${title}',
	        brief='${brief}',
	        beginTime='${beginTime}',
	        repeat='${repeat}'
	    where id='${id}'
	</update>
	
	<update id="updateRun">
	    update t_task
	    set isRun='${isRun}'
	    where id='${id}'
	</update>
	
	<delete id="delete">
	    delete from t_task
	    where id='${id}'
	</delete>
	
</mapper>

                3、動態代理核心程式碼
/**
 * 
 * 仿造mybaits,通過動態代理將xml和mapper繫結,並解析xml執行sql
 * 
 * @author 吳林峰
 *
 */
public class Loader implements InvocationHandler{

	private static String TAG="Loader";

	private Helper helper;
	public static String dbPath;
	private static final String MAPPER_NAMESPACE="namespace";
	private static final String ID="id";
	private static final String TABLE_NAME="name";
	private static final String COLUNM_NAME="name";
	private static final String COLUNM_TYPE="type";
	private static final String COLUNM_PRIMARY="primaryKey";
	private static final String COLUNM_AUTOINCREMENT="autoincrement";
	private static final String RESULT_TYPE="resultType";
	
	private Context context;
	private XmlResourceParser xmlParser;
	private int xmlId;
	public static int dbXmlId;
	private SQLiteDatabase db;
	private Dao dao;

	public Loader(){
		
	}
	
	public Loader(Activity context){
		this.helper=new Helper(context);
		this.context=context;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object result=null;
		if (Object.class.equals(method.getDeclaringClass())) {
			try {
				result=method.invoke(this, args);
			} catch (Throwable t) {
				t.printStackTrace();
			}
		} else {
			result=run(method, args);
		}
		return result;
	}
	
	/**
	 * 
	 * 獲得xml關聯的介面
	 * 
	 * @return
	 * @throws XmlPullParserException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public Class getMapper() throws XmlPullParserException, ClassNotFoundException, IOException{
		Class c=null;
		Resources res = context.getResources();     
		xmlParser = res.getXml(xmlId);
		int eventType = xmlParser.getEventType();  
	    while (eventType != XmlResourceParser.END_DOCUMENT) {  
	    	 if (eventType == XmlResourceParser.START_TAG) {
	    		 String tagName = xmlParser.getName();
	    		 if(tagName.equals("mapper")){
	    			 int count=xmlParser.getAttributeCount();
	    			 for(int i=0;i<count;i++){
	    				if(xmlParser.getAttributeName(i).equals(MAPPER_NAMESPACE)) {
	    					c=Class.forName(xmlParser.getAttributeValue(i));
	    				}
	    			 }
	    		 }
	    	 }
	    	 eventType = xmlParser.next();  
	    }
	    return c;
	}
	
	public Object run(Method method,Object[] args) throws Exception{
		if(dao==null)
			dao=new Dao();
		Object result=null;
		String name=method.getName();
		//初始化資料庫表,如果存不存在則建表
		openDB();
		result=dao.exec(name,args);
		return result;
	}
	
	public void initTable(SQLiteDatabase db) throws XmlPullParserException, IOException{
		Resources res = context.getResources();   
		xmlParser = res.getXml(dbXmlId);
		int eventType = xmlParser.getEventType();  
		    // 判斷是否到了檔案的結尾    
            while (eventType != XmlResourceParser.END_DOCUMENT) {  
            	//啟始標籤
            	 if (eventType == XmlResourceParser.START_TAG) {
            		 String tagName = xmlParser.getName();
            		 if(tagName.equals("table")){
            	   		 StringBuffer sb=new StringBuffer();
	            		 sb.append("create table if not exists ");
            			 int count=xmlParser.getAttributeCount();
            			 for(int i=0;i<count;i++){
            				 //表名
            				 if(xmlParser.getAttributeName(i).equals(TABLE_NAME)){
            					 sb.append(xmlParser.getAttributeValue(i)+"( ");
            				 }
            			 }
            			 int e=xmlParser.next();
            			 if(e==XmlResourceParser.START_TAG){
            				 tagName = xmlParser.getName();
            				 //欄位
            				 while(tagName.equals("colunm")){
            					if(e==XmlResourceParser.START_TAG){
	    	            			sb.append(xmlParser.getAttributeValue(null, COLUNM_NAME)+" ");
	    	            			sb.append(xmlParser.getAttributeValue(null, COLUNM_TYPE)+" ");
	    	            			String isPrimary=xmlParser.getAttributeValue(null, COLUNM_PRIMARY);
	    	            			if(isPrimary!=null && isPrimary.equals("true"))
	    	            				sb.append("primary key ");
	    	            			String isAutoIncrement=xmlParser.getAttributeValue(null, COLUNM_AUTOINCREMENT);
	    	            			if(isAutoIncrement!=null && isAutoIncrement.equals("true"))
	    	            				sb.append("autoincrement ");
	    	            			sb.append(",");
            					}
    	            			e=xmlParser.next();
    	            			tagName = xmlParser.getName();
            				 }
            				 String s=sb.substring(0,sb.length()-1);
            				 s=s+")";
            		         Log.d(TAG, "建表語句為:"+s);
            		         db.execSQL(s);
            			 }
            		 }
            	 }
            	 //移到下一個標籤
            	 eventType = xmlParser.next();    
            }
	}
	
	public void dropTable(SQLiteDatabase db) throws XmlPullParserException, IOException{
		Resources res = context.getResources();   
		xmlParser = res.getXml(dbXmlId);
		int eventType = xmlParser.getEventType();  
		    // 判斷是否到了檔案的結尾    
            while (eventType != XmlResourceParser.END_DOCUMENT) {  
            	//啟始標籤
            	 if (eventType == XmlResourceParser.START_TAG) {
            		 String tagName = xmlParser.getName();
            		 if(tagName.equals("table")){
            	   		 StringBuffer sb=new StringBuffer();
	            		 sb.append("drop table if exists ");
            			 int count=xmlParser.getAttributeCount();
            			 for(int i=0;i<count;i++){
            				 //表名
            				 if(xmlParser.getAttributeName(i).equals(TABLE_NAME)){
            					 sb.append(xmlParser.getAttributeValue(i));
            				 }
            			 }
            			 db.execSQL(sb.toString());
            		 }
            	 }
            	 //移到下一個標籤
            	 eventType = xmlParser.next();    
            }
	}
	
	private void openDB(){
		if(helper==null)
			this.helper=new Helper(context);
		db=helper.getReadableDatabase();
	}
	
	class Dao{
		public Object exec(String name,Object[] args) throws Exception{
			Object result = null;
			//解析xml
			Resources res = context.getResources();   
			xmlParser = res.getXml(xmlId);
		    int eventType = xmlParser.getEventType();  
		    // 判斷是否到了檔案的結尾    
            while (eventType != XmlResourceParser.END_DOCUMENT) {  
            	//啟始標籤
            	 if (eventType == XmlResourceParser.START_TAG) {
            		 String tagName = xmlParser.getName();
            		 if(tagName.equals("select")){
            			 //id
            			 String id=xmlParser.getAttributeValue(null, ID);
            			 if(name.equals(id)){
	            			 //返回型別
	        				 String type=xmlParser.getAttributeValue(null, RESULT_TYPE);
	            			 //獲得sql語句
	            			 eventType = xmlParser.next();  
	            			 String sql=xmlParser.getText();
	            			 //繫結引數
	            			 sql=bindArgs(sql,args);
	            			 Cursor cursor=db.rawQuery(sql,new String[]{});
	        				 //對映結果
	        				 if(!type.equals("java.util.Map")){
	        					 List list=new ArrayList();
	        					 Class c=Class.forName(type);
	        					 while(cursor.moveToNext()){
		        					 Object o=c.newInstance();
		        					 List<Field> fs=getDeclaredField(o);
		        					 for(Field f : fs){
		        						 String fName=f.getName();
		        						 if(fName.equals("serialVersionUID"))
		        							 continue;
		        						 Method m=getDeclaredMethod(o,"set"+fName.substring(0,1).toUpperCase()+fName.substring(1),f.getType());
		        						 if(f.getType()==String.class){
		        							 try{
		        								 m.invoke(o, cursor.getString(cursor.getColumnIndexOrThrow(fName)));
		        							 }catch(Exception e){
		        								 m.invoke(o, "");
		        							 }
		        						 }
		        						 if(f.getType()==Integer.class){
		        							 try{
		        							 	m.invoke(o, cursor.getInt(cursor.getColumnIndexOrThrow(fName)));
			        						 }catch(Exception e){
		        								 m.invoke(o, 0);
		        							 }
		        						 }
		        						 if(f.getType()==Float.class){
		        							 try{
		        							 	m.invoke(o, cursor.getFloat(cursor.getColumnIndexOrThrow(fName)));
			        						 }catch(Exception e){
		        								 m.invoke(o, 0);
		        							 }
		        						 }
		        						 if(f.getType()==Double.class){
		        							 try{
		        							 	m.invoke(o, cursor.getDouble(cursor.getColumnIndexOrThrow(fName)));
			        						 }catch(Exception e){
		        								 m.invoke(o, 0);
		        							 }
		        						 }
		        					 }
		        					 list.add(o);
	        					 }
	        					 result=list;
	        				 }
	        				 else{
	        					 
	        				 }
	        				 return result;
            			 }
            		 }else if(tagName.equals("insert")){
            			 String id=xmlParser.getAttributeValue(null, ID);
            			 if(name.equals(id)){
	            			 //獲得sql語句
	            			 eventType = xmlParser.next();  
	            			 String sql=xmlParser.getText();
	            			 //繫結引數
	            			 sql=bindArgs(sql,args);
	            			 db.execSQL(sql);
            			 }
            		 }else if(tagName.equals("update")){
            			 String id=xmlParser.getAttributeValue(null, ID);
            			 if(name.equals(id)){
	            			 //獲得sql語句
	            			 eventType = xmlParser.next();  
	            			 String sql=xmlParser.getText();
	            			 //繫結引數
	            			 sql=bindArgs(sql,args);
	            			 db.execSQL(sql);
            			 }
            		 }else if(tagName.equals("delete")){
            			 String id=xmlParser.getAttributeValue(null, ID);
            			 if(name.equals(id)){
	            			 //獲得sql語句
	            			 eventType = xmlParser.next();  
	            			 String sql=xmlParser.getText();
	            			 //繫結引數
	            			 sql=bindArgs(sql,args);
	            			 db.execSQL(sql);
            			 }
            		 }
            	 }
            	 //移到下一個標籤
            	 eventType = xmlParser.next();    
            }
			return result;
		}
		
		public Cursor query(){
			return null;
		}
	}
	
	/**
	 * 
	 *  繫結引數
	 * 
	 * @param sql
	 * @param args
	 * @return
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 */
	private String bindArgs(String sql,Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		 if(!args[0].getClass().getName().equals(Map.class.getName())){
			 Class argc=args[0].getClass();
			 List<Field> fs=getDeclaredField(args[0]);
			 for(Field f: fs){
				 String fName=f.getName();
				 Method m=getDeclaredMethod(args[0],"get"+fName.substring(0,1).toUpperCase()+fName.substring(1));
				 //Log.d(TAG, "解析欄位:"+fName);
				 String fValue="";
				 if(!fName.equals("serialVersionUID"))
					fValue=String.valueOf(m.invoke(args[0]));
				 sql=sql.replace("${"+fName+"}", fValue);
			 }
		 }
		 //繫結引數,引數型別為map
		 else{
			 
		 }
		 return sql;
	}
	
	private List<Field> getDeclaredField(Object object){  
        List<Field> fa = new ArrayList<Field>() ;  
          
        Class<?> clazz = object.getClass() ;  
          
        for(; clazz != Object.class ; clazz = clazz.getSuperclass()) {  
            try {  
                Field[] fs=clazz.getDeclaredFields();
                for(Field f : fs){
                	fa.add(f);
                }
            } catch (Exception e) {  
            	
            }   
        }  
      
        return fa;  
    }     
	
	private Method getDeclaredMethod(Object object, String methodName, Class<?> ... parameterTypes){  
        Method method = null ;  
          
        for(Class<?> clazz = object.getClass() ; clazz != Object.class ; clazz = clazz.getSuperclass()) {  
            try {  
                method = clazz.getDeclaredMethod(methodName, parameterTypes) ;  
                return method ;  
            } catch (Exception e) {   
              
            }  
        }  
          
        return null;  
    }  

	public Context getContext() {
		return context;
	}

	public void setContext(Context context) {
		this.context = context;
	}

	public int getXmlId() {
		return xmlId;
	}

	public void setXmlId(int xmlId) {
		this.xmlId = xmlId;
	}

	public Helper getHelper() {
		return helper;
	}

	public void setHelper(Helper helper) {
		this.helper = helper;
	}
	
}

最後,使用mybatis的時候我們通過IOC將我們定義的mapper介面注入到service層,在其中已經將mapper換成了代理類。

我這裡沒有用IOC,通過一個父類,識別傳入的泛型來替換代理

/**
 * 
 * 在例項化的時候自動實現mapper介面
 * 
 * @author 吳林峰
 *
 * @param <T> mapper介面
 */
public class BaseService<T> {

	protected Context context;
	protected T t;
	
	protected BaseService(Context context,HelperCreator hc){
		this.context=context;
		Loader loader=new Loader();
		loader.setXmlId(R.xml.task);
		loader.setHelper(hc.getHelper());
		loader.setContext(context);
		try {
			Object newProxyInstance = Proxy.newProxyInstance(
					loader.getMapper().getClassLoader(),
					new Class[] {loader.getMapper()}, loader);
			this.t=(T) newProxyInstance;
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
}

最後見證一下奇蹟,看看怎麼使用

1、只需要定義一個介面,不需要實現,方法名和xml中的id一致即可

public interface TaskMapper {

	List<TaskVo> findTask(TaskVo vo);
	List<TaskVo> findById(TaskVo vo);
	List<TaskVo> findByRequestCode(TaskVo vo);
	void insert(TaskVo vo);
	void update(TaskVo vo);
	void updateRun(TaskVo vo);
	void delete(TaskVo vo);
	
}

2、需要呼叫該介面的類繼承BaseService該類即可,泛型為你需要用到的mapper介面
public class TaskService extends BaseService<TaskMapper>
這個時候,TaskService中就有了一個名為t的成員變數,該變數已經自動實現了TaskMapper介面

如果我要執行xml的sql語句,只需要呼叫該介面方法即可,比如

public List<TaskVo> getTask() throws Exception{
		TaskVo vo=new TaskVo();
		vo.setRows(100000);
		vo.setOffset(0);
		List<TaskVo> vos=t.findTask(vo);
		for(TaskVo v : vos){
			Log.d(TAG, "requestCode:"+v.getRequestCode());
		}
		return vos;
	}

由於我們的TaskMapper沒有通過程式碼實現,而是在執行時自動實現,所以LogCat會拋一個異常警告我們有介面沒有實現,不過完全沒有關係