android——仿mybatis的半自動資料庫對映的實現
阿新 • • 發佈:2019-02-19
由於一直是做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會拋一個異常警告我們有介面沒有實現,不過完全沒有關係