• [Android]Android端ORM框架——RapidORM(v1.0)


    以下内容为原创,欢迎转载,转载请注明

    来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4748077.html 

    Android上主流的ORM框架有很多,常用的有ORMLite、GreenDao等。

    ORMLite:

    -优点:API很友好,使用比较方便简单稳定,相关功能比较完整。

    -缺点:内部使用反射实现,性能并不是很好。

    GreenDao:

    -优点:性能很不错,

    -缺点:API却不太友好,而且不支持复合主键,主键必须要有并且必须是long或者Long。持久类可以用它提供的模版生成,但是一旦使用了它的模版,持久类、DAO就不能随意去修改,扩展性不是很好。如果不使用它的模版,代码写起来就很繁琐。

    所以结合了两者重新写了一个ORM:RapidORMhttps://github.com/wangjiegulu/RapidORM

    特点:

    1. 支持使用反射和非反射(模版生成)两种方式实现执行SQL。

    2. 支持复合主键

    3. 支持任何主键类型

    4. 兼容android原生的 android.database.sqlite.SQLiteDatabase 和SqlCipher的 net.sqlcipher.database.SQLiteDatabase

    缺点:

    1. 不支持链表查询。

    快速指南:

    一、新建持久类Person:

     1 /**
     2  * Author: wangjie
     3  * Email: tiantian.china.2@gmail.com
     4  * Date: 6/25/15.
     5  */
     6 @Table(propertyClazz = PersonProperty.class)
     7 public class Person implements Serializable{
     8 
     9     @Column(primaryKey = true)
    10     private Integer id;
    11 
    12     @Column(primaryKey = true)
    13     private Integer typeId;
    14 
    15     @Column
    16     private String name;
    17 
    18     @Column
    19     private Integer age;
    20 
    21     @Column
    22     private String address;
    23 
    24     @Column
    25     private Long birth;
    26 
    27     @Column
    28     private Boolean student;
    29 
    30     // getter/setter...
    31 
    32 }

    1. Table注解用于标记这个类需要映射到数据表,RapidORM会创建这个表。

    -name属性:表示对应表的名称,默认为类的simpleName。

    -propertyClazz属性:表示如果不使用反射执行sql时,需要的ModelProperty类的Class,RapidORM会自动在这个ModelProperty中去绑定需要执行SQL的数据。不填写则表示使用默认的反射的方式执行SQL。具体后面会讲到。

    2. Column注解用语标记这个字段映射到的数据表的列(如果)。

    -name属性:表示对应列的名称。默认为字段的名称。

    -primaryKey属性:表示改列是否是主键,默认是false。(可以在多个字段都适用这个属性,表示为复合主键)。

    -autoincrement属性:表示主键是否是自增长,只有该字段是主键并且只有一个主键并且是Integer或int类型才会生效。

    -defaultValue属性:表示该列默认值。

    -notNull属性:表示该列不能为空。

    -unique属性:表示该列唯一约束。

    -uniqueCombo属性:暂未实现。

    -index属性:暂未实现。

    二、注册持久类,并指定SQLiteOpenHelper:

    新建类DatabaseFactory,继承RapidORMConnection:

     1 /**
     2  * Author: wangjie
     3  * Email: tiantian.china.2@gmail.com
     4  * Date: 6/25/15.
     5  */
     6 public class DatabaseFactory extends RapidORMConnection<RapidORMDefaultSQLiteOpenHelperDelegate> {
     7     private static final int VERSION = 1;
     8 
     9     private static DatabaseFactory instance;
    10 
    11     public synchronized static DatabaseFactory getInstance() {
    12         if (null == instance) {
    13             instance = new DatabaseFactory();
    14         }
    15         return instance;
    16     }
    17 
    18     private DatabaseFactory() {
    19         super();
    20     }
    21 
    22     @Override
    23     protected RapidORMDefaultSQLiteOpenHelperDelegate getRapidORMDatabaseOpenHelper(@NonNull String databaseName) {
    24         return new RapidORMDefaultSQLiteOpenHelperDelegate(new MyDatabaseOpenHelper(MyApplication.getInstance(), databaseName, VERSION));
    25     }
    26 
    27     @Override
    28     protected List<Class<?>> registerAllTableClass() {
    29         List<Class<?>> allTableClass = new ArrayList<>();
    30         allTableClass.add(Person.class);
    31         // all table class
    32         return allTableClass;
    33     }
    34 }

    DatabaseFactory推荐使用单例,实现RapidORMConnection的getRapidORMDatabaseOpenHelper()方法,该方法需要返回一个RapidORMDatabaseOpenHelperDelegate或者它的子类对象,RapidORMDatabaseOpenHelperDelegate是SQLiteOpenHelper的委托,因为需要兼容android原生的 android.database.sqlite.SQLiteDatabase 和SqlCipher的 net.sqlcipher.database.SQLiteDatabase 两种方式。

    如果使用 android.database.sqlite.SQLiteDatabase ,则可以使用RapidORMDefaultSQLiteOpenHelperDelegate,然后构造方法中传入真正的android.database.sqlite.SQLiteDatabase (也就是这里的MyDatabaseOpenHelper)即可,使用SqlCipher的方式后面再讲。

    还需要实现registerAllTableClass()方法,这里返回一个所有需要映射表的持久类Class集合。

    三、在需要的地方进行初始化RapidORM(比如在登录完成之后):

    DatabaseFactory.getInstance().resetDatabase("hello_rapid_orm.db");

    如上,调用resetDatabase()方法即可初始化数据库。

    四、编写Dao:

    新建PersonDaoImpl继承BaseDaoImpl<Person>(这里简略了PersonDao接口):

     1 /**
     2  * Author: wangjie
     3  * Email: tiantian.china.2@gmail.com
     4  * Date: 6/26/15.
     5  */
     6 public class PersonDaoImpl extends BaseDaoImpl<Person> {
     7     public PersonDaoImpl() {
     8         super(Person.class);
     9     }
    10 
    11 }

    BaseDaoImpl中默认实现了下面的基本方法(T为范型,指具体的持久类):

    -void insert(T model) throws Exception;  // 插入一条数据。

    -void update(T model) throws Exception;  // 更新一条数据。

    -void delete(T model) throws Exception;  // 删除一条数据。

    -void deleteAll() throws Exception;      // 删除表中所有的数据

    -void insertOrReplace(T model) throws Exception;    // 删除或者替换(暂不支持)

    -List<T> queryAll() throws Exception;      // 查询表中所有数据

    -List<T> rawQuery(String sql, String[] selectionArgs) throws Exception;      // 使用原生sql查询表中所有的数据

    -void rawExecute(String sql, Object[] bindArgs) throws Exception;        // 使用原生sql执行一条sql语句

    -void insertInTx(T... models) throws Exception;          // 插入多条数据(在一个事务中)

    -void insertInTx(Iterable<T> models) throws Exception;      // 同上

    -void updateInTx(T... models) throws Exception;          // 更新多条数据(在一个事务中)

    -void updateInTx(Iterable<T> models) throws Exception;      // 同上

    -void deleteInTx(T... models) throws Exception;            // 删除多条数据(在一个事务中)

    -void deleteInTx(Iterable<T> models) throws Exception;        // 同上

    -void executeInTx(RapidORMSQLiteDatabaseDelegate db, RapidOrmFunc1 func1) throws Exception; // 执行一个方法(可多个sql),在一个事务中

    -void executeInTx(RapidOrmFunc1 func1) throws Exception;    // 同上

    面向对象的方式使用Where和Builder来构建sql语句:

    1. Where:

    Where表示一系列的条件语句,含义与SQL中的where关键字一致。

    支持的where操作:

    -eq(String column, Object value)    // 相等条件判断

    -ne(String column, Object value)    // 不相等条件判断

    -gt(String column, Object value)     // 大于条件判断

    -lt(String column, Object value)     // 小于条件判断

    -ge(String column, Object value)     // 大于等于条件

    -le(String column, Object value)     // 小于等于条件

    -in(String column, Object... values)    // 包含条件

    -ni(String column, Object... values)    // 不包含条件

    -isNull(String column)          // null条件判断

    -notNull(String column)         // 不为null条件判断

    -between(String column, Object value1, Object value2)  // BETWEEN ... AND ..." condition

    -like(String column, Object value)      // 模糊匹配条件

    -Where raw(String rawWhere, Object... values)  // 原生sql条件

    -and(Where... wheres)          // 多个Where与

    -or(Where... wheres)          // 多个Where或

    Where举例:

    1     Where.and(
    2                 Where.like(PersonProperty.name.column, "%wangjie%"),
    3                 Where.lt(PersonProperty.id.column, 200),
    4                 Where.or(
    5                         Where.between(PersonProperty.age.column, 19, 39),
    6                         Where.isNull(PersonProperty.address.column)
    7                 ),
    8                 Where.eq(PersonProperty.typeId.column, 1)
    9         )

    如上,

    Line2条件:name模糊匹配wangjie

    Line3条件:id小于200

    Line5条件:age在19到39之间

    Line6条件:address不是null

    Line8条件:typeId等于1

    Line4条件:Line5条件和Line6条件是or关系;

    Line1条件:Line2、Line3、Line4、Line8条件是and关系。

    2. QueryBuilder:

    QueryBuilder用于构建查询语句。

    -setWhere(Where where)    // 设置Where条件;

    -setLimit(Integer limit)      // 设置limit

    -addSelectColumn(String... selectColumn)    // 设置查询的数据列名(可以调用多遍来设置多个,默认是查询所有数据)

    -addOrder(String column, boolean isAsc)     // 设置查询结果的排序(可以调用多遍来设置多个)

    QueryBuilder举例:

     1     public List<Person> findPersonsByWhere() throws Exception {
     2         return queryBuilder()
     3                 .addSelectColumn(PersonProperty.id.column, PersonProperty.typeId.column, PersonProperty.name.column,
     4                         PersonProperty.age.column, PersonProperty.birth.column, PersonProperty.address.column)
     5                 .setWhere(Where.and(
     6                         Where.like(PersonProperty.name.column, "%wangjie%"),
     7                         Where.lt(PersonProperty.id.column, 200),
     8                         Where.or(
     9                                 Where.between(PersonProperty.age.column, 19, 39),
    10                                 Where.isNull(PersonProperty.address.column)
    11                         ),
    12                         Where.eq(PersonProperty.typeId.column, 1)
    13                 ))
    14                 .addOrder(PersonProperty.id.column, false)
    15                 .addOrder(PersonProperty.name.column, true)
    16                 .setLimit(10)
    17                 .query(this);
    18     }

    如上,通过queryBuilder()方法构建一个QueryBuilder,并设置查询的列名、Where、排序、limit等参数,最后调用query()执行查询。

    3. UpdateBuilder:

    UpdateBuilder用于构建更新语句。

    -setWhere(Where where)    // 设置Where条件;

    -addUpdateColumn(String column, Object value)    // 添加需要更新的字段;

    UpdateBuilder举例:

     1     public void updatePerson() throws Exception {
     2         long now = System.currentTimeMillis();
     3         updateBuilder()
     4                 .setWhere(Where.and(
     5                         Where.like(PersonProperty.name.column, "%wangjie%"),
     6                         Where.lt(PersonProperty.id.column, 200),
     7                         Where.or(
     8                                 Where.between(PersonProperty.age.column, 19, 39),
     9                                 Where.isNull(PersonProperty.address.column)
    10                         ),
    11                         Where.eq(PersonProperty.typeId.column, 1)
    12                 ))
    13                 .addUpdateColumn(PersonProperty.birth.column, now)
    14                 .addUpdateColumn(PersonProperty.address.column, "address_" + now).update(this);
    15     }

    如上,通过updateBuilder()方法构建一个UpdateBuilder,并设置要更新的字段,最后调用update()执行查询。

    4. DeleteBuilder: 

    DeleteBuilder用于构建删除语句。

    -setWhere(Where where)    // 设置Where条件;

    DeleteBuilder举例:

     1     public void deletePerson() throws Exception {
     2         deleteBuilder()
     3                 .setWhere(Where.and(
     4                         Where.like(PersonProperty.name.column, "%wangjie%"),
     5                         Where.lt(PersonProperty.id.column, 200),
     6                         Where.or(
     7                                 Where.between(PersonProperty.age.column, 19, 39),
     8                                 Where.isNull(PersonProperty.address.column)
     9                         ),
    10                         Where.eq(PersonProperty.typeId.column, 1)
    11                 ))
    12                 .delete(this);
    13     }

    如上,通过deleteBuilder()方法构建一个DeleteBuilder,并设置Where条件,最后调用delete()执行查询。

    使用模版生成ModelProperty:

    如果你打算使用非反射的方式执行sql,则可以使用ModelPropertyGenerator模版来生成

    1. 新建一个类,在main方法中执行:

    /**
     * Author: wangjie
     * Email: tiantian.china.2@gmail.com
     * Date: 7/2/15.
     */
    public class MyGenerator {
        public static void main(String[] args) throws Exception {
    
            Class tableClazz = Person.class;
            new ModelPropertyGenerator().generate(tableClazz,
                    "example/src/main/java",
                    tableClazz.getPackage().getName() + ".config");
    
        }
    
    }

    如上,generate()方法参数如下:

    generate(Class tableClazz, String outerDir, String packageName)

    参数一:要生成的Property持久类的Class

    参数二:生成的ModelProperty类文件存放目录

    参数三:生成的ModelProperty类文件的包名

    执行完毕后,就会在com.wangjie.rapidorm.example.database.model.config包下生成如下的ModelProperty:

      1 // THIS CODE IS GENERATED BY RapidORM, DO NOT EDIT.
      2 /**
      3 * Property of Person
      4 */
      5 public class PersonProperty implements IModelProperty<Person> {
      6 
      7     public static final ModelFieldMapper id = new ModelFieldMapper(0, "id", "id");
      8     public static final ModelFieldMapper typeId = new ModelFieldMapper(1, "typeId", "typeId");
      9     public static final ModelFieldMapper name = new ModelFieldMapper(2, "name", "name");
     10     public static final ModelFieldMapper age = new ModelFieldMapper(3, "age", "age");
     11     public static final ModelFieldMapper address = new ModelFieldMapper(4, "address", "address");
     12     public static final ModelFieldMapper birth = new ModelFieldMapper(5, "birth", "birth");
     13     public static final ModelFieldMapper student = new ModelFieldMapper(6, "student", "student");
     14 
     15     public PersonProperty() {
     16     }
     17 
     18 
     19     @Override
     20     public void bindInsertArgs(Person model, List<Object> insertArgs) {
     21         Integer id = model.getId();
     22         insertArgs.add(null == id ? null : id);
     23 
     24         Integer typeId = model.getTypeId();
     25         insertArgs.add(null == typeId ? null : typeId);
     26 
     27         String name = model.getName();
     28         insertArgs.add(null == name ? null : name);
     29 
     30         Integer age = model.getAge();
     31         insertArgs.add(null == age ? null : age);
     32 
     33         String address = model.getAddress();
     34         insertArgs.add(null == address ? null : address);
     35 
     36         Long birth = model.getBirth();
     37         insertArgs.add(null == birth ? null : birth);
     38 
     39         Boolean student = model.isStudent();
     40         insertArgs.add(null == student ? null : student ? 1 : 0);
     41 
     42     }
     43 
     44     @Override
     45     public void bindUpdateArgs(Person model, List<Object> updateArgs) {
     46         String name = model.getName();
     47         updateArgs.add(null == name ? null : name);
     48 
     49         Integer age = model.getAge();
     50         updateArgs.add(null == age ? null : age);
     51 
     52         String address = model.getAddress();
     53         updateArgs.add(null == address ? null : address);
     54 
     55         Long birth = model.getBirth();
     56         updateArgs.add(null == birth ? null : birth);
     57 
     58         Boolean student = model.isStudent();
     59         updateArgs.add(null == student ? null : student ? 1 : 0);
     60 
     61     }
     62 
     63     @Override
     64     public void bindPkArgs(Person model, List<Object> pkArgs) {
     65         Integer id = model.getId();
     66         pkArgs.add(null == id ? null : id);
     67 
     68         Integer typeId = model.getTypeId();
     69         pkArgs.add(null == typeId ? null : typeId);
     70 
     71     }
     72 
     73     @Override
     74     public Person parseFromCursor(Cursor cursor) {
     75         Person model = new Person();
     76         int index;
     77         index = cursor.getColumnIndex("id");
     78         if(-1 != index){
     79             model.setId(cursor.isNull(index) ? null : (cursor.getInt(index)));
     80         }
     81 
     82         index = cursor.getColumnIndex("typeId");
     83         if(-1 != index){
     84             model.setTypeId(cursor.isNull(index) ? null : (cursor.getInt(index)));
     85         }
     86 
     87         index = cursor.getColumnIndex("name");
     88         if(-1 != index){
     89             model.setName(cursor.isNull(index) ? null : (cursor.getString(index)));
     90         }
     91 
     92         index = cursor.getColumnIndex("age");
     93         if(-1 != index){
     94             model.setAge(cursor.isNull(index) ? null : (cursor.getInt(index)));
     95         }
     96 
     97         index = cursor.getColumnIndex("address");
     98         if(-1 != index){
     99             model.setAddress(cursor.isNull(index) ? null : (cursor.getString(index)));
    100         }
    101 
    102         index = cursor.getColumnIndex("birth");
    103         if(-1 != index){
    104             model.setBirth(cursor.isNull(index) ? null : (cursor.getLong(index)));
    105         }
    106 
    107         index = cursor.getColumnIndex("student");
    108         if(-1 != index){
    109             model.setStudent(cursor.isNull(index) ? null : (cursor.getInt(index) == 1));
    110         }
    111 
    112         return model;
    113     }
    114 
    115 }

    注意:此Property类一旦生成,则不能修改;如果该持久类相关的注解属性改变,则需要重新生成这个Property类。

    2. 在相应的持久类的@Table注解中设置propertyClazz:

    1 @Table(propertyClazz = PersonProperty.class)
    2 public class Person implements Serializable{

    如何兼容SqlCipher

    1. 新建MySQLCipherOpenHelper,继承net.sqlcipher.database.SQLiteOpenHelper:

     1 import android.content.Context;
     2 import com.wangjie.rapidorm.core.dao.DatabaseProcessor;
     3 import com.wangjie.rapidorm.core.delegate.database.RapidORMSQLiteDatabaseDelegate;
     4 import net.sqlcipher.database.SQLiteDatabase;
     5 import net.sqlcipher.database.SQLiteDatabaseHook;
     6 import net.sqlcipher.database.SQLiteOpenHelper;
     7 
     8 /**
     9  * Author: wangjie
    10  * Email: tiantian.china.2@gmail.com
    11  * Date: 8/17/15.
    12  */
    13 public class MySQLCipherOpenHelper extends SQLiteOpenHelper {
    14 
    15     private RapidORMSQLiteDatabaseDelegate rapidORMSQLiteDatabaseDelegate;
    16 
    17     public MySQLCipherOpenHelper(Context context, String name, int version) {
    18         super(context, name, null, version);
    19     }
    20 
    21     public MySQLCipherOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
    22         super(context, name, factory, version);
    23     }
    24 
    25     public MySQLCipherOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, SQLiteDatabaseHook hook) {
    26         super(context, name, factory, version, hook);
    27     }
    28 
    29     @Override
    30     public void onCreate(SQLiteDatabase db) {
    31 //        super.onCreate(db);
    32         rapidORMSQLiteDatabaseDelegate = new MySQLCipherDatabaseDelegate(db);
    33         DatabaseProcessor databaseProcessor = DatabaseProcessor.getInstance();
    34         for (Class<?> tableClazz : databaseProcessor.getAllTableClass()) {
    35             databaseProcessor.createTable(rapidORMSQLiteDatabaseDelegate, tableClazz, true);
    36         }
    37     }
    38 
    39     @Override
    40     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    41 //        super.onUpgrade(db, oldVersion, newVersion);
    42         rapidORMSQLiteDatabaseDelegate = new MySQLCipherDatabaseDelegate(db);
    43         DatabaseProcessor databaseProcessor = DatabaseProcessor.getInstance();
    44         // todo: dev only!!!!!
    45         databaseProcessor.dropAllTable(rapidORMSQLiteDatabaseDelegate);
    46 
    47     }
    48 }

    2. 新建MySQLCipherDatabaseDelegate继承RapidORMSQLiteDatabaseDelegate,并实现以下的方法:

     1 import android.database.Cursor;
     2 import android.database.SQLException;
     3 import com.wangjie.rapidorm.core.delegate.database.RapidORMSQLiteDatabaseDelegate;
     4 import net.sqlcipher.database.SQLiteDatabase;
     5 
     6 /**
     7  * Author: wangjie
     8  * Email: tiantian.china.2@gmail.com
     9  * Date: 8/17/15.
    10  */
    11 public class MySQLCipherDatabaseDelegate extends RapidORMSQLiteDatabaseDelegate<SQLiteDatabase> {
    12     public MySQLCipherDatabaseDelegate(SQLiteDatabase db) {
    13         super(db);
    14     }
    15 
    16     @Override
    17     public void execSQL(String sql) throws SQLException {
    18         db.execSQL(sql);
    19     }
    20 
    21     @Override
    22     public boolean isDbLockedByCurrentThread() {
    23         return db.isDbLockedByCurrentThread();
    24     }
    25 
    26     @Override
    27     public void execSQL(String sql, Object[] bindArgs) throws Exception {
    28         db.execSQL(sql, bindArgs);
    29     }
    30 
    31     @Override
    32     public Cursor rawQuery(String sql, String[] selectionArgs) {
    33         return db.rawQuery(sql, selectionArgs);
    34     }
    35 
    36     @Override
    37     public void beginTransaction() {
    38         db.beginTransaction();
    39     }
    40 
    41     @Override
    42     public void setTransactionSuccessful() {
    43         db.setTransactionSuccessful();
    44     }
    45 
    46     @Override
    47     public void endTransaction() {
    48         db.endTransaction();
    49     }
    50 
    51     @Override
    52     public void close() {
    53         db.close();
    54     }
    55 }

    注意这里的import,这里的SQLiteDatabase需要导入 net.sqlcipher.database.SQLiteDatabase 。

    3. 新建MySQLCipherOpenHelperDelegate继承RapidORMDatabaseOpenHelperDelegate,实现如下方法:

     1 import com.wangjie.rapidorm.core.delegate.openhelper.RapidORMDatabaseOpenHelperDelegate;
     2  3 
     4 /**
     5  * Author: wangjie
     6  * Email: tiantian.china.2@gmail.com
     7  * Date: 6/18/15.
     8  */
     9 public class MySQLCipherOpenHelperDelegate extends RapidORMDatabaseOpenHelperDelegate<MySQLCipherOpenHelper, MySQLCipherOpenHelperDelegate> {
    10     public MySQLCipherOpenHelperDelegate(MySQLCipherOpenHelper sqLiteOpenHelper) {
    11         super(sqLiteOpenHelper);
    12     }
    13 
    14     public static final String SECRET_KEY = "1234567890abcdef";
    15 
    16     @Override
    17     public MySQLCipherOpenHelperDelegate getReadableDatabase() {
    18         return new MySQLCipherDatabaseDelegate(openHelper.getReadableDatabase(SECRET_KEY));
    19     }
    20 
    21     @Override
    22     public MySQLCipherOpenHelperDelegate getWritableDatabase() {
    23         return new MySQLCipherDatabaseDelegate(openHelper.getWritableDatabase(SECRET_KEY));
    24     }
    25 
    26 }

    4. 在DatabaseFactory中使用SqlCipher版本:

     1 /**
     2  * Author: wangjie
     3  * Email: tiantian.china.2@gmail.com
     4  * Date: 6/25/15.
     5  */
     6 public class DatabaseFactory extends RapidORMConnection<MySQLCipherOpenHelperDelegate> {
     7     private static final int VERSION = 1;
     8 
     9     private static DatabaseFactory instance;
    10 
    11     public synchronized static DatabaseFactory getInstance() {
    12         if (null == instance) {
    13             instance = new DatabaseFactory();
    14         }
    15         return instance;
    16     }
    17 
    18     private DatabaseFactory() {
    19         super();
    20     }
    21 
    22     @Override
    23     protected MySQLCipherOpenHelperDelegate getRapidORMDatabaseOpenHelper(@NonNull String databaseName) {
    24         return new MySQLCipherOpenHelperDelegate(new MySQLCipherOpenHelper(applicationContext, databaseName, VERSION));
    25     }
    26 
    27     @Override
    28     protected List<Class<?>> registerAllTableClass() {
    29         List<Class<?>> allTableClass = new ArrayList<>();
    30         allTableClass.add(Person.class);
    31         // register all table class here...
    32         return allTableClass;
    33     }
    34 }
  • 相关阅读:
    Generator函数介绍
    C语言基础三
    C语言基础二
    C语言基础一
    node——路由控制
    Node.js_HTTP模块
    node_Express安装及检验
    conda Pyhon版本切换
    JAVA泛型里面各值代表的意义
    jq实现表格多行列复制
  • 原文地址:https://www.cnblogs.com/tiantianbyconan/p/4748077.html
Copyright © 2020-2023  润新知