• mybatis通用DAO


    扫扫关注“茶爸爸”微信公众号
    坚持最初的执着,从不曾有半点懈怠,为优秀而努力,为证明自己而活。
    回复:茶爸爸了解他这个人!!

    花了几天的时间研究了一下mybatis的源代码,觉得这个框架还是很不错的。但是有一些缺陷的地方。

    先来说说它和别的框架比起来我认为的好处:

              (1)sql外置,把sql文件写在配置文件中加上强大的动态sql支持。

              (2)利用cjk动态代理,实现配置文件映射接口。

    这样我们在使用这套orm框架的时候就可以不再写那个拼接sql语句的dao实现类(dao-impl class),把dao实现类的工作都移交给那些*mapper.xml文件,如果数据库查询业务需要修改sql的话,那么只需要修改xml文件中的sql语句。

    需要扩展的地方:

              (1)没有内置物理分页的实现。

    我们一般的习惯都是返回我们自己的分页实现类,网上大多的mybatis分页实现都是先返回分页列表,再查下返回总页数,然后处理成我们自己的分页类。我自己也写了一个分页,把这部分工作交给mybatis来处理,以后会把代码贴出来。

               (2)公用范型接口dao

    公用范型接口dao类几乎成为我们开发中最长使用的,把一些长操作的方法封装一下,供我们的业务dao调用,但是在mybatis中,我现在不想再写dao的实现类,只有接口类,取缔他的是mapper-xml文件,这样如何才能封装我们的公用方法呢。

    幸好mybatis提供了插件拦截器,我们可以在不修改mybatis源码的基础上来扩展它的功能。

     

    好忙啊,花了些时间写了一个范型接口dao,还没有经过验证,先贴上代码。

    使用方法很简单,只要你使用MappInterceptor这个拦截器,在你的Mapper接口中继承这个IGenericDao这样 公用的方法就可以使用了,这样的好处是在一般业务的类中不用再写Dao的实现,直接使用 IGenericDao中提供的方法就ok

     

     

    公用dao,你的接口只要继承这个dao就可以使用里面的方法。

     

    package org.cf.guott.mybatis;
    
    import java.util.List;
    import org.apache.ibatis.annotations.Delete;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;
    import org.cf.guott.mybatis.pagination.PageMyBatis;
    
    public interface IGenericDao<T,PK> {
      
      @Select("MapperGD.find.entityById")
      public T findEntityById(PK id);
      
      @Select("MapperGD.find.entitys")
      public List<T> findEntity(Object... obj);
      
      @Select("MapperGD.find.ListByLike")
      public List<T> findLikeEntity(Object... obj);
      
      @Insert("MapperGD.insert.entity")
      public void insertEntity(T t);
      
      @Update("MapperGD.update.entity")
      public void updateEntityById(T entity);
      
      @Delete("MapperGD.delete.id")
      public void deleteById(PK id);
      
      @Delete("MapperGD.delete.condition")
      public void deleteByCondition(Object param);
      
      @Select("MapperGD.find.entity.queryByVo")
      public PageMyBatis<T> queryByVo(int i,int c,Object... obj);
      
      @Select("MapperGD.find.entity.queryByVoLike")
      public PageMyBatis<T> LikequeryByVo(int i,int c,Object... obj);
      
      
    }
     

     

     

     这个一个拦截器,来处理你的公用dao中的方法

    package org.cf.guott.mybatis.dbmapper.interceptor;
    
    import java.util.Map;
    import java.util.Properties;
    
    import org.apache.commons.lang3.reflect.FieldUtils;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Plugin;
    import org.apache.ibatis.plugin.Signature;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.cf.guott.mybatis.helpers.MapperSqlHelper;
    import org.cf.guott.mybatis.pagination.MappedStatmentHelper;
    
      @Intercepts({
        @Signature(
              type = Executor.class,
              method = "query",
              args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
          ,@Signature(
              type = Executor.class,
              method = "update",
              args = {MappedStatement.class, Object.class})
      })
      
      public class MappInterceptor implements Interceptor{
        private final static String _sql_regex = ".*MapperGD.*";
        
        @SuppressWarnings("unchecked")
        private void processIntercept(final Object[] queryArgs) {
          final MappedStatement ms = (MappedStatement) queryArgs[0];
          final Object parameter = queryArgs[1];
          String mapperSQL = ms.getBoundSql(parameter).getSql();
          BoundSql boundSQL = ms.getBoundSql(parameter);
          
          Class<?> entityclazz = MappedStatmentHelper.getEntityClazz(ms.getResource());
          queryArgs[0] = MappedStatmentHelper.setMSReturnSetMap(ms, entityclazz);
          
          boolean interceptor = mapperSQL.matches(_sql_regex);
          if(!interceptor){
            return;
          }
          if(entityclazz == null){
            throw new RuntimeException("使用公共dao必須給mapper接口的@MyBatisRepository(User.class) 注解設置值.");
          }
          
          String new_sql = MapperSqlHelper.getExecuSQL(entityclazz,mapperSQL,parameter);
          
          BoundSql newBoundSql = MappedStatmentHelper.copyFromBoundSql(ms,
              boundSQL, new_sql);
          MappedStatement newMs = MappedStatmentHelper
              .copyFromMappedStatement(ms, newBoundSql);
          queryArgs[0] = newMs;
          
        }
        public Object intercept(Invocation invocation) throws Throwable {
          
          processIntercept(invocation.getArgs());
          
          return invocation.proceed();
        }
        public Object plugin(Object o) {
              return Plugin.wrap(o, this);
        }
        public void setProperties(Properties arg0) {
        }
      }
      这个类来处理你的sql语句
    package org.cf.guott.mybatis.helpers;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.Map;
    
    import org.apache.commons.lang3.reflect.FieldUtils;
    import org.cf.guott.mybatis.annotation.MyBatisColumn;
    import org.cf.guott.mybatis.annotation.MyBatisRepository;
    import org.cf.guott.mybatis.annotation.MybatisID;
    import org.cf.guott.mybatis.annotation.MybatisTable;
    import org.cf.guott.mybatis.entity.User;
    
    public class MapperSqlHelper{
    
      public static final Class<? extends Annotation> MYBATISREPOSITORY = MyBatisRepository.class;
      public static final Class<? extends Annotation> MYBATISTABLE = MybatisTable.class;
      public static final Class<? extends Annotation> MYBATISCOLUMN = MyBatisColumn.class;
      public static final Class<? extends Annotation> MYBATISID = MybatisID.class;
      
      
      public  String getUpdateSQL(){
        return null;
      }
      /**
       * 传入mapper接口class
       * @param mapperclazz
       * @return
       */
      private String insertEntity(Class<?> clazz){
        StringBuilder sql = new StringBuilder();
        StringBuilder intosql = new StringBuilder();
        StringBuilder valuessql = new StringBuilder();
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("INSERT INTO "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("INSERT INTO "+clazz.getName());
          }else{
            sql.append("INSERT INTO "+antable.value());
          }
        }
        Field[] files = clazz.getDeclaredFields();
        intosql.append("(");
        for(Field file : files){
          file.setAccessible(true);
          if(file.isAnnotationPresent(MYBATISCOLUMN)){
            MyBatisColumn anColumn = (MyBatisColumn)file.getAnnotation(MYBATISCOLUMN);
            if(!anColumn.isAutoKey()){
              if(anColumn.value().equals("")){
                intosql.append(file.getName()+",");
              }else{
                intosql.append(anColumn.value()+",");
              }
            }
          }else{
            intosql.append(file.getName()+",");
          }
        }
        valuessql.append(" values (");
        for(Field file : files){
          file.setAccessible(true);
          if(file.isAnnotationPresent(MYBATISCOLUMN)){
            MyBatisColumn anColumn = (MyBatisColumn)file.getAnnotation(MYBATISCOLUMN);
            if(!anColumn.isAutoKey()){
              if(anColumn.value().equals("")){
                valuessql.append("#{"+file.getName()+"},");
              }else{
                valuessql.append("#{"+file.getName()+"},");
              }
            }
          }else{
            valuessql.append("#{"+file.getName()+"},");
          }
          
        }
        return sql.append(intosql.substring(0, intosql.length()-1)).append(") ").append(valuessql.substring(0, valuessql.length()-1)).append(")").toString();
      }
      
      /**
       * 传入mapper接口class
       * @param mapperclazz
       * @return
       */
      private  String updateEntityById(Class<?> clazz){
        StringBuilder sql = new StringBuilder();
        StringBuilder set = new StringBuilder();
        StringBuilder wheresql = new StringBuilder();
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("UPDATE  "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("UPDATE  "+clazz.getName());
          }else{
            sql.append("UPDATE  "+antable.value());
          }
        }
        Field[] files = clazz.getDeclaredFields();
        set.append(" set ");
        wheresql.append(" where 1=1 ");
        for(Field file : files){
          file.setAccessible(true);
          if(file.isAnnotationPresent(MYBATISCOLUMN)){
            MyBatisColumn anColumn = (MyBatisColumn)file.getAnnotation(MYBATISCOLUMN);
            if(!anColumn.isID()){//判断字段不为主键
              if(anColumn.value().equals("")){
                set.append(file.getName()+" = #{"+file.getName()+"} ,");
              }else{
                set.append(anColumn.value()+" = #{"+file.getName()+"} ,");
              }
            }else{
              if(anColumn.value().equals("")){
                wheresql.append(" and "+file.getName()+" = #{"+file.getName()+"},");
              }else{
                wheresql.append(" and "+anColumn.value()+" = #{"+anColumn.value()+"},");
              }
            }
          }else{
            set.append(file.getName()+" = #{"+file.getName()+"} ,");
          }
        }
        if(wheresql.equals(" where 1=1 ")){
          throw new RuntimeException("实体变量没有设置ID字段值");
        }
        sql.append(set.substring(0, set.length()-1)).append(wheresql.substring(0, wheresql.length()-1));
        return sql.toString();
      }
      private String findEntityAll(Class<?> clazz,Object args,boolean islike){
        StringBuilder sql = new StringBuilder();
        StringBuilder whereSQL = new StringBuilder(" where 1=1 ");
        StringBuffer orderby = new StringBuffer();
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("select * from  "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("select * from  "+clazz.getSimpleName());
          }else{
            sql.append("select * from  "+antable.value());
          }
        }
        Object[] paramObjs = (Object[]) ((Map)args).get("array");
        if(paramObjs != null && paramObjs.length>0){
          Object param = paramObjs[0];
          if(param != null ){
            if(param instanceof Map){
              Map<String,Object> map = (Map)param;
              if(map.containsKey("orderby")){
                orderby.append(" order by "+map.get("orderby"));
              }
              if(map.containsKey("sortby")){
                orderby.append(" "+map.get("sortby")+" ");
              }
              for(String key : map.keySet())
              {
                if(islike)
                whereSQL.append(" and "+key + " like '%" + map.get(key)+"%',");
                else whereSQL.append(" and "+key + " = '" + map.get(key)+"',");
              }
              sql.append(whereSQL.subSequence(0,whereSQL.length()-1)).append(orderby);
            }else if(param instanceof Conditions){
              Conditions<String,Object> map = (Conditions)param;
              if(map.containsKey("orderby")){
                orderby.append(" order by "+map.get("orderby"));
              }
              if(map.containsKey("sortby")){
                orderby.append(" "+map.get("sortby")+" ");
              }
              for(String key : map.keySet())
              {
                if(key.equals("orderby") || key.equals("sortby")){
                  continue;
                }
                if(islike)
                whereSQL.append(" and "+key + " like '%" + map.get(key)+"%',");
                else whereSQL.append(" and "+key + " = '" + map.get(key)+"',");
              }
              sql.append(whereSQL.subSequence(0,whereSQL.length()-1)).append(orderby);
            }
          }
        }
        return sql.toString();
      }
      private String findEntityById(Class<?> clazz){
        StringBuilder sql = new StringBuilder();
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("select * from  "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("select * from  "+clazz.getSimpleName());
          }else{
            sql.append("select * from  "+antable.value());
          }
        }
        sql.append("  where 1=1 ");
        Field[] files = clazz.getDeclaredFields();
        boolean falg = false;
        for(Field file : files){
          file.setAccessible(true);
          if(file.isAnnotationPresent(MyBatisColumn.class)){
            MyBatisColumn anColumn = (MyBatisColumn)file.getAnnotation(MYBATISCOLUMN);
            if(anColumn.isID()){//判断字段不为主键
              falg = true;
              if(anColumn.value().equals("")){
                sql.append(" and "+file.getName()+" = #{"+file.getName()+"},");
              }else{
                sql.append(" and "+file.getName()+" = #{"+file.getName()+"},");
              }
            }
          }
        }
        if(!falg){
          throw new RuntimeException("不能通過id查詢實體,實體中沒有定義@mybatisID");
        }
        
        return sql.subSequence(0, sql.length()-1).toString();
      }
      public String deleteById(Class<?> clazz){
        StringBuilder sql = new StringBuilder();
        StringBuilder wheresql = new StringBuilder(" where 1=1 ");
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("delete  "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("delete  "+clazz.getName());
          }else{
            sql.append("delete  "+antable.value());
          }
        }
        Field[] files = clazz.getDeclaredFields();
        for(Field file : files){
          file.setAccessible(true);
          if(file.isAnnotationPresent(MYBATISCOLUMN)){
            MyBatisColumn anColumn = (MyBatisColumn)file.getAnnotation(MYBATISCOLUMN);
            if(anColumn.isID()){//判断字段不为主键
              if(anColumn.value().equals("")){
                wheresql.append(" and "+file.getName()+" = #{"+file.getName()+"},");
              }else{
                wheresql.append(" and "+anColumn.value()+" = #{"+anColumn.value()+"},");
              }
            }
          }
        }
        if(wheresql.equals(" where 1=1 ")){
          throw new RuntimeException("实体变量没有设置ID字段值");
        }
        sql.append(wheresql.substring(0,wheresql.length()-1));
        return sql.toString();
      }
      public String queryByVo(Class<?> clazz,Object args,boolean islike){
        StringBuilder sql = new StringBuilder();
        StringBuilder whereSQL = new StringBuilder(" where 1=1 ");
        StringBuilder orderbySQL = new StringBuilder("");
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("select *  from "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("select * from "+clazz.getName());
          }else{
            sql.append("select * from "+antable.value());
          }
        }
        if(args instanceof Map){
          Map map = (Map)args;
          Object[] arr = (Object[])map.get("param3");
          if(arr.length>0){
            Map<String,Object> params = (Map)arr[0];
            if(params.containsKey("orderby")){
              orderbySQL.append(" order by "+params.get("orderby"));
            }
            if(params.containsKey("sortby")){
              orderbySQL.append(" "+params.get("sortby")+" ");
            }
            for(String key:params.keySet()){
              if(key.equals("orderby") || key.equals("sortby")){
                continue;
              }
              if(islike)
                whereSQL.append(" and "+key + " like '%" + params.get(key)+"%',");
                else whereSQL.append(" and "+key + " = '" + params.get(key)+"',");
            }
          }
        }
        
        return sql.append(whereSQL.substring(0, whereSQL.length()-1)).append(orderbySQL).toString();
      }
      public String count(Class<?> clazz,Object args){
        StringBuilder sql = new StringBuilder();
        StringBuilder whereSQL = new StringBuilder(" where 1=1 ");
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("select count(*)  from "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("select count(*) from "+clazz.getName());
          }else{
            sql.append("select count(*) from "+antable.value());
          }
        }
        Object[] paramObjs = (Object[]) ((Map)args).get("array");
        if(paramObjs != null && paramObjs.length>0){
          Object param = paramObjs[0];
          if(param != null ){
            if(param instanceof Map){
              Map<String,Object> map = (Map)param;
              for(String key : map.keySet())
              {
                whereSQL.append(" and "+key + " = '" + map.get(key)+"',");
              }
              sql.append(whereSQL.subSequence(0,whereSQL.length()-1));
            }else if(param instanceof Conditions){
              Conditions<String,Object> map = (Conditions)param;
              for(String key : map.keySet())
              {
                whereSQL.append(" and "+key + " = '" + map.get(key)+"',");
              }
              sql.append(whereSQL.subSequence(0,whereSQL.length()-1));
            }
          }
        }
        return sql.toString();
      }
      public String deleteByCondition(Class<?> clazz,Object param){
        StringBuilder sql = new StringBuilder();
        StringBuilder whereSQL = new StringBuilder(" where 1=1 ");
        if(!clazz.isAnnotationPresent(MYBATISTABLE)){
          sql.append("delete  "+clazz.getName());
        }else{
          MybatisTable antable = (MybatisTable)clazz.getAnnotation(MYBATISTABLE);
          if(antable.value() == ""){
            sql.append("delete  "+clazz.getName());
          }else{
            sql.append("delete  "+antable.value());
          }
        }
        if(param != null ){
          if(param instanceof Map){
            Map<String,Object> map = (Map)param;
            for(String key : map.keySet())
            {
              whereSQL.append(" and "+key + " = '" + map.get(key)+"',");
            }
            sql.append(whereSQL.subSequence(0,whereSQL.length()-1));
          }else if(param instanceof Conditions){
            Conditions<String,Object> map = (Conditions)param;
            for(String key : map.keySet())
            {
              whereSQL.append(" and "+key + " = '" + map.get(key)+"',");
            }
            sql.append(whereSQL.subSequence(0,whereSQL.length()-1));
          }
        }
        if(whereSQL.equals(" where 1=1 ")){
          throw new RuntimeException("实体变量没有设置ID字段值");
        }
        return sql.toString();
      }
      private static MapperSqlHelper App(){
        return new MapperSqlHelper();
      }
      public static String getExecuSQL(Class<?> clazz, String mapperDBsql,Object param) {
        if(mapperDBsql.equals("MapperGD.find.entitys")){
          return MapperSqlHelper.App().findEntityAll(clazz,param,false);//条件查询实体列表
        }else if(mapperDBsql.equals("MapperGD.find.entityById")){
          return MapperSqlHelper.App().findEntityById(clazz);//id查询实体
        }else if(mapperDBsql.equals("MapperGD.find.ListByLike")){
          return MapperSqlHelper.App().findEntityAll(clazz,param,true);//条件查询实体列表
        }else if(mapperDBsql.equals("MapperGD.insert.entity")){
          return MapperSqlHelper.App().insertEntity(clazz);//保存单一实体
        }else if(mapperDBsql.equals("MapperGD.update.entity")){
          return MapperSqlHelper.App().updateEntityById(clazz);//保存单一实体
        }else if(mapperDBsql.equals("MapperGD.update.entity.condistion")){
          return mapperDBsql;
        }else if(mapperDBsql.equals("MapperGD.delete.id")){
          return MapperSqlHelper.App().deleteById(clazz);
        }else if(mapperDBsql.equals("MapperGD.delete.condition")){
          return MapperSqlHelper.App().deleteByCondition(clazz,param);
        }else if(mapperDBsql.equals("MapperGD.count.condition")){
          return MapperSqlHelper.App().count(clazz,param);
        }else if(mapperDBsql.equals("MapperGD.find.entity.queryByVo")){
          return MapperSqlHelper.App().queryByVo(clazz,param,false);
        }else if(mapperDBsql.equals("MapperGD.find.entity.queryByVoLike")){
          return MapperSqlHelper.App().queryByVo(clazz,param,true);
        }
        return null;
      }
      //预留接口
      private  String updateEntityByConditions(Class<?> clazz,Object param){
        
        return null;
      }
      public void getParam(Object param){
        StringBuffer bf = new StringBuffer();
        if (isPrimitiveType(param.getClass())) {
          bf.append(param);
            } else if (param instanceof Map) {
            	Map<String,Object> map = (Map)param;
            } 
      }
      
      public static boolean isPrimitiveType(Class clazz) {
            return clazz != null && (clazz.isPrimitive() || clazz.equals(Long.class) || clazz.equals(Integer.class)
                    || clazz.equals(Short.class) || clazz.equals(Byte.class) || clazz.equals(Double.class)
                    || clazz.equals(Float.class) || clazz.equals(Boolean.class) || clazz.equals(Character.class) || clazz.equals(String.class));
        }
      public static void main(String[] args) {
    //		Object object = null;
    //		if (isPrimitiveType(object.getClass())) {
    //        } else if (object instanceof Map) {
    //        } 
        Field[] files = User.class.getDeclaredFields();
        User u = new User();
        try {
          System.out.println(FieldUtils.readDeclaredField(u, "id",true));
        } catch (IllegalAccessException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    }
     测试用例:

     

    业务dao,这个接口继承自 IGenericDao

    package org.cf.guott.mybatis.mapper;
    
    import java.util.List;
    import java.util.Map;
    
    import org.apache.ibatis.annotations.Select;
    import org.cf.guott.mybatis.ITestGDAO;
    import org.cf.guott.mybatis.annotation.MyBatisRepository;
    import org.cf.guott.mybatis.entity.User;
    import org.cf.guott.mybatis.pagination.PageMyBatis;
    
    @MyBatisRepository(User.class)
    public interface UserMapper extends IGenericDao<User,Integer>{
    
    }
     main运行方法
    package org.cf.guott.mybatis.main;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.ibatis.session.RowBounds;
    import org.cf.guott.mybatis.entity.User;
    import org.cf.guott.mybatis.helpers.Conditions;
    import org.cf.guott.mybatis.mapper.UserMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Main {
      @Autowired
      UserMapper userMapper;
      public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-mybatis.xml");
        Main main = context.getBean(Main.class);
        Map map = new HashMap();
        map.put("name", "ccc");
        List<User> users = main.userMapper.LikequeryByVo(0,2,new Conditions<String, Object>().add("name", "guotiantdddian").addOrderBy("id").setSortByDesc());
        System.out.println(users.size());
      }
    }

  • 相关阅读:
    UINavigationController的简单使用
    UIApplication
    NSOperation开启线程情况分析
    NSOperation & NSOperationQueue
    GCD 多线程 Ios 补充
    GCD中不同队列 配合 不同函数的 情况分析
    GCD详解
    is,as,sizeof,typeof,GetType
    人人必知的10个jQuery小技巧
    移动设备wap手机网页html5通过特殊链接:打电话,发短信,发邮件详细教程
  • 原文地址:https://www.cnblogs.com/duyinqiang/p/5696765.html
Copyright © 2020-2023  润新知