Android数据存储——文件存储、SharedPreferences、SQLite、Litepal

数据存储全方案——详解持久化技术

Android系统中主要提供了3中方式用于简单地实现数据持久化功能,即文件存储SharedPreference存储以及数据库存储。除了这三种方式外,还可以将数据保存在手机的SD卡中,不给使用文件、SharedPreference或者数据库来保存数据会更简单一些,而且比起将数据保存在SD卡中会更加地安全。

文件存储

文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中,适合用于存储一些简单的文本数据或二进制数据。如果想使用文件存储的方式来保存一些较为复杂的文本数据,就需要定义一套自己的格式规范,这样方便之后将数据从文件中重新解析出来。

将数据存储到文件中

Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。这个方法接收两个参数,第一个参数是文件名,在文件创建的时候使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/<package name>/files/目录下的。第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件

文件的操作模式本来还有另外两种:MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,这两种模式表示允许其他的应用程序对我们程序中的文件进行读写操作,由于这两种模式过于危险,很容易引起应用的安全性漏洞,已在Android 4.2版本中被废弃。

openFileOutput()方法返回的是一个FileOutputStream对象,得到这个对象之后就可以使用Java流的方式将数据写入到文件中了。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="10dp"><EditTextandroid:id="@+id/edit01"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Type something here"android:maxLines="1" /></LinearLayout>
public class Database01Activity extends AppCompatActivity {private EditText editText01;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_database01);editText01 = findViewById(R.id.edit01);}@Overrideprotected void onDestroy() {super.onDestroy();String inputText = editText01.getText().toString();save(inputText);}private void save(String inputText) {FileOutputStream out = null;BufferedWriter writer = null;try {out = openFileOutput("data", Context.MODE_PRIVATE);writer = new BufferedWriter(new OutputStreamWriter(out));writer.write(inputText);} catch (IOException e) {e.printStackTrace();} finally {try {if (writer != null) {writer.close();}} catch (IOException e) {e.printStackTrace();}}}
}

在onCreate()方法中获取了EditText的实例,然后重写了onDestory()方法,可以保证活动在销毁前一定会调用这个方法;在onDestory()方法中获取了EditText中的内容,并且调用save()方法把输入的内容存储到文件中,文件命名为data;并且在下面例子中找到对应文件即可,如下图所示:

在这里插入图片描述

从文件中读取数据

Context类中还提供了一个openFileInput()方法,用于从文件中读取数据。这个方法要比openFileOutput()简单一些,它只接收一个参数,即要读取的文件名,然后系统会自动到/data/data/<package name>/files/目录下去加载这个文件,并返回一个FileInputStream对象,得到了这个对象之后再通过Java流的方式就可以将数据读取出来了。

public String load() {FileInputStream in = null;BufferedReader reader = null;StringBuilder content = new StringBuilder();try {in = openFileInput("data");reader = new BufferedReader(new InputStreamReader(in));String line = "";while ((line = reader.readLine()) != null) {content.append(line);}} catch (IOException e) {e.printStackTrace();} finally {if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return content.toString();
}

首先通过openFileInput()方法获取到了一个FileInputStream对象,然后借助它又构建出了一个InputStreamReader对象,接着再使用InputStreamReader构建出一个BufferedReader对象,此时可以通过BufferedReader进行一行一行地读取,把文件中所有的文本内容全部读取出来,并存在在一个StringBuilder对象中,最后将读取到的内容返回就可以了。

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_database01);editText01 = findViewById(R.id.edit01);String inputText = load();if (!TextUtils.isEmpty(inputText)) {editText01.setText(inputText);editText01.setSelection(inputText.length());Toast.makeText(this, "Restoring Succeeded", Toast.LENGTH_SHORT).show();}
}

在onCreate()方法中调用load()方法来读取文件中存储的文本内容,如果读取到的内容不为null,就调用EditText方法中的setText()方法将内容填充到EditText里,并调用setSelection()方法将输入光标移动到文本的末尾位置以便继续输入。

上述代码对字符串进行非空判断的时候使用了TextUtils.isEmpty()方法;它可以一次性进行两种空值的判断。当传入的字符串等于null或者等于空字符串的时候,这个方法都会返回true,从而使得不需要先单独判断这两种空值再使用逻辑运算符连接起来了。

SharedPreferences存储

不同于文件的存储方式,SharedPreferences是使用键值对的方式来存储数据的。也就是说,当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相对应的值取出来。而且SharedPreferences还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型;如果存储的数据是一个字符串,那么读取出来的仍然是字符串。

将数据存储到SharedPreferences中

首先需要获取到SharedPreferences对象,Android中主要提供了3中方法用于得到SharedPreferences对象。

1、Context类中的getSharedPreferences()方法

该方法接收两个参数,第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在,则会创建一个,SharedPreferences问及爱你都是存放在/data/data/<packet name>/shared_prefs/目录下的。第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,它是默认的操作模式,和直接传入0效果是相同的,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。其他几种操作模式均已被放弃。

2、Activity类中的getPreferences()方法

该方法和Context类中很相似,不过它只接受一个操作模式参数,因为使用这个方法时,会自动将当前活动的类名作为SharedPreferences的文件名。

3、PreferenceManager类中的getDefaultSharedPreferences()方法

这是一个静态方法,它接收到一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。得到了SharedPreferences对象之后,就可以开始向SharedPreferences问及爱你中存储数据了,主要分为3步:

(1)调用SharedPreferences对象的edit()方法俩获取一个SharedPreferences.Editor对象

(2)向SharedPreferences.Editor对象中添加数据,比如添加一个布尔型数据就使用putBoolean()方法,添加一个字符串则使用putString()方法,以此类推

(3)调用apply()方法将添加的数据提交,从而完成数据存储操作

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><Buttonandroid:id="@+id/btn_save_data"style="@style/MainButton"android:text="Save data" /></LinearLayout>

设置一个按钮,用于将一些数据存储到SharedPreferences文件当中,然后修改SharedPerferencesActivity中的代码:

public class SharedPreferencesActivity extends BaseActivity {private Button mBtnSaveData;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_sharedpreferences);mBtnSaveData = findViewById(R.id.btn_save_data);mBtnSaveData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();editor.putString("name", "Tom");editor.putInt("age", 28);editor.putBoolean("married", false);editor.apply();}});}
}

给按钮注册了一个点击事件,然后在点击事件中通过getSharedPreferences()方法指定SharedPreferences的文件名为data,并得到了SharedPreferences.Editor对象。接着向这个对象(editor)中添加了三条不同类型的数据,最后调用apply()方法进行提交,完成了数据存储的操作。

运行程序后,点击Save data按钮,这时的数据应该已经保存成功了,借助File Explorer来进行查看,打开Android Device Monitor,点击File Explorer标签页,然后进入到/data/data/<package name>/shared_prefs/目录下,可以看到生成一个data.xml文件,如下所示:

在这里插入图片描述

这里通过Nodepad++打开data.xml文件,记事本打开也可:

在这里插入图片描述

可以看到所有的数据已经成功保存下来了,并且SharedPreferences文件是使用XML格式对数据进行管理的。

从SharedPreferences中读取数据

从SharedPreferences来存储数据时非常简单的,SharedPreferences对象中提供了一系列的get方法,用于对存储的数据进行读取,每种get方法都对应了SharedPreferences.Editor中的一种put方法,比如读取一个布尔型数据就使用getBoolean()方法,读取一个字符串就使用getString()方法。这些get方法都接收两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回。

<Buttonandroid:id="@+id/btn_restore_data"style="@style/MainButton"android:text="Restore data" />

添加一个还原数据按钮,点击这个按钮中读取数据,修改SharedPreferencesActivity中的代码,如下所示:

mBtnRestoreData = findViewById(R.id.btn_restore_data);
mBtnRestoreData.setOnClickListener(new View.OnClickListener() {@SuppressLint("LongLogTag")@Overridepublic void onClick(View view) {SharedPreferences preferences = getSharedPreferences("data", MODE_PRIVATE);String name = preferences.getString("name", "");int age = preferences.getInt("age", 0);boolean married = preferences.getBoolean("married", false);Log.d(TAG, "name is " + name);Log.d(TAG, "age is " + age);Log.d(TAG, "married is " + married);}
});

首先通过getSharedPreferences()方法得到了SharedPreferences对象,然后分别调用它的getString()、getInt()和getBoolean()方法,去获取前面所存储的姓名、年龄和是否已婚,如果没有找到相应的值,就会使用方法中传入的默认值来代替,最后通过Log将这些值打印出来。重新运行程序,点击Restore data按钮,查看log信息,如下所示:

在这里插入图片描述

所有之前存储的数据都成功读取出来了!下面编写一个记住密码的功能,加深对SharedPreferences的理解。

实现记住密码功能

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingLeft="10dp"android:paddingRight="10dp"><TextViewandroid:id="@+id/tv_Account"android:layout_width="80dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="Account:"android:textStyle="bold" /><EditTextandroid:id="@+id/et_Account"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="30" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingLeft="10dp"android:paddingRight="10dp"><TextViewandroid:id="@+id/tv_Password"android:layout_width="80dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="Password:"android:textStyle="bold" /><EditTextandroid:id="@+id/et_Password"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="30" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><CheckBoxandroid:id="@+id/cb_remember_pass"android:layout_width="wrap_content"android:layout_height="wrap_content" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Remember password"android:textSize="18sp" /></LinearLayout><Buttonandroid:id="@+id/btn_Login"style="@style/MainButton"android:layout_height="60dp"android:text="Login" /></LinearLayout>

对应的LoginActivity逻辑代码如下:

public class LoginActivity extends AppCompatActivity {private SharedPreferences preferences;private SharedPreferences.Editor editor;private EditText accountEdit, passwordEdit;private Button mBtnLogin;private CheckBox rememberPass;@SuppressLint("MissingInflatedId")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);accountEdit = findViewById(R.id.et_Account);passwordEdit = findViewById(R.id.et_Password);mBtnLogin = findViewById(R.id.btn_Login);rememberPass = findViewById(R.id.cb_remember_pass);preferences = PreferenceManager.getDefaultSharedPreferences(this);		//重要函数boolean isRemember = preferences.getBoolean("remember_password", false);if (isRemember) {//将账号和密码都设置到文本中String account = preferences.getString("account", "");String password = preferences.getString("password", "");accountEdit.setText(account);passwordEdit.setText(password);}mBtnLogin.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {String account = accountEdit.getText().toString();String password = passwordEdit.getText().toString();//如果账号是admin且密码123456,登录成功if (account.equals("admin") && password.equals("123456")) {editor = preferences.edit();if (rememberPass.isChecked()) { //检查复选框是否被选中editor.putBoolean("remember_password", true);editor.putString("account", account);editor.putString("password", password);} else {editor.clear();}editor.apply();Intent intent = new Intent(LoginActivity.this, MainActivity.class);startActivity(intent);finish();} else {Toast.makeText(LoginActivity.this, "account or password is invalid", Toast.LENGTH_SHORT).show();}}});}
}

首先在onCreate()方法中获取到了SharedPreferences对象,然后调用它的getBoolean()方法去获取remember_password这个键对应的值。一开始当然不存在对应的值,默认值为false。登录成功之后,会检查复选框是否被选中,如果被选中,则表示用户想要记住密码,这是remember_password设置为true,然后把account和password对应的值都存入到SharedPreferences文件当中并提交。如果没有被选中,就简单调用一下clear()方法,将SharedPreferences文件中的数据全部清除掉。

当用户选中记住密码复选框,并成功登录一次之后,remember_password键对应的值就是true,这时候重新启动登录界面,就会从SharedPreferences文件中将保存的账号和密码都读取出来,并填充到文本输入框中,然后把记住密码复选框选中,这样就完成了记住密码的功能了。

在这里插入图片描述

SQLite数据库存储

Android为了方便管理数据库,专门提供了一个SQLiteOpenHelper帮助类,是一个抽象类,需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

SQLiteOpenHelper中有两个非常重要的实例方法:getReadableDatabase()和getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。

SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少的构造方法,其中有四个参数:第一个参数是Context;第二个参数是数据库名,创建数据库时使用的就是这里指定的名称;第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null;第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。构建出SQLiteOpenHelper的实例后,再调用getReadableDatabase()和getWritableDatabase()方法就能创建数据库了,数据库文件放在data/data/<package name>/database/目录下。此时,重写的onCreate()方法也会得到执行,所以通常会在这里处理一些创建表的逻辑。

创建数据库

创建一个名为BookStore.db的数据库,然后在这个数据库中新建一张Book表,表中有id(主键)、作者、价格、页数和书名等列。创建数据库还是需要用建表语句的:

create table Book(id integer primary key autoincrement,author text,price real,pages integer,name text)

integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。另外,上述建表语句中使用primary key将id列设为主键,并用autoincrement关键字表示id是自增长的。

新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下:

public class MyDatabaseHelper extends SQLiteOpenHelper {public static final String CREATE_BOOK = "create table Book ("+ "id integer primary key autoincrement,"+ "author text,"+ "price real, "+ "pages integer,"+ "name text)";private Context mContext;public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);mContext = context;}@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase) {sqLiteDatabase.execSQL(CREATE_BOOK);Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();}@Overridepublic void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {}
}

1、将建表语句定义成一个字符串常量

2、在onCreate()方法中调用SQLiteDatabase的execSQL()方法去执行这条建表语句

这样保养在数据库创建完成的同时还能够成功创建Book表

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".db.Database03.Database03Activity"><Buttonandroid:id="@+id/btn_create_database"style="@style/MainButton"android:text="Create database" /></LinearLayout>

布局设置,加入了一个按钮,用于创建数据库。最后修改Activity中的代码,如下所示:

public class Database03Activity extends AppCompatActivity {private MyDatabaseHelper dbHelper;private Button mBtnCreateDatabase;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_database03);dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);mBtnCreateDatabase = findViewById(R.id.btn_create_database);mBtnCreateDatabase.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {dbHelper.getWritableDatabase();}});}
}

1、在onCreate()方法中构建MyDatabaseHelper对象,并且通过构造函数将数据库名指定为BookStore.db,版本号指定为1

2、在按钮点击事件里调用getWritableDatabase()方法;当第一次点击按钮时,就会检测到当前程序中没有BookStore.db数据库,于是创建该数据库,并调用MyDatabaseHelper中的onCreate()方法,这样Book表也就得到了创建,然后会染出一个Toast提示创建成功。

3、再次点击按钮,此时已经存在BookStore.db数据库,因此不会再创建一次。

在这里插入图片描述

升级数据库

在MyDatabaseHelper中还有一个空方法onUpgrade()方法,是用于对数据库进行升级的,它在整个数据库的管理工作中起着非常重要的作用。

目前已经有了一张Book表用于存放书的各种详细数据,如果想再添加一张Category表用于记录图书的分类,应该怎么做呢?

create table Category(id integer primary key autoincrement,category_name text,category_code integer)

接下来我们将这条建表语句添加到MyDatabaseHelper中,代码如下:

public class MyDatabaseHelper extends SQLiteOpenHelper {public static final String CREATE_BOOK = "create table Book ("+ "id integer primary key autoincrement,"+ "author text,"+ "price real, "+ "pages integer,"+ "name text)";//添加Categorypublic static final String CREATE_CATEGORY = "create table Category(" +"id integer primary key autoincrement," +"category_name text," +"category_code integer)";private Context mContext;public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);mContext = context;}@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase) {sqLiteDatabase.execSQL(CREATE_BOOK);sqLiteDatabase.execSQL(CREATE_CATEGORY);    //添加CategoryToast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();}@Overridepublic void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {sqLiteDatabase.execSQL("drop table if exists Book");sqLiteDatabase.execSQL("drop table if exists Category");onCreate(sqLiteDatabase);}
}

在onUpgrade()方法中执行了两条DROP语句;如果数据库中已存在Book表或者Category表,就将这两张表删除掉了,然后在调用onCreate()方法重新创建。这里先将已经存在的表删除掉,因为如果在创建表时发现这张表已经存在了,就会直接报错。如何让onUpgrade()方法能够执行呢?在SQLiteOpenHelper的构造方法里接收的第四个参数,表示当前数据库版本号,最初传入的是1,现在只需要传入一个比1大的数,就可以执行onUpgrade()方法。

public class Database03Activity extends AppCompatActivity {private MyDatabaseHelper dbHelper;private Button mBtnCreateDatabase;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_database03);dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);	//升级数据库版本号mBtnCreateDatabase = findViewById(R.id.btn_create_database);mBtnCreateDatabase.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {dbHelper.getWritableDatabase();}});}
}

运行程序,打开BookStore.db文件,如下所示:

在这里插入图片描述

由此可以看出,Category表已经创建成功了,同时也说明升级功能的确起到了作用。

添加数据

调用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法是可以用于创建和升级数据库的,不仅如此,这两个方法都会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了

SQLiteDatabase中提供了insert()方法,这个方法专门用于添加数据。它接收三个参数,第一个参数是表名,希望向哪张表添加数据,就填写该表的名字;第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到,直接null;第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加的数据传入即可。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".db.SQLite.SQLiteActivity"><Buttonandroid:id="@+id/btn_create_database"style="@style/MainButton"android:text="Create database" /><Buttonandroid:id="@+id/btn_Add_Data"style="@style/MainButton"android:text="Add data" /></LinearLayout>

添加一个新的按钮,用于添加数据,修改Activity,代码如下:

public class SQLiteActivity extends AppCompatActivity {private MyDatabaseHelper dbHelper;private Button mBtnCreateDatabase, mBtnAddData;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_sqlite);dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);mBtnCreateDatabase = findViewById(R.id.btn_create_database);mBtnCreateDatabase.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {dbHelper.getWritableDatabase();}});mBtnAddData = findViewById(R.id.btn_Add_Data);mBtnAddData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SQLiteDatabase db = dbHelper.getWritableDatabase();ContentValues values = new ContentValues();//开始组装第一条数据values.put("name", "The Da Vinci Code");values.put("author", "Dan Brown");values.put("pages", 454);values.put("price", 16.96);db.insert("Book", null, values);    //插入第一条数据//开始组装第二条数据values.put("name", "The Lost Symbol");values.put("author", "Dan Brown");values.put("pages", 510);values.put("price", 19.95);db.insert("Book", null, values);    //插入第二条数据}});}
}

在这里插入图片描述

更新数据

SQLiteDatabase中也提供了一个非常好用的update()方法,用于对数据进行更新;这个方法接收4个参数,第一个参数为表名;第二个参数是ContentValues对象,要把更多的数据在这里组装进去。第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话,默认更新所有行。

<Buttonandroid:id="@+id/btn_Update_Data"style="@style/MainButton"android:text="Update data" />

设置了更新数据按钮后,设置点击事件:

mBtnUpdateData = findViewById(R.id.btn_Update_Data);
mBtnUpdateData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SQLiteDatabase db = dbHelper.getWritableDatabase();ContentValues values = new ContentValues();values.put("price", 10.99);db.update("Book", values, "name = ?", new String[]{"The Da Vinci Code"});}
});

构建了一个ContentValues对象,并且只给它指定了一组数据,说明我们只是想把价格这一列的数据更新成10.99。然后调用了SQLiteDatabase的update()方法去执行具体的更新操作。第三个参数对应的是SQL语句的where部分,表示更新所有name等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。因此上述代码想表达的意图是将名字是The Da Vinci Code的这本书的价格改成10.99。

在这里插入图片描述

删除数据

SQLiteDatabase中提供了一个delete()方法,专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,第二、第三个参数又用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。

<Buttonandroid:id="@+id/btn_Delete_Data"style="@style/MainButton"android:text="Delete data" />

设置了删除数据按钮后,设置点击事件:

mBtnDeleteData = findViewById(R.id.btn_Delete_Data);
mBtnDeleteData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SQLiteDatabase db = dbHelper.getWritableDatabase();db.delete("Book", "price > ?", new String[]{"500"});}
});

查询数据

SQLiteDatabase中还提供了一个query()方法用于对数据进行查询。这个方法比较复杂,最短的一个方法重载需要传入7个参数。第一个参数表名;第二个参数用于指定查询哪几列,不指定则默认查询所有列。第三、第四个参数用于约束查询某一行或某几行的数据,不指定则默认查询所有行的数据。第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作;第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤;第七个参数用于指定查询结果的排序方式,不指定则使用默认的排序方式。

query()方法参数对应SQL部分描述
tablefrom table_name
columnsselect column1,column2
selectionwhere column = value
selectionArgs-
groupBygroup by column
havinghaving column = value
orderByorder by column1,column2

虽然query()方法的参数非常多,多数情况下只需要传入少数几个参数就可以完成查询操作了。调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。

<Buttonandroid:id="@+id/btn_Query_Data"style="@style/MainButton"android:text="Query data" />

设置了查询数据按钮后,设置点击事件:

mBtnQueryData = findViewById(R.id.btn_Query_Data);
mBtnQueryData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SQLiteDatabase db = dbHelper.getWritableDatabase();//查询Book表中所有的数据Cursor cursor = db.query("Book", null, null, null, null, null, null, null);if (cursor.moveToFirst()) {do {//遍历Cursor对象,取出数据并打印String name = cursor.getString(cursor.getColumnIndex("name"));String author = cursor.getString(cursor.getColumnIndex("author"));int pages = cursor.getInt(cursor.getColumnIndex("pages"));double price = cursor.getDouble(cursor.getColumnIndex("price"));Log.d(TAG, "book name is " + name);Log.d(TAG, "book author is " + author);Log.d(TAG, "book pages is " + pages);Log.d(TAG, "book price is " + price);} while (cursor.moveToNext());}cursor.close();}
});

在这里插入图片描述

使用SQL操作数据库

  • 添加数据的方法如下:

    db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",new String[]{"The Da Vinci Code","Dan Brown","454","16.96"});
    db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",new String[]{"The Lost Symbol","Dan Brown","510","19.95"});
    
  • 更新数据的方法如下:

    db.execSQL("update Book set price = ? where name = ?",new String[]{"10.99","The Da Vinci Code"});
    
  • 删除数据的方法如下:

    db.execSQL("delete from Book where pages > ?",new String[]{"500"});
    
  • 查询数据的方法如下:

    db.rawQuery("select * from Book", null);
    

除了查询数据的时候调用的是SQLiteDatabase的rawQuery()方法,其他的操作都是调用execSQL()方法。

使用LitePal操作数据库

LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行封装,使得不用编写一行SQL语句就可以完成各种建表和增删改查的操作。LitePal的项目主页上也有详细的使用文档,地址是:https://github.com/guolindev/LitePal

  • 使用对象关系映射(ORM)模式
  • 几乎零配置(仅一个具有少量属性的配置文件)
  • 自动维护所有表(例如创建、修改或删除表)
  • 支持多数据库
  • 封装的API,避免编写SQL语句
  • 非常棒的流畅查询API
  • 仍然可以选择使用SQL,但是API比原来的更简单、更好

配置LitePal

使用LitePal的第一步,就是编辑app/build.gradle文件,在dependencies闭包中添加如下内容:

dependencies {implementation 'org.litepal.guolindev:core:3.2.3'
}

接下来需要配置litepal.xml文件。右键app/src/main目录->New->Directory,创建一个assets目录,然后在assets目录下再新建一个litepal.xml文件,接着编辑litepal.xml文件中的内容,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<litepal><dbname value="BookStore" /><version value="1" /><list></list></litepal>

其中,<dbname>标签用于指定数据库名,<version>标签用于指定数据库版本号,<list>标签用于指定所有的映射模型

最后还需要再配置一下LitePalApplication,修改AndroidManifest.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><applicationandroid:name="org.litepal.LitePalApplication"android:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@drawable/icon_2"android:label="@string/app_name"android:roundIcon="@drawable/icon_2"android:supportsRtl="true"android:theme="@style/Theme.Design.Light.NoActionBar"tools:targetApi="31"><!--其他activity代码--></application></manifest>

将项目的application配置为org.litepal.LitePalApplication,这样才能让LitePal的所有功能都可以正常工作。

现在LitePal的配置工作已经全部结束了,下面开始正式使用。

如果将application配置为org.litepal.LitePalApplication红色报错,提供一下解决方案:

1、在根目录下的settings.gradle,添加如下内容

在这里插入图片描述

dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()jcenter()maven { url 'https://jitpack.io' }}
}

2、如果采用方法1未能解决问题,可添加如下内容

在这里插入图片描述

maven{ url 'https://maven.aliyun.com/repository/google'}
maven{ url 'https://maven.aliyun.com/repository/gradle-plugin'}
maven{ url 'https://maven.aliyun.com/repository/public'}
maven{ url 'https://maven.aliyun.com/repository/jcenter'}

这是添加镜像仓库的代码,猜测可能原因是jcenter弃用了,然后LitePal是2021年8月更新到3.2.3,当时jcenter可能没有弃用,在最新的AndroidStudio中就不从Jcenter引入依赖了,导致无法导入LitePal。

创建和升级数据库

LitePal采取的是对象关系映射(ORM)的模式,简单来说,我们使用的编程语言是面向对象语言,而使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射了。

对象关系映射模式赋予一个强大的功能,可以面向对象的思维来操作数据库,而不用再和SQL语句打交道了。

public class Book {private int id;private String author;private double price;private int pages;private String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public int getPages() {return pages;}public void setPages(int pages) {this.pages = pages;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

这是一个典型的Java bean,在Book类中我们定义了id、author、price、pages、name,并生成了相应的getter和setter方法。Book类机会对应数据库中Book表,而类中的每一个字段分别对应了表中的每一个列,这就是对象关系映射最直观的体验。

接下来还需要将Book类添加到映射模型列表中,修改litepal.xml代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<litepal><dbname value="BookStore" /><version value="1" /><list><mapping class="com.weicomp.broadcastreceiver.entity.Book"></mapping></list></litepal>

这里使用<mapping>标签来声明需要配置的映射模型类,注意一定要使用完整的类名。不管有多少模型类需要映射,都使用同样的方式配置在<list>标签下即可。

现在只要进行任意一次数据库的操作,BookStore.db数据库就会自动创建出来。修改对应Activity中的代码,如下所示:

<Buttonandroid:id="@+id/btn_LitePal_Create_database"style="@style/MainButton"android:text="Create database" />

设置点击事件:

mBtnLitePalCreateDatabase = findViewById(R.id.btn_LitePal_Create_database);
mBtnLitePalCreateDatabase.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Connector.getDatabase();}
});

其中,调用Connector.getDatabase()方法就是以一次最简单的数据库操作,只要点击一下按钮,数据库就会自动创建完成了,运行以下程序,然后点击Create database按钮,通过DB Browser (SQLite)查看数据库创建情况,如下所示:

在这里插入图片描述

如果数据库不存在,会创建新数据库;如果数据库已存在,会打开现有数据库。

这里查看BookStore.db文件中,里面有4个表,其中android_metadata表仍然不管用,table_schema表是LitePal内部使用的,我们也可以直接忽视,book表就是根据我们定义的Book类似及类中的字段来自动生成的了。

在这里插入图片描述

在SQLiteOpenHelper来升级数据库的方式,虽然功能实现了,但是升级数据库的时候需要先把之前的表drop,然后重新创建。这是一个非常严重的问题,因为这样会造成数据丢失,每当升级一次数据库,之前表中的数据就全没了。使用LitePal来升级数据库非常非常简单,只需要修改内容,将版本号加1就行了。

比如向book表中添加一个press(出版社)列,直接修改Book类中的代码,添加一个press字段即可,如下所示:

public class Book {private int id;private String author;private double price;private int pages;private String name;private String press;public String getPress() {return press;}public void setPress(String press) {this.press = press;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public int getPages() {return pages;}public void setPages(int pages) {this.pages = pages;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

同时再添加一张Category表,只需要新建一个Category类就可以了,代码如下所示:

public class Category {private int id;private String categoryName;private String categoryCode;public void setCategoryName(String categoryName) {this.categoryName = categoryName;}public void setCategoryCode(String categoryCode) {this.categoryCode = categoryCode;}
}

修改完后,只需要将版本号加1即可。由于这里添加了一个新的模型类,因此也需要将它添加到映射模型列表中。修改litepal.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<litepal><dbname value="BookStore" /><version value="2" /><list><mapping class="com.weicomp.broadcastreceiver.entity.Book"></mapping><mapping class="com.weicomp.broadcastreceiver.entity.Category"></mapping></list></litepal>

重新运行程序,点击Create database按钮,重新数据库文件,如下所示:

使用LitePal添加数据

之前添加数据,需要创建一个ContentValues对象,然后将需要添加的数据put到这个ContentValues对象中,最后再调用SQLiteDatabase的insert()方法将数据添加到数据库表当中。

使用LitePal添加数据,只需要创建出模型类的实例,再将所有要存储的数据设置好,最后调用一下save()方法就可以了

LitePal进行表管理操作时不需要模型类有任何的继承结构,但是进行CRUD操作时就不行了,必须要继承自LitePalSupport类才可以,需要先把继承结构给加上。修改Book类中的代码,如下所示:

public class Book extends LitePalSupport{//...
}

DataSupport类已经被弃用,可以使用**LitePalSupport**类代替

在这里插入图片描述

接着开始向Book表中添加数据,修改LitepalActivity中的代码,代码如下:

public class LitepalActivity extends BaseActivity {private mBtnLitePalLitePalAddData;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_litepal);mBtnLitePalLitePalAddData = findViewById(R.id.btn_LitePal_Add_Data);mBtnLitePalLitePalAddData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Book book = new Book();book.setName("The Da Vinci Code");book.setAuthor("Dan Brown");book.setPages(454);book.setPrice(16.96);book.setPress("Unknown");book.save();Toast.makeText(LitepalActivity.this, "添加数据成功\n价格为:" + book.getPrice(), Toast.LENGTH_SHORT).show();}});}
}

创建了一个Book实例,然后调用Book类中的各种set方法对数据进行设置,最后调用book.save()方法就能完成数据添加操作了。save()方法是从LitepalSupport类中继承而来,除了save()方法,LitepalSupport类还给我们提供了丰富的CRUD方法。

重新运行程序,点击Add data按钮,数据添加完成,打开BookStore.db数据库,查看数据如下所示:

在这里插入图片描述

使用LitePal更新数据

最简单的更新方式就是对已存储的对象重新设值,然后重新调用save()方法即可。那么,什么是已存储的对象呢?

对于LitePal来说,对象是否存储就是根据调用model.isSaved()方法的结果来判断的,返回true就表示已存储,返回false就表示未存储。

实际上只有两种情况下model.isSaved()方法才会返回true,一种情况是model.save()方法去添加数据了,此时model会被认为是已存储的对象。另一种情况是model对象是通过LitePal提供的查询API查出来的,由于是从数据库查到的对象,因此也会被认为是已存储的对象。

由于查询API暂时没有学,因此先学习第一种情况来进行验证:

mBtnLitePalUpdateData = findViewById(R.id.btn_LitePal_Update_Data);
mBtnLitePalUpdateData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Book book = new Book();book.setName("The Lost Symbol");book.setAuthor("Dan Brown");book.setPages(510);book.setPrice(19.95);book.setPress("Unknown");book.save();book.setPrice(10.99);book.save();Toast.makeText(LitepalActivity.this, "修改后的价格为\n" + book.getPrice(), Toast.LENGTH_SHORT).show();}
});

在这里插入图片描述

可以看到Book表中新增了一条数据,但这本书的价格并不是一开始设置的19.95,而是10.99,说明更新操作生效了。但是这种更新方式只能对已存储的对象进行操作,限制性比较大,接下来我们学习另外一种更加灵巧的更新方式——LitePal更新API。修改LitepalActivity中的代码,如下所示:

mBtnLitePalUpdateData = findViewById(R.id.btn_LitePal_Update_Data);
mBtnLitePalUpdateData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Book book = new Book();book.setPrice(14.95);book.setPress("Anchor");book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown");Toast.makeText(LitepalActivity.this, "修改后的价格为\n" + book.getPrice(), Toast.LENGTH_SHORT).show();}
});

在这里插入图片描述

首先New一个Book实例,调用setPrice()和setPress()方法来设置要更新的数据,最后调用updateAll()方法执行更新操作。

updateAll()方法中可以指定一个条件约束,和SQLiteDatabase中update()方法的where参数部分类似,但更加简洁,如果不指定条件语句的话,表示更新所有数据。这里指定将书名是The Lost Symbol并且作者是Dan brown的书价格更新为14.95,出版社更新为Anchor。

不过,在使用updateAll()方法时,想把一个字段的值更新成默认值时,是不可以使用上面的方式来set数据。在Java中任何一种数据类型的字段都有默认值,例如int类型的默认值是0,boolean类型的默认值是false,String类型的默认值是null。当new出一个Book对象时,其实所有字段都已经被初始化成默认值了,比如说pages字段的值就是0。因此,想把数据库表中的pages列更新成0,直接调用book.setPages(0)是不可以的,因为即使不调用这行代码,pages字段本身也是0,LitePal此时是不会对这个列进行更新的。对于所有想要将数据更新成默认值的操作,LitePal统一提供了一个setToDefault()方法,然后传入相应的列名就可以实现了。如下所示:

Book book = new Book();
book.setToDefault("pages");
book.updateAll();

将所有书的页面都更新为0,因为updateAll()方法中没有指定约束条件,因此更新操作对所有数据都生效。

使用LitePal删除数据

使用LitePal删除数据的方式主要有两种,第一种比较简单,直接调用已存储对象的delete()方法就可以了,即调用过save()方法的对象,或者通过LitePal提供的查询API查出来的对象,都是可以直接使用delete()方法来删除数据的。下面直接来看另一种删除数据的方式:

mBtnLitePalDeleteData = findViewById(R.id.btn_LitePal_Delete_Data);
mBtnLitePalDeleteData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {LitePal.deleteAll(Book.class,"price < ?","15");}
});

这里调用了LitePal.deleteAll()方法来删除数据,其中deleteAll()方法的第一个参数用于指定删除哪张表中的数据,Book.class就意味着删除Book表中的数据,后面的参数用于指定约束条件。目前Book表中有两本书,一本书的价格是16.96,另一本价格是14.95,刚好可以看出效果。

在这里插入图片描述

另外,deleteAll()方法如果不指定约束条件,就意味着要删除表中所有数据,和updateAll()方法比较相似。

使用LitePal查询数据

query()方法中使用了第一个参数指明去查询Book表,后面的参数全部为null。使用LitePal完成同样的功能非常简单,只需要这样写:

List<Book> books = LitePal.findAll(Book.class);

没有cursor冗长的参数列表,只需要调用findAll()方法,然后通过Book.class参数指定查询Book表就可以。另外,findAll()方法的返回值是一个Book类型的List集合。

mBtnLitePalQueryData = findViewById(R.id.btn_LitePal_Query_Data);
mBtnLitePalQueryData.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {List<Book> books = LitePal.findAll(Book.class);for (Book book : books) {Log.d(TAG, "book name is " + book.getName());Log.d(TAG, "book author is " + book.getAuthor());Log.d(TAG, "book pages is " + book.getPages());Log.d(TAG, "book price is " + book.getPrice());Log.d(TAG, "book press is " + book.getPress());}}
});

查询代码已经解释过了,接下来就是遍历List集合中的Book对象,并将其中的信息全部打印出来。重新运行一下程序,点击Query Data按钮,查看logcat的打印内容,结果如下:

在这里插入图片描述

除了findAll()方法之外,LitePal还提供了很多其他非常有用的查询API;比如想查询Book表中的第一条数据就可以这样写:

Book firstBook = LitePal.findFirst(Book.class);
Log.d(TAG, "第一本书的价格为: " + firstBook.getPrice());

查询Book表中的最后一条数据就可以这样写:

Book lastBook = LitePal.findLast(Book.class);
Log.d(TAG, "最后一本书的价格为: " + lastBook.getPrice());

还可以通过连缀查询来定值更多的查询功能:

  • select()方法用于指定查询哪几列的数据,对应了SQL当中的select关键字。比如只查name和author这两列的数据:

    List<Book> bookSelect = LitePal.select("name", "author").find(Book.class);
    
  • where()方法用于指定查询的约束条件,对应了SQL当中的where关键字。比如只查询页数大于400的数据:

    List<Book> bookWhere = LitePal.where("pages > ?", "400").find(Book.class);
    
  • order()方法用于指定结果的排序方式,对应了SQL当中的order by关键字。比如将查询结果按照书价从高到低排序:

    List<Book> bookOrder = LitePal.order("price desc").find(Book.class);
    

    其中desc表示降序排列,asc或者不写表示升序排列。

  • limit()方法用于指定查询结果的数量,比如只查表中的前3条数据:

    List<Book> bookLimit = LitePal.limit(3).find(Book.class);
    
  • offset()方法用于指定查询结果的偏移量,比如查询表中的第2条、第3条、第4条试数据:

    List<Book> bookOffset = LitePal.limit(3).offset(1).find(Book.class);
    

    由于limit(3)查询的是前三条数据,这里再加上offset(1)进行一个位置的偏移,就能实现查询第2条、第3条、第4条数据的功能。limit()和offset()方法共同对应了SQL中的limit关键字。

当然,这5个方法进行任意的连缀组合,来完成一个比较复杂的查询操作:

List<Book> books = LitePal.select("name","author","pages").where("pages > ?","400").order("pages").limit(10).offset(10).find(Book.class);

这段代码表示:查询Book表中第11~20条满足页数大于400这个条件的name、author和pages这3列数据,并将查询结果按照页数升序排列。

关于LitePal的查询API已经足够应对绝大多数场景的查询需求。如果有一些特殊需求,LitePal仍然支持使用原生的SQL来进行查询:

Cursor c = LitePal.findBySQL("select * from Book where page > ? and price < ?", "400", "20");

findBySQL()方法返回的是一个Cursor对象,需要通过之前所学的老方式将数据一一取出才行。

.find(Book.class);


- where()方法用于指定查询的约束条件,对应了SQL当中的where关键字。比如只查询页数大于400的数据:```java
List<Book> bookWhere = LitePal.where("pages > ?", "400").find(Book.class);
  • order()方法用于指定结果的排序方式,对应了SQL当中的order by关键字。比如将查询结果按照书价从高到低排序:

    List<Book> bookOrder = LitePal.order("price desc").find(Book.class);
    

    其中desc表示降序排列,asc或者不写表示升序排列。

  • limit()方法用于指定查询结果的数量,比如只查表中的前3条数据:

    List<Book> bookLimit = LitePal.limit(3).find(Book.class);
    
  • offset()方法用于指定查询结果的偏移量,比如查询表中的第2条、第3条、第4条试数据:

    List<Book> bookOffset = LitePal.limit(3).offset(1).find(Book.class);
    

    由于limit(3)查询的是前三条数据,这里再加上offset(1)进行一个位置的偏移,就能实现查询第2条、第3条、第4条数据的功能。limit()和offset()方法共同对应了SQL中的limit关键字。

当然,这5个方法进行任意的连缀组合,来完成一个比较复杂的查询操作:

List<Book> books = LitePal.select("name","author","pages").where("pages > ?","400").order("pages").limit(10).offset(10).find(Book.class);

这段代码表示:查询Book表中第11~20条满足页数大于400这个条件的name、author和pages这3列数据,并将查询结果按照页数升序排列。

关于LitePal的查询API已经足够应对绝大多数场景的查询需求。如果有一些特殊需求,LitePal仍然支持使用原生的SQL来进行查询:

Cursor c = LitePal.findBySQL("select * from Book where page > ? and price < ?", "400", "20");

findBySQL()方法返回的是一个Cursor对象,需要通过之前所学的老方式将数据一一取出才行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/480432.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3 + vite + antdv 项目中自定义图标

前言&#xff1a; 去iconfont-阿里巴巴矢量图标库 下载自己需要的icon图标&#xff0c;下载格式为svg&#xff1b;项目中在存放静态资源的文件夹下 assets 创建一个存放svg格式的图片的文件夹。 步骤&#xff1a; 1、安装vite-plugin-svg-icons npm i vite-plugin-svg-icons …

【H2O2|全栈】Node.js(2)

目录 前言 开篇语 准备工作 npm 概念 常见指令 项目中的包 创建项目 启动项目 服务器搭建 express 基本步骤 搭建应用 创建路由 监听端口 启动服务器 面试相关 结束语 前言 开篇语 本系列博客分享Node.js的相关知识点&#xff0c;本章讲解npm与服务器的简单…

QChart数据可视化

目录 一、QChart基本介绍 1.1 QChart基本概念与用途 1.2 主要类的介绍 1.2.1 QChartView类 1.2.2 QChart类 1.2.3QAbstractSeries类 1.2.4 QAbstractAxis类 1.2.5 QLegendMarker 二、与图表交互 1. 动态绘制数据 2. 深入数据 3. 缩放和滚动 4. 鼠标悬停 三、主题 …

Harbor安装、HTTPS配置、修改端口后不可访问?

Harbor安装、HTTPS配置、修改端口后不可访问&#xff1f; 大家好&#xff0c;我是秋意零。今天分享Harbor相关内容&#xff0c;安装部分可完全参考官方文档&#xff0c;写的也比较详细。 安装Harbor 官方文档&#xff1a;https://goharbor.io/docs/2.12.0/install-config/ …

MTK 展锐 高通 sensorhub架构

一、MTK平台 MTK框架可以分为两部分&#xff0c;AP和SCP。 AP是主芯片&#xff0c;SCP是协处理器&#xff0c;他们一起工作来处理sensor数据。 SCP 是用来处理sensor和audio相关功能和其他客制化需求的一个协处理理器&#xff0c;MTK SCP选择freeRTOS作为操作系统&#xff0c…

JDK的版本演化,JDK要收费吗?

Java版本演化历史 Java的版本历史可以追溯到1995年&#xff0c;以下是Java语言自诞生以来的主要版本及其关键特性&#xff1a; 一、早期版本 Java 1.0&#xff08;1996年1月发布&#xff09; 引入了Java虚拟机&#xff08;JVM&#xff09;和Java应用编程接口&#xff08;API&…

【Code First】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列 &#x1f…

podman 源码 5.3.1编译

1. 构建环境 在麒麟V10服务器操作系统上构建&#xff1a;Kylin-Server-V10-GFB-Release-2204-Build03-ARM64.iso。由于只是编译 podman 源码&#xff0c;没必要特地在物理机或服务上安装一个这样的操作系统&#xff0c;故采用在虚拟机里验证。 2. 安装依赖 参考资料&#xf…

git的使用(简洁版)

什么是 Git&#xff1f; Git 是一个分布式版本控制系统 (DVCS)&#xff0c;用于跟踪文件的更改并协调多人之间的工作。它由 Linus Torvalds 在 2005 年创建&#xff0c;最初是为了管理 Linux 内核的开发。Git 的主要目标是提供高效、易用的版本控制工具&#xff0c;使得开发者…

Vue构建错误解决:(error TS6133)xxx is declared but its value is never read.

TypeScript会检查代码中未使用的变量&#xff0c;如果vscode安装了Vue的语法检查工具&#xff0c;会看到告警提示&#xff0c;再npm run build的时候&#xff0c;这个警告会变成错误 解决方案1&#xff1a;删除定义了未使用的变量 推荐使用这种方案&#xff0c;能保证代码的质…

Perplexica - AI 驱动的搜索引擎

更多AI开源软件&#xff1a; AI开源 - 小众AIhttps://www.aiinn.cn/sources Perplexica 是一个开源的 AI 驱动的搜索工具或 AI 驱动的搜索引擎&#xff0c;可以深入互联网寻找答案。受 Perplexity AI 的启发&#xff0c;它是一个开源选项&#xff0c;不仅可以搜索网络&#xf…

企业后端多租户管理平台

1 简介 此系统在企业后端管理系统上进行的更改&#xff0c;用于快速开发租户管理平台。项目中详细的功能请查看文章&#xff1a;企业后端系统通用模版_后端模板-CSDN博客 支持多租户&#xff0c;支持多租户切换&#xff0c;支持多租户数据隔离&#xff0c;支持多租户数据同步等…

微信小程序蓝牙writeBLECharacteristicValue写入数据返回成功后,实际硬件内信息查询未存储?

问题&#xff1a;连接蓝牙后&#xff0c;调用小程序writeBLECharacteristicValue&#xff0c;返回传输数据成功&#xff0c;查询硬件响应发现没有存储进去&#xff1f; 解决&#xff1a;一直以为是这个write方法的问题&#xff0c;找了很多相关贴&#xff0c;后续进行硬件日志…

Zero to JupyterHub with Kubernetes中篇 - Kubernetes 常规使用记录

前言&#xff1a;纯个人记录使用。 搭建 Zero to JupyterHub with Kubernetes 上篇 - Kubernetes 离线二进制部署。搭建 Zero to JupyterHub with Kubernetes 中篇 - Kubernetes 常规使用记录。搭建 Zero to JupyterHub with Kubernetes 下篇 - Jupyterhub on k8s。 参考&…

电脑无互联网连接怎么解决?分享5种解决方案

无互联网连接是指设备无法与互联网进行通信或连接失败。这可能会导致我们无法正常上网&#xff0c;给我们的日常生活和工作带来很大的不便。但请不要担心&#xff0c;下面将为您介绍一些解决无互联网连接问题的方法。 一、检查网络是否正常连接 首先&#xff0c;确保您的路由器…

Web前端学习_CSS盒子模型

content padding border margin <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS盒子模型</title><style></style> </head> <body> <div class"demo&quo…

HTML CSS JS基础考试题与答案

一、选择题&#xff08;2分/题&#xff09; 1&#xff0e;下面标签中&#xff0c;用来显示段落的标签是&#xff08; d &#xff09;。 A、<h1> B、<br /> C、<img /> D、<p> 2. 网页中的图片文件位于html文件的下一级文件夹img中&#xff0c;…

华为开源操作系统openEuler安装部署

本文主要描述华为开源操作系统openEuler的安装部署。openEuler是面向数字基础设施的开源操作系统&#xff0c;是由开放原子开源基金会&#xff08;OpenAtom Foundation&#xff09;孵化及运营的开源项目&#xff0c;其愿景是为世界提供数字基础设施的开源操作系统&#xff0c;其…

分布式搜索引擎之elasticsearch单机部署与测试

分布式搜索引擎之elasticsearch单机部署与测试 1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器&#xff0c;因此需要让es和kibana容器互联。这里先创建一个网络&#xff1a; docker network create es-net1.2.加载镜像 这里我们采用elasticsearch的7.12.1版本的…

渣土车治理新方案:智能化引领安全与环保新时代

一、渣土车问题现状 1. 盲区众多隐患大&#xff0c;事故频发令人忧。 渣土车盲区多&#xff0c;易引发交通事故&#xff0c;给行人和其他车辆带来严重安全威胁。由于渣土车体积庞大&#xff0c;实际的视觉盲区范围包括半盲区为左车门 1.2 米、右前方 1.5 米、正前方 1.2 米&am…