SQLite是一个开源的关系型数据库,实现自容纳、零配置、反对事务的SQL数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、牢靠。并且SQLite是在世界上最宽泛部署的 SQL 数据库引擎。SQLite 源代码不受版权限度。 本篇文章书要是记述了SQLite的根本架构以及SQLite的几种操作形式,其中比拟重要的就是ADB Shell命令操作与SQL语句,另外在开发中还是Litepal这款开源ORM框架用的比拟多一些,应用起来的确十分不便。

<!-- more -->

SQLite根本构造

接口由SQLite C API组成,程序与SQLite交互的根底就是用C语言编写的API,JDBC也只是JNI调用而已。

在编译器中,词法分析器与语法分析器把SQL翻译为语法树,Code Generator依据语法树生产SQLite的汇编代码,交给虚拟机执行。

虚拟机,与Java虚拟机执行class中的指令相似,SQLite的汇编代码由SQLite虚拟机来执行,由虚拟机负责SQL到数据存取的交互,对于虚拟机的更多内容能够查看官网《The Virtual Database Engine of SQLite》

更多对于SQLite架构的内容能够查看官网《 Architecture of SQLite 》, 外面介绍的比拟具体。

SQLite数据类型

类型类型阐明
NULL这个值为空值
VARCHAR(n)长度不固定且其最大长度为 n 的字串,n不能超过 4000
CHAR(n)长度固定为n的字串,n不能超过 254
INTEGER值被标识为整数,根据值的大小能够顺次被存储为1,2,3,4,5,6,7,8
REAL所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号
TEXT值为文本字符串,应用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE)
BLOB值是BLOB数据块,以输出的数据格式进行存储。如何输出就如何存储,不扭转格局
DATA蕴含了 年份、月份、日期
TIME蕴含了 小时、分钟、秒

Android中操作SQLite

SQLite的SQL语句其实和一般SQL没什么特地的不同,Windows下可视化操作SQLite能够应用SQLite Expert Personal 4 - 这款工具,下载地址如下:http://www.sqliteexpert.com/v4/SQLiteExpertPersSetup64.exe。关上后即可通过图形化界面的形式操作SQLite,同样也能够通过SQL语句来操作:

# 建表create table stu_info (       id integer primary key autoincrement,       name varchar(30) not null,       age integer ,       gender varchar(2) not null)# 插入数据insert into stu_info(name, age, gender) values ('Mike', 24, '女');insert into stu_info(name, age, gender) values ('Jone', 26, '男');insert into stu_info(name, age, gender) values ('Tom', 28, '女');# 查问数据select * from stu_info;# 删除数据delete from stu_info where id = 13;# 批改数据update stu_info set name = 'Jack' where id = 10;# 按条件查问select * from stu_info where age = 24; 

当初次要还是看看在Android平台如何应用吧!SQLiteOpenHelper:Android平台里一个数据库辅助类,用于创立或关上数据库,并且对数据库的创立和版本进行治理。

SQLiteDatabase:用于治理和操作SQLite数据库,简直所有的数据库操作,最终都将由这个类实现。

ADB Shell操作SQLite

关上CMD窗口,输出adb shell,找到sqlite文件,通过sqlite3 + sqlite文件名就能够进入sqlite shell:

命令作用
.database显示数据库信息
.tables显示表名称
.schema命令能够查看创立数据表时的SQL命令
.schema table_name查看创立表table_name时的SQL的命令

SQLiteDatabase + SQL语句

上面这个例子是通过SQLiteDatabase + SQL语句来操作数据库,即须要本人手动拼接SQL,如果是插入数据、批改数据、删除数据都是用sqLiteDatabase.execSQL(insertSQL/updateSQL/deleteSQL)来实现的,查问数据时应用sqLiteDatabase.rawQuery()办法来实现,因为应用起来须要拼接SQL,略微麻烦一点。

main_activity.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:padding="20dp"    tools:context=".MainActivity">    <EditText        android:id="@+id/et_name"        android:hint="姓名"        android:layout_width="match_parent"        android:layout_height="wrap_content"/>    <EditText        android:id="@+id/et_age"        android:hint="年龄"        android:inputType="number"        android:layout_width="match_parent"        android:layout_height="wrap_content"/>    <RadioGroup        android:id="@+id/rg_sex"        android:orientation="horizontal"        android:layout_width="match_parent"        android:layout_height="wrap_content">        <RadioButton            android:id="@+id/rb_male"            android:text="男"            android:layout_width="wrap_content"            android:layout_height="wrap_content"/>        <RadioButton            android:id="@+id/rb_female"            android:text="女"            android:layout_width="wrap_content"            android:layout_height="wrap_content"/>    </RadioGroup>    <LinearLayout        android:orientation="horizontal"        android:layout_width="match_parent"        android:layout_height="wrap_content">        <Button            android:id="@+id/btn_add"            android:text="增加"            android:onClick="operatorData"            android:layout_width="wrap_content"            android:layout_height="wrap_content"/>        <Button            android:id="@+id/btn_update"            android:text="批改"            android:onClick="operatorData"            android:layout_width="wrap_content"            android:layout_height="wrap_content"/>        <Button            android:id="@+id/btn_delete"            android:onClick="operatorData"            android:text="删除"            android:layout_width="wrap_content"            android:layout_height="wrap_content"/>    </LinearLayout>    <EditText        android:id="@+id/et_id"        android:hint="批改/删除的ID"        android:inputType="number"        android:layout_width="match_parent"        android:layout_height="wrap_content"/>    <ListView        android:id="@+id/lv_data"        android:layout_width="match_parent"        android:layout_height="wrap_content"/></LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private EditText etId;    private EditText etName;    private EditText etAge;    private String sex;    private SQLiteDatabase sqLiteDatabase;    private ListView lvData;    // 申请权限申请码    private static final int REQUEST_EXTERNAL_STORAGE = 1001;    // 查看权限,这种写法次要是针对比拟新的Android6.0及当前的版本    public static void verifyStoragePermissions(Activity activity) {        int writePermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);        int readPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);        if (writePermission != PackageManager.PERMISSION_GRANTED                || readPermission != PackageManager.PERMISSION_GRANTED) {            // 如果没有权限须要动静地去申请权限            ActivityCompat.requestPermissions(                    activity,                    // 权限数组                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},                    // 权限申请码                    REQUEST_EXTERNAL_STORAGE            );        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        etId = findViewById(R.id.et_id);        etName = findViewById(R.id.et_name);        etAge = findViewById(R.id.et_age);        // 单选框组件        RadioGroup rgSex = findViewById(R.id.rg_sex);        lvData = findViewById(R.id.lv_data);        verifyStoragePermissions(this);        /*         * 结构参数:         * 1、上下文         * 2、数据库名称,默认地位利用的公有目录(外部存储的database文件夹)         * 3、CursorFactory类型         * 4、数据库版本         */        String path = Environment.getExternalStorageDirectory() + "/sqlite_demo.db";        SQLiteOpenHelper helper = new SQLiteOpenHelper(this, path, null, 1){            // 创立数据库            @Override            public void onCreate(SQLiteDatabase db) {                Toast.makeText(MainActivity.this, "数据库创立", Toast.LENGTH_SHORT).show();                // 如果当时没有数据库的话,创立表的操作就能够在这里进行                db.execSQL("create table stu_info (id integer primary key autoincrement, name varchar(30) not null, age integer,gender varchar(2) not null)");            }            // 版本号变动之后会调用这个办法            @Override            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {                Toast.makeText(MainActivity.this, "数据库降级", Toast.LENGTH_SHORT).show();            }        };        // 获取数据库对象        sqLiteDatabase = helper.getWritableDatabase();        // 设置单选框的监听        rgSex.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {            @Override            public void onCheckedChanged(RadioGroup group, int checkedId) {                switch (checkedId){                    case R.id.rb_female:                        sex = "女";                        break;                    case R.id.rb_male:                        sex = "男";                        break;                }            }        });        flushStuData();    }    private void flushStuData() {        List<StudentInfo> stuList = new ArrayList<>();        String selectSQL = "select * from stu_info";        Cursor cursor = sqLiteDatabase.rawQuery(selectSQL, new String[]{});        cursor.moveToFirst();        while (!cursor.isAfterLast()){            int id = cursor.getInt(0);            String name = cursor.getString(1);            int age = cursor.getInt(2);            String sex = cursor.getString(3);            stuList.add(new StudentInfo(id, name, age, sex));            cursor.moveToNext();        }        cursor.close();        lvData.setAdapter(new StuInfoAdapter(this, stuList));    }    public void operatorData(View view) {        int viewId = view.getId();        switch (viewId) {            case R.id.btn_add:                if(TextUtils.isEmpty(sex)) {                    Toast.makeText(MainActivity.this, "请抉择性别", Toast.LENGTH_SHORT).show();                    return;                }                String insertSQL = String.format(Locale.CHINA,"insert into stu_info(name, age, gender) values ('%s', %d, '%s')",                        etName.getText().toString(),                        Integer.parseInt(etAge.getText().toString()),                        sex);                Log.i(TAG, "operatorData: insertSQL = " + insertSQL);                sqLiteDatabase.execSQL(insertSQL);                // 刷新数据展现                flushStuData();                Toast.makeText(MainActivity.this, "增加胜利", Toast.LENGTH_SHORT).show();                break;            case R.id.btn_update:                String idStr = etId.getText().toString();                if(TextUtils.isEmpty(idStr)){                    Toast.makeText(MainActivity.this, "请输出ID", Toast.LENGTH_SHORT).show();                    return;                }                int id = Integer.parseInt(idStr);                String updateSQL = String.format(Locale.CHINA,                        "update stu_info set name = '%s', age=%d, gender='%s' where id = %d",                        etName.getText().toString(),                        Integer.parseInt(etAge.getText().toString()),                        sex, id);                Log.i(TAG, "operatorData: updateSQL = " + updateSQL);                sqLiteDatabase.execSQL(updateSQL);                // 刷新数据展现                flushStuData();                Toast.makeText(MainActivity.this, "更新胜利", Toast.LENGTH_SHORT).show();                break;            case R.id.btn_delete:                String deleteIdStr = etId.getText().toString();                if(TextUtils.isEmpty(deleteIdStr)){                    Toast.makeText(MainActivity.this, "请输出ID", Toast.LENGTH_SHORT).show();                    return;                }                String deleteSQL = String.format(Locale.CHINA, "delete from stu_info where id = %d", Integer.parseInt(deleteIdStr));                Log.i(TAG, "operatorData: deleteSQL = " + deleteSQL);                sqLiteDatabase.execSQL(deleteSQL);                // 刷新数据展现                flushStuData();                Toast.makeText(MainActivity.this, "删除胜利", Toast.LENGTH_SHORT).show();                break;        }    }}class StudentInfo {    public int id;    public String name;    public int age;    public String sex;    public StudentInfo() {    }    public StudentInfo(int id, String name, int age, String sex) {        this.id = id;        this.name = name;        this.age = age;        this.sex = sex;    }}

StuInfoAdapter.java

public class StuInfoAdapter extends BaseAdapter {    private List<StudentInfo> stuList;    private Activity context;    public StuInfoAdapter(Activity context, List<StudentInfo> stuList) {        this.stuList = stuList;        this.context = context;    }    @Override    public int getCount() {        return stuList.size();    }    @Override    public Object getItem(int position) {        return stuList.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder viewHolder;        if(convertView == null){            LayoutInflater inflater = context.getLayoutInflater();            convertView = inflater.inflate(R.layout.stu_item, null);            viewHolder = new ViewHolder();            viewHolder.tvName = convertView.findViewById(R.id.tv_item_name);            viewHolder.tvId = convertView.findViewById(R.id.tv_item_id);            viewHolder.tvAge = convertView.findViewById(R.id.tv_item_age);            viewHolder.tvSex = convertView.findViewById(R.id.tv_item_sex);            convertView.setTag(viewHolder);        }else {            viewHolder = (ViewHolder) convertView.getTag();        }        StudentInfo studentInfo = stuList.get(position);        viewHolder.tvId.setText(String.valueOf(studentInfo.id));        viewHolder.tvName.setText(studentInfo.name);        viewHolder.tvSex.setText(studentInfo.sex);        viewHolder.tvAge.setText(String.valueOf(studentInfo.age));        return convertView;    }    // ViewHolder    static class ViewHolder{        TextView tvId;        TextView tvName;        TextView tvSex;        TextView tvAge;    }}

最好不要遗记申明读写的权限(以及在Android6.0当前的动静权限申请),最初成果展现如下:

在Android 中应用SQLiteDatabase的静态方法openOrCreateDatabase(String path,SQLiteDatabae.CursorFactory factory)关上或者创立一个数据库。它会主动去检测是否存在这个数据库,如果存在则关上,不存在则创立一个数据库;创立胜利则返回一个SQLiteDatabase对象,否则抛出异样FileNotFoundException。

还有须要留神的就是,查问数据返回的后果是Cursor,当咱们应用SQLiteDatabase.query()办法时,会失去一个Cursor对象,Cursor指向的就是每一条数据。它提供了很多无关查问的办法,具体方法如下:

办法名称办法形容
getCount()取得总的数据项数
isFirst()判断是否第一条记录
isLast()判断是否最初一条记录
moveToFirst()挪动到第一条记录
moveToLast()挪动到最初一条记录
move(int offset)挪动到指定记录
moveToNext()挪动到下一条记录
moveToPrevious()挪动到上一条记录
getColumnIndexOrThrow(String columnName)依据列名称取得列索引
getInt(int columnIndex)取得指定列索引的int类型值
getString(int columnIndex)取得指定列缩影的String类型值

这个能够参考在代码示例中flushStuData()办法中的应用:

private void flushStuData() {    List<StudentInfo> stuList = new ArrayList<>();    // 参数解释:表名、要查问的字段、列条件、列条件参数、GroupBy、having、orderBy    Cursor cursor = sqLiteDatabase.query("stu_info", null, null, null, null, null, null);    if(cursor.moveToFirst()){        do{            int id = cursor.getInt(0);            String name = cursor.getString(1);            int age = cursor.getInt(2);            String sex = cursor.getString(3);            stuList.add(new StudentInfo(id, name, age, sex));        } while (cursor.moveToNext());    }    cursor.close();    lvData.setAdapter(new StuInfoAdapter(this, stuList));}

通过API来操作数据库

SQLiteDatabase也提供了insert()、delete()、update()、query()办法专门用于插入、删除、更新和查问,通过这种API的操作形式就须要编写SQL语句了,只须要传入对应的参数,即可实现CRUD操作。还是通过下面的例子,改变的中央无非就是操作数据的局部而已:

// ...// 查问private void flushStuData() {    List<StudentInfo> stuList = new ArrayList<>();    // 参数解释:表名、要查问的字段、列条件、列条件参数、GroupBy、having、orderBy    Cursor cursor = sqLiteDatabase.query("stu_info", null, null, null, null, null, null);    if(cursor.moveToFirst()){        do{            int id = cursor.getInt(0);            String name = cursor.getString(1);            int age = cursor.getInt(2);            String sex = cursor.getString(3);            stuList.add(new StudentInfo(id, name, age, sex));        } while (cursor.moveToNext());    }    cursor.close();    lvData.setAdapter(new StuInfoAdapter(this, stuList));}public void operatorData(View view) {    int viewId = view.getId();    switch (viewId) {        // 增加        case R.id.btn_add:            // 参数解释:操作表的名称、能够为空的列、参数            ContentValues values = new ContentValues();            // Key - value            values.put("name", etName.getText().toString());            values.put("age", Integer.parseInt(etAge.getText().toString()));            values.put("gender", sex);            // 插入胜利,返回数据的ID            long infoId = sqLiteDatabase.insert("stu_info", null, values);            // 刷新数据展现            flushStuData();            Toast.makeText(MainActivity.this, "增加胜利,Id = " + infoId, Toast.LENGTH_SHORT).show();            break;        // 更新        case R.id.btn_update:            String idStr = etId.getText().toString();            ContentValues updateValues = new ContentValues();            // Key - value            updateValues.put("name", etName.getText().toString());            updateValues.put("age", Integer.parseInt(etAge.getText().toString()));            updateValues.put("gender", sex);            int info = sqLiteDatabase.update("stu_info", updateValues, "id=?", new String[]{idStr});            // 刷新数据展现            flushStuData();            Toast.makeText(MainActivity.this, "更新胜利,影响行数:" + info, Toast.LENGTH_SHORT).show();            break;        // 删除        case R.id.btn_delete:            String deleteIdStr = etId.getText().toString();            int delete = sqLiteDatabase.delete("stu_info", "id=?", new String[]{deleteIdStr});            // 刷新数据展现            flushStuData();            Toast.makeText(MainActivity.this, "删除胜利,影响行数:" + delete, Toast.LENGTH_SHORT).show();            break;    }}// ...

应用LitePal操作SQLite

下面应用SQLiteDatabase来操作SQLite数据库的办法,应用起来真的很不不便,像我这种习惯应用ORM框架的人来说,SQLiteDatabase的操作形式几乎太过于简单,所以当初来看看Litepal这款开源框架吧,应用完过后本人也来尝试造一轮子,能够参考:https://github.com/huyongli/T...

环境搭建

首先引入依赖:

implementation 'org.litepal.android:core:1.4.1'

接下来须要配置 litepal.xml 文件。右击 app/src/main 目录 —>New—>Directory,创立一个 assets 目录,在assets目录下新建litepal.xml文件:

<?xml version="1.0" encoding="utf-8" ?><litepal>    <!-- 指定数据库名称 -->    <dbname value="BookStore"/>    <!-- 指定数据库版本号 -->    <version value="1"/>    <!-- 指定映射模型 -->    <list></list></litepal>

最初还须要批改一下 AndroidManifest.xml 中的代码:

<!-- 要害就这一句:android:name="org.litepal.LitePalApplication" --><application    android:name="org.litepal.LitePalApplication"    android:allowBackup="true"    android:icon="@mipmap/ic_launcher"    android:label="@string/app_name"    android:roundIcon="@mipmap/ic_launcher_round"    android:supportsRtl="true"    android:theme="@style/AppTheme">    <activity android:name=".MainActivity">        <intent-filter>            <action android:name="android.intent.action.MAIN" />            <category android:name="android.intent.category.LAUNCHER" />        </intent-filter>    </activity></application>

当初 Litepal 的配置工作曾经做完了,让咱们开始正式应用它吧!

建库建表

当初开始申明一个JavaBean,也就是咱们要存储的数据:

public class Book {    private int id;    private String name;    private String author;    public Book(){ }    public Book(int id, String author, String name) {        this.id = id;        this.author = author;        this.name = name;    }    // Getter and Setter ...}

并且在配置文件中配置它的映射模型:

<?xml version="1.0" encoding="utf-8" ?><litepal>    <!-- 指定数据库名称 -->    <dbname value="BookStore"/>    <!-- 指定数据库版本号 -->    <version value="1"/>    <!-- 指定映射模型 -->    <list>        <mapping class="cn.tim.litepal_demo.Book"/>    </list></litepal>

在Activity启动时创立数据库:

// 创立数据库SQLiteDatabase database = LitePal.getDatabase();

尽管有三张表,其中android_matedata表依然不必管,table_schema表是litepal外部应用的,也能够间接漠视,Book表就是依据配置的Book类主动生成的表,是不是很不便?

数据库降级

而且Litepal很好的解决了数据库降级问题,应用SQLiteOpenHelper来降级数据库的形式:降级数据库的时候咱们须要先把之前的表drop掉,而后再从新创立才行。这其实是一个十分重大的问题,因为这样会造成数据失落,每当降级一次数据库,之前表中的数据就全没了。尽管能够通过简单的逻辑管制来防止这种状况,然而保护老本很高。而有了LitePal,这些就都不再是问题了,应用LitePal来降级数据库十分非常简单,你齐全不必思考任何的逻辑,只须要改你想改的任何内容,而后将版本号加1就行了。

比方,将图书表的中再增加一个价格的字段,再新建一张分类表:

public class Book {    private int id;    private String name;    private String author;    private int price;        // ...}public class Category {    private int id;    private String name;    private long count;        // ...}

同样须要配置映射模型:

<?xml version="1.0" encoding="utf-8" ?><litepal>    <!-- 指定数据库名称 -->    <dbname value="BookStore"/>    <!-- 指定数据库版本号:此时数据库版本应该为2 -->    <version value="2"/>    <!-- 指定映射模型 -->    <list>        <mapping class="cn.tim.litepal_demo.Book"/>        <mapping class="cn.tim.litepal_demo.Category"/>    </list></litepal>

由此可见,book表中新增了一个price列,并且新创建了category表。

CRUD操作

上面来看看应用Litepal来对数据进行CRUD是如许不便吧:

首先须要让数据模型对象,也就是定义的Javabean来继承DataSupport

public class Book extends DataSupport {    ...}

上面间接通过查看log日志的形式来验证LitePal框架的CRUD:

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // 创立数据库        SQLiteDatabase database = LitePal.getDatabase();        // 增加数据        Book book = new Book("Think In Java", "Tim", 58);        boolean saveRet = book.save();        Log.i(TAG, "onCreate: saveRet = " + saveRet);        new Book("Think In C/C++", "Tom", 38).save();        Log.i(TAG, "onCreate: 增加数据胜利");        // 查问数据        List<Book> bookList = DataSupport.findAll(Book.class);        Book[] books = new Book[bookList.size()];        bookList.toArray(books);        Log.i(TAG, "onCreate: books = " + Arrays.toString(books));        // 删除数据        int delete = DataSupport.delete(Book.class, books[0].getId());        Log.i(TAG, "onCreate: 删除数据胜利,delete = " + delete);                // 查问数据        bookList = DataSupport.findAll(Book.class);        books = new Book[bookList.size()];        bookList.toArray(books);        Log.i(TAG, "onCreate: books = " + Arrays.toString(books));        // 批改数据        Book cppBook = new Book("Think In C/C++", "Tom", 28);        int update = cppBook.update(2);        Log.i(TAG, "onCreate: 批改数据胜利,update = " + update);        // 查问数据        bookList = DataSupport.findAll(Book.class);        books = new Book[bookList.size()];        bookList.toArray(books);        Log.i(TAG, "onCreate: books = " + Arrays.toString(books));    }}

下面演示了具体的操作,其实还有很多高级查询方法,这里不再赘述,如果用到能够参考作者的博客《Android数据库高手秘籍》。

博客原文:https://zouchanglin.cn/2020/1...