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">

    <EditText
        android: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;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_database01);
        editText01 = findViewById(R.id.edit01);
    }

    @Override
    protected 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">

    <Button
        android:id="@+id/btn_save_data"
        style="@style/MainButton"
        android:text="Save data" />

</LinearLayout>

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

public class SharedPreferencesActivity extends BaseActivity {

    private Button mBtnSaveData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sharedpreferences);

        mBtnSaveData = findViewById(R.id.btn_save_data);
        mBtnSaveData.setOnClickListener(new View.OnClickListener() {
            @Override
            public 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方法都接收两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回。

<Button
        android: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")
    @Override
    public 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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingLeft="10dp"
        android:paddingRight="10dp">

        <TextView
            android:id="@+id/tv_Account"
            android:layout_width="80dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Account:"
            android:textStyle="bold" />

        <EditText
            android:id="@+id/et_Account"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="30" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingLeft="10dp"
        android:paddingRight="10dp">

        <TextView
            android:id="@+id/tv_Password"
            android:layout_width="80dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Password:"
            android:textStyle="bold" />

        <EditText
            android:id="@+id/et_Password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="30" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <CheckBox
            android:id="@+id/cb_remember_pass"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Remember password"
            android:textSize="18sp" />

    </LinearLayout>

    <Button
        android: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")
    @Override
    protected 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() {
            @Override
            public 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;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_BOOK);
        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public 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">

    <Button
        android: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;

    @Override
    protected 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() {
            @Override
            public 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)";

    //添加Category
    public 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;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_BOOK);
        sqLiteDatabase.execSQL(CREATE_CATEGORY);    //添加Category
        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public 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;

    @Override
    protected 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() {
            @Override
            public 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">

    <Button
        android:id="@+id/btn_create_database"
        style="@style/MainButton"
        android:text="Create database" />

    <Button
        android: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;

    @Override
    protected 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() {
            @Override
            public void onClick(View view) {
                dbHelper.getWritableDatabase();
            }
        });

        mBtnAddData = findViewById(R.id.btn_Add_Data);
        mBtnAddData.setOnClickListener(new View.OnClickListener() {
            @Override
            public 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对象,要把更多的数据在这里组装进去。第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话,默认更新所有行。

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

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

mBtnUpdateData = findViewById(R.id.btn_Update_Data);
mBtnUpdateData.setOnClickListener(new View.OnClickListener() {
    @Override
    public 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()方法,专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,第二、第三个参数又用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。

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

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

mBtnDeleteData = findViewById(R.id.btn_Delete_Data);
mBtnDeleteData.setOnClickListener(new View.OnClickListener() {
    @Override
    public 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对象,查询到的所有数据都将从这个对象中取出。

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

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

mBtnQueryData = findViewById(R.id.btn_Query_Data);
mBtnQueryData.setOnClickListener(new View.OnClickListener() {
    @Override
    public 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">

    <application
        android: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中的代码,如下所示:

<Button
    android: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() {
    @Override
    public 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;

    @Override
    protected 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() {
            @Override
            public 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() {
    @Override
    public 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() {
    @Override
    public 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() {
    @Override
    public 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() {
    @Override
    public 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对象,需要通过之前所学的老方式将数据一一取出才行。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识

STM32-FOC&#xff08;1&#xff09;STM32 电机控制的软件开发环境 STM32-FOC&#xff08;2&#xff09;STM32 导入和创建项目 STM32-FOC&#xff08;3&#xff09;STM32 三路互补 PWM 输出 STM32-FOC&#xff08;4&#xff09;IHM03 电机控制套件介绍 STM32-FOC&#xff08;5&…

ubuntu 安装proxychains

在Ubuntu上安装Proxychains&#xff0c;你可以按照以下步骤操作&#xff1a; 1、更新列表 sudo apt-update 2、安装Proxychains sudo apt-get install proxychains 3、安装完成后&#xff0c;你可以通过编辑/etc/proxychains.conf文件来配置代理规则 以下是一个简单的配置示例&…

ZooKeeper 基础知识总结

先赞后看&#xff0c;Java进阶一大半 ZooKeeper 官网这样介绍道&#xff1a;ZooKeeper 是一种集中式服务&#xff0c;用于维护配置信息、命名、提供分布式同步和提供组服务。 各位hao&#xff0c;我是南哥&#xff0c;相信对你通关面试、拿下Offer有所帮助。 ⭐⭐⭐一份南哥编写…

visionpro官方示例分析(一) 模板匹配工具 缺陷检测工具

1.需求&#xff1a;找出图像中的这个图形。 2.步骤 使用CogPMAlignTool工具&#xff0c;该工具是模板匹配工具&#xff0c;见名知意&#xff0c;所谓模板匹配工具就是说先使用该工具对一张图像建立模板&#xff0c;然后用这个模板在其他图像上进行匹配&#xff0c;匹配上了就说…

代码随想录算法训练营第六十天|Day60 图论

Bellman_ford 队列优化算法&#xff08;又名SPFA&#xff09; https://www.programmercarl.com/kamacoder/0094.%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B4%A7%E7%89%A9%E8%BF%90%E8%BE%93I-SPFA.html 本题我们来系统讲解 Bellman_ford 队列优化算法 &#xff0c;也叫SPFA算法&#xf…

LAMP环境的部署

一、软件安装介绍 在Linux系统中安装软件有rpm安装、yum安装、源码安装等方法&#xff0c;在这里主要给大家介绍 yum 安装&#xff0c;这是一种最简单方便的一种安装方法。 YUM&#xff08;Yellow dog Upadate Modifie&#xff09;是改进版的 RPM 管理器&#xff0c;很好地解…

搭建文件服务器并使用Qt实现文件上传和下载(带账号和密码)

文章目录 0 背景1 搭建文件服务器2 代码实现文件上传和下载2.1 在pro文件中添加网络支持2.2 创建网络管理类2.3 文件上传2.4 文件下载 3 扩展&#xff08;其他方法实现文件上传和下载&#xff09;3.1 python3.2 npm3.3 ftp服务器 4 完整的代码 0 背景 因为需要使程序具备在远程…

matlab导出3D彩色模型(surface类转stl,并对白模上色)

在matlab中绘制3维图形时&#xff0c;需要将3维图形导出到PPT中展示。但是直接导出图片效果欠佳&#xff0c;无法全方位展示。 最近学习了如何将matlab中的图形导出为stl模型&#xff0c;然后再采用简单的方法对模型上色。 中间尝试过matlab导出stl、ply、3dm等多种格式&…

Java项目中加缓存

Java项目中加缓存 1.更新频率低&#xff1b;但读写频率高的数据很适合加缓存&#xff1b; 2.可以加缓存的地方很多&#xff1a;浏览器的缓存&#xff1b;CDN的缓存&#xff1b;服务器的缓存&#xff1b; 本地内存&#xff1b;分布式远端缓存&#xff1b; 加缓存的时候不要…

VTK的基本概念(一)

文章目录 三维场景的基本要素1.灯光2.相机3.颜色4.纹理映射 三维场景的基本要素 1.灯光 在三维渲染场景中&#xff0c;可以有多个灯光的存在&#xff0c;灯光和相机是三维渲染场景的必备要素&#xff0c;如果没有指定的话&#xff0c;vtkRenderer会自动创建默认的灯光和相机。…

【C知道】数据包捕获(wire shark)

请解释一下数据包捕获和分析工具&#xff08;如Wireshark&#xff09;的工作原理和用途。 数据包捕获和分析工具&#xff0c;例如Wireshark&#xff08;以前称为 Ethereal&#xff09;&#xff0c;是一种网络协议分析软件&#xff0c;它允许用户实时监控计算机网络中的数据传输…

浮点数计算,不丢失精度

在js中对于浮点数直接计算会存在精度丢失的情况&#xff0c;为了保证精度问题&#xff0c;可以做如下处理&#xff1a; 浮点数精度计算 主要流程如下&#xff1a; 浮点数转换成整数 示例代码如下 /** 将一个浮点数转成整数&#xff0c;返回整数和倍数。如 3.14 >> 314…

计算机网络八股整理(三)

目录 计算机网络八股&#xff08;三&#xff09;传输层1&#xff1a;说一下tcp的头部&#xff1f;2&#xff1a;tcp三次握手的过程说一下&#xff1f;拓展linux中查看tcp状态&#xff1a; 3:tcp为什么需要三次握手建立连接&#xff1f;4&#xff1a;tcp三次握手&#xff0c;如果…

C#基础控制台程序

11.有一个54的矩阵&#xff0c;要求编程序求出其中值最大的那个元素的值&#xff0c;以及其所在的行号和列号。 12.从键盘输入一行字符&#xff0c;统计其中有多少个单词&#xff0c;单词之间用空格分隔开。 13.输入一个数&#xff0c;判断它是奇数还是偶数&#xff0c;如果…

小程序-基于java+SpringBoot+Vue的微信小程序养老院系统设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…

LeetCode—74. 搜索二维矩阵(中等)

仅供个人学习使用 题目描述&#xff1a; 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。 每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true…

命令行使用ssh隧道连接远程mysql

本地电脑A 跳板机B 主机2.2.2.2 用户名 B ssh端口号22 登录密码bbb 远程mysql C 地址 3.3.3.3 端口号3306 用户名C 密码ccc A需要通过跳板机B才能访问C; navicat中配置ssh可以实现在A电脑上访问C 如何实现本地代码中访问C呢? # 假设本地使…

海康VsionMaster学习笔记(学习工具+思路)

一、前言 VisionMaster算法平台集成机器视觉多种算法组件&#xff0c;适用多种应用场景&#xff0c;可快速组合算法&#xff0c;实现对工件或被测物的查找测量与缺陷检测等。VM算法平台依托海康威视在图像领域多年的技术积淀&#xff0c;自带强大的视觉分析工具库&#xff0c;可…

⭐️ GitHub Star 数量前十的工作流项目

文章开始前&#xff0c;我们先做个小调查&#xff1a;在日常工作中&#xff0c;你会使用自动化工作流工具吗&#xff1f;&#x1f64b; 事实上&#xff0c;工作流工具已经变成了提升效率的关键。其实在此之前我们已经写过一篇博客&#xff0c;跟大家分享五个好用的工作流工具。…

视频汇聚平台Liveweb国标GB28181视频平台监控中心设计

在现代安防视频监控领域&#xff0c;Liveweb视频汇聚平台以其卓越的兼容性和灵活的拓展能力&#xff0c;为用户提供了一套全面的解决方案。该平台不仅能够实现视频的远程监控、录像、存储与回放等基础功能&#xff0c;还涵盖了视频转码、视频快照、告警、云台控制、语音对讲以及…