android sqlite数据库封装 实现crud
android常用的数据保存方式有文件、sharepreferences、数据库、网络、contentprovider集中方式。
文件存储方式,经常使用在缓存整个页面数据,比如电子书内容、html数据等。
sharepreferrences存储方式,实质也就是xml文件存储的封装,常用于存储配置参数数据。当然也可以用文件存储+Properties来存储参数数据。
网络,就是将数据存储在网络空间上。
contentprovider主要作用是统一数据存储方式,实现数据共享,以后有机会仔细分析下。
数据库的方式,常用在存储一系列的结构复杂的数据,轻量级的数据库SQlit使用起来还是比较简单,但是总想能像hibernate似的框架可以进行下封装,实现orm并且可以实现简单的rcud。这里就介绍下一个分装过程,源码下载在后面。
一、封装数据库的类结构
结构比较简单:BaseBean.java--实体类的基类
DBConfig.java---数据库的参数,包括数据库名、要创建的表列表、数据库版本等
IssContentProvider.java--继承了ContentProvider,定义了数据库的初始化和操作
DBFactory--数据库工厂类
Table.java----定义Table的annotation
TableColumn.java--定义表的列的annotation和属性
TableUtil.java--书库操作工具类,主要是获得表和根据标签拼装sql语句
二、封装数据库的使用过程
由于封装后的数据库使用比较简单,就跟配置好hibernate之后使用似的,所以咱们先看下咱们使用,不理解的地方,等分析了整个实现过程后就行清晰了。
1、要实现orm,肯定要定义带标签的实体类,当然是继承BaseBean类。
2、要将数据库参数传递给DBConfig,并初始化。
3、数据库操作类,通过contentprovideder实现crud。
用例子看下
1,定义实体类
- public class SmartDownloadBean extends BaseBean<SmartDownloadBean> {
- @TableColumn(type = TableColumn.Types.TEXT, isIndex = true, isNotNull = true)
- public String downpath;
- @TableColumn(type = TableColumn.Types.INTEGER)
- public int threadid;
- @TableColumn(type = TableColumn.Types.INTEGER)
- public int downlength;
- @Override
- public SmartDownloadBean parseJSON(JSONObject jsonObj) {
- return null;
- }
- @Override
- public JSONObject toJSON() {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public SmartDownloadBean cursorToBean(Cursor cursor) {
- this.downpath = cursor.getString(cursor.getColumnIndex("downpath"));
- this.threadid = cursor.getInt(cursor.getColumnIndex("threadid"));
- this.downlength = cursor.getInt(cursor.getColumnIndex("downlength"));
- return this;
- }
- @Override
- public ContentValues beanToValues() {
- ContentValues values = new ContentValues();
- if (!TextUtils.isEmpty(downpath)) {
- values.put("downpath", downpath);
- }
- if (!TextUtils.isEmpty(threadid+"")) {
- values.put("threadid", threadid);
- }
- if (!TextUtils.isEmpty(downlength+"")) {
- values.put("downlength", downlength);
- }
- return values;
- }
- }
实体类通过标签,定义了对应表的列名、及列的属性
2、定义要创建的表、数据名等参数
- /**
- * 数据库配置
- **/
- public class SssProvider extends IssContentProvider {
- @Override
- public void init() {
- // 数据库相关参数设置
- DBConfig config = new DBConfig.Builder()
- .addTatble(SmartDownloadBean.class)
- .setName("sss.db").setVersion(2)
- .setAuthority("com.sss").build();
- IssDBFactory.init(getContext(), config);
- }
- }
要定义都个表的话,再addTatble(Bean.class)即可。
3、调用数据库的工具类
- /**
- * 操作数据库的utils
- *
- * @author dllik 2013-11-23
- */
- public class DBUtils {
- public static Uri URI_SMARTDOWNLOAD = IssContentProvider.buildUri(SmartDownloadBean.class);
- /**
- * 获取每条线程已经下载的文件长度
- *
- * @param context
- * @param downpath
- * @return
- */
- public static Map<Integer, Integer> querySmartDownData(Context context, String downpath) {
- ContentResolver mResolver = context.getContentResolver();
- Cursor cursor = mResolver.query(URI_SMARTDOWNLOAD, null, "downpath=?", new String[] {
- downpath
- }, null);
- Map<Integer, Integer> data = new HashMap<Integer, Integer>();
- while (cursor.moveToNext()) {
- SmartDownloadBean bean = new SmartDownloadBean();
- bean.cursorToBean(cursor);
- data.put(bean.threadid, bean.downlength);
- }
- cursor.close();
- return data;
- }
- /**
- * 保存每条线程已经下载的文件长度
- *
- * @param context
- * @param path
- * @param map
- */
- public static void insertSmartDown(Context context, String path, Map<Integer, Integer> map) {
- ContentResolver mResolver = context.getContentResolver();
- for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
- SmartDownloadBean bean = new SmartDownloadBean();
- bean.downpath = path;
- bean.downlength = entry.getValue();
- bean.threadid = entry.getKey();
- mResolver.insert(URI_SMARTDOWNLOAD, bean.beanToValues());
- }
- }
- /**
- * 实时更新每条线程已经下载的文件长度
- *
- * @param context
- * @param path
- * @param map
- */
- public static void updateSmartDown(Context context, String path, Map<Integer, Integer> map) {
- ContentResolver mResolver = context.getContentResolver();
- for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
- SmartDownloadBean bean = new SmartDownloadBean();
- bean.downpath = path;
- bean.downlength = entry.getValue();
- bean.threadid = entry.getKey();
- mResolver.update(URI_SMARTDOWNLOAD, bean.beanToValues(), "downpath=? and threadid=?",
- new String[] {
- bean.downpath, bean.threadid + ""
- });
- }
- }
- /**
- * 当文件下载完成后,删除对应的下载记录
- *
- * @param context
- * @param path
- */
- public static void deleteSmartDown(Context context, String path) {
- ContentResolver mResolver = context.getContentResolver();
- mResolver.delete(URI_SMARTDOWNLOAD, "downpath=?", new String[] {
- path
- });
- }
- }
三、数据库的封装过程
看下实体类的基类
- public abstract class BaseBean<T> implements Serializable {
- private static final long serialVersionUID = -804757173578073135L;
- @TableColumn(type = TableColumn.Types.INTEGER, isPrimary = true)
- public static final String _ID = "_id";
- /**
- * 将json对象转化为Bean实例
- *
- * @param jsonObj
- * @return
- */
- public abstract T parseJSON(JSONObject jsonObj);
- /**
- * 将Bean实例转化为json对象
- *
- * @return
- */
- public abstract JSONObject toJSON();
- /**
- * 将数据库的cursor转化为Bean实例(如果对象涉及在数据库存取,需实现此方法)
- *
- * @param cursor
- * @return
- */
- public abstract T cursorToBean(Cursor cursor);
- /**
- * 将Bean实例转化为一个ContentValues实例,供存入数据库使用(如果对象涉及在数据库存取,需实现此方法)
- *
- * @return
- */
- public abstract ContentValues beanToValues();
- @SuppressWarnings("unchecked")
- public T parseJSON(Gson gson, String json) {
- return (T) gson.fromJson(json, this.getClass());
- }
- public ContentValues toValues() {
- ContentValues values = new ContentValues();
- try {
- Class<?> c = getClass();
- Field[] fields = c.getFields();
- for (Field f : fields) {
- f.setAccessible(true);
- final TableColumn tableColumnAnnotation = f.getAnnotation(TableColumn.class);
- if (tableColumnAnnotation != null) {
- if (tableColumnAnnotation.type() == TableColumn.Types.INTEGER) {
- values.put(f.getName(), f.getInt(this));
- } else if (tableColumnAnnotation.type() == TableColumn.Types.BLOB) {
- values.put(f.getName(), (byte[]) f.get(this));
- } else if (tableColumnAnnotation.type() == TableColumn.Types.TEXT) {
- values.put(f.getName(), f.get(this).toString());
- } else {
- values.put(f.getName(), f.get(this).toString());
- }
- }
- }
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- return values;
- }
- }
说明几点:1、用到了泛型,因为定义的数据实体类有多个
2、实现序列化,实体类数据通过Intent进行传递
3、定义了一个主键id
数据库参数类
- public class DBConfig {
- final ArrayList<Class<? extends BaseBean<?>>> tableList;
- final String dbName;
- final int dbVersion;
- final String authority;
- final ArrayList<String> tableNameList;
- private DBConfig(final Builder builder) {
- tableList = builder.tableList;
- dbName = builder.dbName;
- dbVersion = builder.dbVersion;
- authority = builder.authority;
- tableNameList = new ArrayList<String>();
- for(Class<? extends BaseBean<?>> c:tableList){
- String name = TableUtil.getTableName(c);
- tableNameList.add(name);
- }
- }
- public static class Builder {
- private ArrayList<Class<? extends BaseBean<?>>> tableList;
- private String dbName;
- private int dbVersion;
- private String authority = "com.iss.mobile";
- public Builder() {
- tableList = new ArrayList<Class<? extends BaseBean<?>>>();
- }
- public Builder setName(String name) {
- dbName = name;
- return this;
- }
- public Builder setVersion(int version) {
- dbVersion = version;
- return this;
- }
- public Builder addTatble(Class<? extends BaseBean<?>> table) {
- tableList.add(table);
- return this;
- }
- public Builder setAuthority(String authority){
- this.authority = authority;
- return this;
- }
- public DBConfig build(){
- return new DBConfig(this);
- }
- }
- }
通过该类,来设置数据库的参数,在初始化数据库的时候用到。
内容提供者类,初始化和操作数据库
- public abstract class IssContentProvider extends ContentProvider {
- public static String CONTENT_TYPE = "vnd.android.cursor.dir/iss.db";
- protected SQLiteDatabase mDB;
- public static String AUTHORITY = "com.iss.mobile";
- @Override
- public boolean onCreate() {
- init();
- IssDBFactory issDBFactory = IssDBFactory.getInstance();
- DBConfig config = IssDBFactory.getInstance().getDBConfig();
- if (config == null) {
- throw new RuntimeException("db factory not init");
- }
- AUTHORITY = config.authority;
- CONTENT_TYPE = "vnd.android.cursor.dir/" + config.dbName;
- mDB = issDBFactory.open();
- return true;
- }
- public abstract void init();
- public static final String SCHEME = "content";
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- String tableName = getTableName(uri);
- long result = mDB.insert(tableName, null, values);
- if (result != -1) {
- getContext().getContentResolver().notifyChange(uri, null);
- }
- return buildResultUri(tableName, result);
- }
- @Override
- public int bulkInsert(Uri uri, ContentValues[] values) {
- mDB.beginTransaction();
- String tableName = getTableName(uri);
- for(ContentValues value:values){
- mDB.insert(tableName, null, value);
- }
- mDB.setTransactionSuccessful();
- mDB.endTransaction();
- return values.length;
- }
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- String tableName = getTableName(uri);
- return mDB.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
- }
- @Override
- public String getType(Uri uri) {
- return CONTENT_TYPE;
- }
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- String tableName = getTableName(uri);
- int result = mDB.delete(tableName, selection, selectionArgs);
- if (result != 0) {
- getContext().getContentResolver().notifyChange(uri, null);
- }
- return result;
- }
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- String tableName = getTableName(uri);
- int result = mDB.update(tableName, values, selection, selectionArgs);
- if (result != 0) {
- getContext().getContentResolver().notifyChange(uri, null);
- }
- return result;
- }
- private Uri buildResultUri(String tableName, long result) {
- final Uri.Builder builder = new Uri.Builder();
- DBConfig config = IssDBFactory.getInstance().getDBConfig();
- if (config == null) {
- throw new RuntimeException("db factory not init");
- }
- builder.scheme(SCHEME);
- builder.authority(config.authority);
- builder.path(tableName);
- builder.appendPath(String.valueOf(result));
- return builder.build();
- }
- private String getTableName(Uri uri) {
- DBConfig config = IssDBFactory.getInstance().getDBConfig();
- if (config == null) {
- throw new RuntimeException("db factory not init");
- }
- String path = uri.getLastPathSegment();
- if (!config.tableNameList.contains(path)) {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- return path;
- }
- public static Uri buildUri(String path, String id) {
- final Uri.Builder builder = new Uri.Builder();
- DBConfig config = IssDBFactory.getInstance().getDBConfig();
- if (config == null) {
- throw new RuntimeException("db factory not init");
- }
- builder.scheme(SCHEME);
- builder.authority(config.authority);
- builder.path(path);
- builder.appendPath(id);
- return builder.build();
- }
- public static Uri buildUri(String path) {
- final Uri.Builder builder = new Uri.Builder();
- DBConfig config = IssDBFactory.getInstance().getDBConfig();
- if (config == null) {
- throw new RuntimeException("db factory not init");
- }
- builder.scheme(SCHEME);
- builder.authority(config.authority);
- builder.path(path);
- return builder.build();
- }
- public static Uri buildUri(Class<? extends BaseBean<?>> c) {
- final String tableName = TableUtil.getTableName(c);
- return buildUri(tableName);
- }
- }
该内容提供者在创建的时候,先执行init()(将要数据库的参数设置好,再将参数传递给工厂类),工厂类根据参数创建数据库mDB = issDBFactory.open();
数据库工厂类:
- public class IssDBFactory {
- private static final String TAG = IssDBFactory.class.getSimpleName();
- private DBConfig mConfig;
- private SQLiteDatabase mSQLiteDB;
- private IssDBOpenHelper mDBOpenHelper;
- private final Context mContext;
- private static IssDBFactory instance ;
- private IssDBFactory(Context context) {
- mContext = context;
- }
- public static void init(Context context,DBConfig dbConfig){
- if(instance==null){
- instance = new IssDBFactory(context.getApplicationContext());
- instance.setDBConfig(dbConfig);
- }
- }
- public static IssDBFactory getInstance(){
- return instance;
- }
- public void setDBConfig(DBConfig dbConfig){
- mConfig = dbConfig;
- }
- public DBConfig getDBConfig(){
- return mConfig;
- }
- public SQLiteDatabase open() {
- if(mSQLiteDB==null){
- mDBOpenHelper = new IssDBOpenHelper(mContext, mConfig.dbName, null, mConfig.dbVersion);
- mSQLiteDB = mDBOpenHelper.getWritableDatabase();
- }
- return mSQLiteDB;
- }
- public void close() {
- if(mDBOpenHelper!=null){
- mDBOpenHelper.close();
- }
- }
- public void beginTransaction() {
- if(mSQLiteDB==null){
- mSQLiteDB.beginTransaction();
- }
- }
- public void endTransaction() {
- if (mSQLiteDB==null&&mSQLiteDB.inTransaction()) {
- mSQLiteDB.endTransaction();
- }
- }
- public void setTransactionSuccessful() {
- if (mSQLiteDB==null){
- mSQLiteDB.setTransactionSuccessful();
- }
- }
- private final class IssDBOpenHelper extends SQLiteOpenHelper {
- public IssDBOpenHelper(Context context, String name, CursorFactory factory, int version) {
- super(context, name, factory, version);
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- for (Class<? extends BaseBean<?>> table : mConfig.tableList) {
- try {
- for (String statment : TableUtil.getCreateStatments(table)) {
- Log.d(TAG, statment);
- db.execSQL(statment);
- }
- } catch (Throwable e) {
- Log.e(TAG, "Can't create table " + table.getSimpleName());
- }
- }
- /**
- * 初始化数据
- */
- // initData();
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- Log.d(TAG, "onUpgrade: " + oldVersion + " >> " + newVersion);
- for (Class<? extends BaseBean<?>> table : mConfig.tableList) {
- try {
- db.execSQL("DROP TABLE IF EXISTS " + TableUtil.getTableName(table));
- } catch (Throwable e) {
- Log.e(TAG, "Can't create table " + table.getSimpleName());
- }
- }
- onCreate(db);
- }
- }
- public void cleanTable(String tableName, int maxSize, int batchSize) {
- Cursor cursor = mSQLiteDB.rawQuery("select count(_id) from " + tableName, null);
- if (cursor.getCount() != 0 && cursor.moveToFirst() && !cursor.isAfterLast()) {
- if (cursor.getInt(0) >= maxSize) {
- int deleteSize = maxSize - batchSize;
- mSQLiteDB.execSQL("delete from " + tableName + " where _id in (" + "select _id from " + tableName
- + " order by _id " + " limit " + deleteSize + " )");
- }
- }
- cursor.close();
- }
看到这用过数据库的就比较清晰了,用到了SQLiteOpenHelper是以内部类的形式实现的,在oncreat里创建了表,在onupgrade里实现了更新表。
其中用到TableUtil.getCreateStatments(table),
数据库工具类:
- public class TableUtil {
- public static String getTableName(Class<? extends BaseBean<?>> c) {
- String name = null;
- Table tableNameAnnotation = c.getAnnotation(Table.class);
- if (tableNameAnnotation != null) {
- name = tableNameAnnotation.name();
- }
- if (TextUtils.isEmpty(name)) {
- name = c.getSimpleName();
- }
- return name;
- }
- /**
- * 拼装sql用的建表语句以及索引语句
- *
- * @param c
- * @return
- */
- public final static List<String> getCreateStatments(Class<? extends BaseBean<?>> c) {
- final List<String> createStatments = new ArrayList<String>();
- final List<String> indexStatments = new ArrayList<String>();
- final StringBuilder builder = new StringBuilder();
- final String tableName = getTableName(c);
- builder.append("CREATE TABLE ");
- builder.append(tableName);
- builder.append(" (");
- int columnNum = 0;
- for (final Field f : c.getFields()) {
- f.setAccessible(true);
- final TableColumn tableColumnAnnotation = f.getAnnotation(TableColumn.class);
- if (tableColumnAnnotation != null) {
- columnNum++;
- String columnName = f.getName();
- builder.append(columnName);
- builder.append(" ");
- if (tableColumnAnnotation.type() == TableColumn.Types.INTEGER) {
- builder.append(" INTEGER");
- } else if (tableColumnAnnotation.type() == TableColumn.Types.BLOB) {
- builder.append(" BLOB");
- } else if (tableColumnAnnotation.type() == TableColumn.Types.TEXT) {
- builder.append(" TEXT");
- } else {
- builder.append(" DATETIME");
- }
- if (tableColumnAnnotation.isPrimary()) {
- builder.append(" PRIMARY KEY");
- } else {
- if (tableColumnAnnotation.isNotNull()) {
- builder.append(" NOT NULL");
- }
- if (tableColumnAnnotation.isUnique()) {
- builder.append(" UNIQUE");
- }
- }
- if (tableColumnAnnotation.isIndex()) {
- indexStatments.add("CREATE INDEX idx_" + columnName + "_" + tableName + " ON "
- + tableName + "(" + columnName + ");");
- }
- builder.append(", ");
- }
- }
- builder.setLength(builder.length() - 2); // remove last ','
- builder.append(");");
- if (columnNum > 0) {
- createStatments.add(builder.toString());
- createStatments.addAll(indexStatments);
- }
- return createStatments;
- }
- }
就两个方法,获取表名,在更新表的时候用到,拼装sql在创建表时候用到。
最后两个标签类:
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Table {
- String name();
- }
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TableColumn {
- public enum Types {
- INTEGER, TEXT, BLOB, DATETIME
- }
- Types type() default Types.TEXT;
- boolean isPrimary() default false;
- boolean isIndex() default false;
- boolean isNotNull() default false;
- boolean isUnique() default false;
- }
思路比较简单,就是注意先标签、过滤器、内容提供者的使用就行了。
最后是封装包代码,使用过程没有加,自己加入吧:http://download.csdn.net/detail/xiangxue336/7001299
android sqlite数据库封装 实现crud的更多相关文章
- Android SQLite 数据库 增删改查操作
Android SQLite 数据库 增删改查操作 转载▼ 一.使用嵌入式关系型SQLite数据库存储数据 在Android平台上,集成了一个嵌入式关系型数据库--SQLite,SQLite3支持NU ...
- Android SQLite 数据库详细介绍
Android SQLite 数据库详细介绍 我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用 ...
- Android Sqlite 数据库版本更新
Android Sqlite 数据库版本更新 http://87426628.blog.163.com/blog/static/6069361820131069485844/ 1.自己写一个类继承 ...
- Android sqlite数据库存取图片信息
Android sqlite数据库存取图片信息 存储图片:bitmap private byte[] getIconData(Bitmap bitmap){ int size = bitmap.get ...
- 图解IntelliJ IDEA 13版本对Android SQLite数据库的支持
IntelliJ IDEA 13版本的重要构建之一是支持Android程序开发.当然对Android SQLite数据库的支持也就成为了Android开发者对IntelliJ IDEA 13版本的绝对 ...
- Android——SQLite/数据库 相关知识总结贴
android SQLite简介 http://www.apkbus.com/android-1780-1-1.html Android SQLite基础 http://www.apkbus.com/ ...
- Android SQLite数据库增删改查操作
一.使用嵌入式关系型SQLite数据库存储数据 在Android平台上,集成了一个嵌入式关系型数据库——SQLite,SQLite3支持NULL.INTEGER.REAL(浮点数字). TEXT(字符 ...
- Android SQLite数据库使用
在Android开发中SQLite起着很重要的作用,网上SQLite的教程有很多很多,不过那些教程大多数都讲得不是很全面.本人总结了一些SQLite的常用的方法,借着论坛的大赛,跟大家分享分享的.一. ...
- [Android] Sqlite 数据库操作 工具封装类
sqlite 数据库封装类 DatabaseUtil.java(封装的类) package com.jack.androidbase.tools; import android.content.Con ...
随机推荐
- iptables简述
一.linux防火墙基础防火墙分为硬件防火墙和软件防火墙. 1.概述linux 防火墙体系主要工作在网络层,针对TCP/IP数据包实施过滤和限制,属于典型的包过滤防火墙. 包过滤机制:ne ...
- DateBox( 日期输入框) 组件
本节课重点了解 EasyUI 中 DateBox(日期输入框)组件的使用方法,这个组件依赖于 Combo(自定义下拉框)和 Calendar(日历). 一. 加载方式//class 加载方式<i ...
- 关于使用Jsonp做跨域请求
今天在使用Jsonp做跨域请求的练习时碰上这样一个问题 代码如下 <!DOCTYPE html> <html> <head> <meta charset=&q ...
- Error:Could not determine Java version-- 关于Android Studio JDK设置和JVM version设置
最近在装AS的时候遇到一个问题,新建工程后,编译报错,Error:Could not determine Java version 不言而喻:可定是JDK的问题,网上查到2中可能性 第一:就是JDK路 ...
- centos静默式安装Oracle11g
1. Centos及Oracle版本 Centos:CentOS release 6.4 (Final) Oracle:linux.x64_Oracle_11gR2_database 2. 硬 ...
- Symfony2 HttpKernel事件驱动
HttpKernel:事件驱动 Symfony2 框架层和应用层的工作都是在 HttpKernel::handle()方法中完成,HttpKernel::handle() 的内部的实现其实 ...
- PHP数据过滤
1.php提交数据过滤的基本原则 1)提交变量进数据库时,我们必须使用addslashes()进行过滤,像我们的注入问题,一个addslashes()也就搞定了.其实在涉及到变量取值时,intval ...
- python中的model模板中的数据类型
mode对应的类型 见 : https://docs.djangoproject.com/en/1.8/ref/models/fields/ 命令行ipython查看 from django.db i ...
- 读取Excel文件内容在Web上显示
点击事件代码.cs protected void Button1_Click(object sender, EventArgs e) { string strPath = "d:/test. ...
- ExtJS4.2.1
ExtJS4.2.1 1. 介绍 1.1 说明 ExtJS是一个用javascript.CSS和HTML等技术实现的主要用于创建RIA即富客户端,且与后台技术无关的前端Ajax框架. 常用于企业内部管 ...