第一行代码 第6章 数据存储全方案——详解持久化技术

时间:2021-07-22
本文章向大家介绍第一行代码 第6章 数据存储全方案——详解持久化技术,主要包括第一行代码 第6章 数据存储全方案——详解持久化技术使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

整体大纲:

一、     文件存储

1.1  将数据存储到文件

在onDestroy()方法中保存文本数据,先通过openFileOutput(String filename, int mode)方法返回得到FileOutputStream对象,得到这个对象之后再用Java流的方式将数据写入文件。以下为示例代码:

public 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();
      }
   }

}

1.2  从文件中读取数据

在onCreate()方法中恢复文本数据,先通过openFileInput(String filename)方法返回得到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();
}

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

    P.S.对字符串进行非空判断的时候使用了TextUtils.isEmpty()方法(当传入的字符串等于null或者等于空字符串的时候都会返回true)。

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

   mEditText = findViewById(R.id.edit);
   String inputText = load();
   if (!TextUtils.isEmpty(inputText)) {
      mEditText.setText(inputText);
      mEditText.setSelection(inputText.length());
      Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show();
   }
}

二、     SharedPreferences存储

SharedPreferences文件是使用XML格式来对数据进行管理的。

2.1  将数据存储到SharedPreferences中

(1)  获取SharedPreferences对象

  a)   Context类中的getSharedPreferences(String filename, int mode)方法,操作模式只能选MODE_PRIVATE(值为0)

  b)   Activity类中的getPreferences()方法

(2)  调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象。

(3)  向SharedPreferences.Editor对象中添加数据,通过putBoolean()方法、putString()方法等等。

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

以下为示例代码:

SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();  //(1).a)Context方法、(2)
editor.putString("name", "HELLO");  // (3)
editor.putInt("age", 28);
editor.putBoolean("married", false);
editor.apply();  //(4)

2.2  从SharedPreferences中读取数据

SharedPreferences对象中提供了一系列的get(String key, String defValue)方法,如getBoolean()方法、getString()方法等等。

很多应用程序的偏好设置功能其实都用到了SharedPreferences技术。

以下为示例代码:

SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
int age = pref.getInt("age", 0);
boolean married = pref.getBoolean("married", false);

三、     SQLite数据库存储

SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百KB的内存就足够了。SQLite不仅支持标准的SQL语法,还遵循了数据库的ACID事务。

SQLiteOpenHelper帮助类可以对数据库进行创建和升级。这是一个抽象类,里面有两个抽象方法,分别是onCreate()和onUpgrade()。我们需要创建一个自己的帮助类去继承它并在里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑,一般在重写的onCreate()方法中处理一些创建表的逻辑

SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()和getWritableDatabase(),当数据库不可写入的时候两者有区别。

SQLiteOpenHelper中重写构造方法。

SQLite数据库的数据类型很简单,integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。primary key表示主键,autoincrement表示自增长。

以下为自己的帮助类代码:

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

   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 db) {
      db.execSQL(CREATE_BOOK);
      db.execSQL(CREATE_CATEGORY);
      Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
   }

   @Override
   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      db.execSQL("drop table if exists Book");
      db.execSQL("drop table if exists Category");
      onCreate(db);
   }
}

3.1  创建数据库

构建出SQLiteOpenHelper的实例之后,再调用它的getWritableDatabase()或getReadableDatabase()方法就能够创建数据库了,以下为示例代码:

dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
dbHelper.getWritableDatabase();

3.2  升级数据库(以再建一张表为例)

在重写的onCreate()方法中添加db.execSQL()建表执行语句,接着在重写的onUpgrade()方法中执行两条DROP语句,如果数据库中表已存在,就删除这些表再调用onCreate()方法重新创建,但这样会造成数据丢失。最后,修改构造方法中的第四个参数(代表当前数据库的版本号)+1,则onUpgrade()方法可以得到执行,以下为示例代码:

dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2); 
dbHelper.getWritableDatabase();

3.3  添加数据

先获取到了SQLiteDatabase对象,然后使用ContentValues来对要添加的数据进行组装,接下来调用了insert(String table, String nullColumnHack, android.content.ContentValues values)方法将数据添加到表中。ContentValues对象提供了一系列的put()方法重载,只要将表中的每个列名以及相应的待添加数据传入即可,以下为示例代码:

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.clear();
// 开始组装第二条数据
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); // 插入第二条数据

3.4  更新数据

用update(String table, android.content.ContentValues values, String whereClause, String[] whereArgs)方法对数据进行更新,第三、四个参数用于约束更新某一行或某几行中的数据,不指定则默认所有行,以下为示例代码:

SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" });

P.S.第三个参数对应SQL语句的where部分,表示更新所有name=?的行(?是一个占位符),第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。

3.5  删除数据

delete(String table, String whereClause, String[] whereArgs)方法专门用于删除数据,以下为示例代码:

SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });

3.6  查询数据

query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)方法用于数据查询。调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。

Cursor对象的moveToFirst()方法将数据的指针移动到第一行的位置,然后进去循环中遍历查询到的每一行数据。在这个循环中可以通过Cursor的getColumnIndex(String columnName)方法获取到某一列在表中对应位置的索引,然后将这个索引传入到对应的取值方法getXxxx(int columnIndex)中,就可以得到从数据库中读取到的数据了。最后调用close()方法关闭Cursor,以下为示例代码:

SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查询Book表中所有的数据
Cursor cursor = db.query("Book", 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, "onClick: book name is " + name);
      Log.d(TAG, "onClick: book author is " + author);
      Log.d(TAG, "onClick: book pages is " + pages);
      Log.d(TAG, "onClick: book price is " + price);
   } while (cursor.moveToNext());
}
cursor.close();

3.7  使用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);

四、     使用LitePal操作数据库

LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,即将面向对象的语言和面向关系的数据库之间建立一种映射关系。

LitePal项目主页有详细的使用文档,地址是:https://github.com/guolindev/LitePal

4.1  配置LitePal

(1)  在app/builde.gradle文件中声明该开源库的引用,以下为示例代码:

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'org.litepal.guolindev:core:3.2.3' //在LitePal的项目主页上查看最新版本号
}

(2)  配置spp/src/main/assets/litepal.xml文件,文件内容如下:

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

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

(3)  配置LitePalApplication(让LitePal的所有功能都可以正常工作)。

  a)   修改AndroidManifest.xml中的代码,如下所示:

<application
      android:name="org.litepal.LitePalApplication"
     ……>
   ……
</application>

  b)   如果已经有自己的Application配置,例如:

<manifest>
<application
        android:name="com.example.MyOwnApplication"
        ...
    >
        ...
    </application>
</manifest>

        则只需要在你自己的Application中使用LitePal.initialize(context),以下为示例代码:

public class MyOwnApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LitePal.initialize(this);
    }
    ...
}

4.2  创建数据库

ORM模式使我们可以用面向对象的思维来操作数据库。

(1)建表。定义一个Book类,Book类就会对应数据库中的Book表,而类中的每一个字段分别对应了表中的每一列,这就是对象关系映射最直观的体验,以下为示例代码:

public class Book {
   private int id;
   private String author;
   private double price;
   private int pages;
   private String name;

   //省略getter和setter方法
}

(2)修改配置。接下来将Book类添加到映射模型列表中,修改litepal.xml中的代码,如下所示:

<litepal>
   <dbname value="BookStore"></dbname>
   <version value="1"></version>
   <list>
      <mapping class="com.bignerdranch.android.litepaltest.Book"></mapping>
   </list>
</litepal>

P.S.<mapping>标签来声明我们要配置的映射模型类,一定要使用完整的类名。所有的映射模型类都配置在<list>标签下即可。

(3)创建数据库。进行任意一次数据库的操作,以下为示例代码:

LitePal.getDatabase();

4.3  升级数据库

LitePal避免了SQLiteOpenHelper升级数据库方式中造成表数据丢失的问题,不需要思考任何逻辑,直接修改任何想改的内容,然后将版本号加1就行了。

以Book表中添加一个press(出版社)列,再添加一张Categroy表为例,以下为示例代码:

public class Book {
   private int id;
   private String author;
   private double price;
   private int pages;
   private String name;
   private String press;

   //省略getter和setter方法
}

public class Category {
   private int id;
   private String categoryName;
   private int categoryCode;

   //省略setter方法
}

litepal.xml:

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

4.4  使用LitePal添加数据

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

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();

P.S. LitePal进行表管理操作时不需要模型类有任何的继承结构,但是进行CRUD操作时就不行了,必须要继承自LitePalSupport类才行。

public class Book extends LitePalSupport { }

4.5  使用LitePal更新数据

最简单的一种更新方式(使用对象限制性比较大)就是对已存储的对象重新设置,然后重新调用save()方法,示例代码如下:

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();

P.S.调用model.isSaved()方法的结果为true表示已存储对象,false表示未存储对象。实际上只有在两种情况下model.isSave()方法才返回true,一种情况是已经调用过model.save()方法去添加数据了。另一种情况是model对象是通过LitePal提供的查询API查出来的(能从数据库中查到当然已存储)。

 

更为灵巧的更新方式,先新建一个model实例,再直接通过setXxx()方法设置要更新的数据,最后调用updateAll(java.lang.String... conditions)方法,以下为示例代码:

Book book = new Book();
book.setPrice(16.95);
book.setPress("Anchor");
book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown"); 

P.S.不可以使用上述方式来set数据使字段值更新成对应默认值。若想将数据更新成默认值,可使用LitePal提供的setToDefault(String fieldName)方法,以下为示例代码:

Book book = new Book();
book.setToDefault("pages");
book.updateAll();  //若不指定条件语句就表示更新所有数据

4.6  使用LitePal删除数据

主要有两种方式,第一种是直接调用已存储对象的delete()方法,第二种调用了LitePal.deleteAll(Class<?> modelClass, String... conditions)方法来删除数据。与updateAll()类似,deleteAll()方法如果不指定约束条件,就意味着要删除表中的所有数据,以下为示例代码:

LitePal.deleteAll(Book.class, "price < ?", "15");

4.7  使用LitePal查询数据

使用LitePal.findAll(Class<?> modelClass, Long... ids)方法,findAll()方法的返回值是一个Model类型的List集合,即不用再通过Cursor对象一行行取值,LitePal已经自动完成了赋值操作,以下为示例代码:

List<Book> books = LitePal.findAll(Book.class); //查询表中的所有数据
for (Book book : books) {
   Log.d(TAG, "onClick: book name is " + book.getName());
   Log.d(TAG, "onClick: book author is " + book.getAuthor());
   Log.d(TAG, "onClick: book pages is " + book.getPages());
   Log.d(TAG, "onClick: book price is " + book.getPrice());
   Log.d(TAG, "onClick: book press is " + book.getPress());
}

LitePal中其他的查询API

  • findFirst(Class<T> modelClass)方法用于查询表中的第一条数据:
Book firstBook = LitePal.findFirst(Book.class);
  • findLast(Class<T> modelClass)方法用于查询表中的最后一条数据:
Book lastBook = LitePal.findLast(Book.class);
连缀查询
  • select(String... columns)方法用于指定查询哪几列的数据:
List<Book> books1 = LitePal.select("name","author").find(Book.class);
  • where(String... conditions)方法用于指定查询的约束条件:
List<Book> books2 = LitePal.where("pages > ?", "400").find(Book.class);
  • order(String column)方法用于指定结果的排序方式,其中desc表示降序排列,asc或者不写表示升序排列:
List<Book> books3 = LitePal.order("price desc").find(Book.class);
  • limit(int value)方法用于指定查询结果的数量,limit(3)表示只查表中的前3条数据:
List<Book> books4 = LitePal.limit(3).find(Book.class);
  • offset(int value)方法用于制定查询结果的偏移量,limit(3).offset(1)表示查询表中的第2、3、4条数据:
List<Book> books5 = LitePal.limit(3).offset(1).find(Book.class);
  • LitePal.findBySQL(String... sql)方法用于进行原生查询,该方法返回的是一个Cursor对象,还需要用SQLite数据库中学习的操作方式将数据一一取出才行:
Cursor cursor = LitePal.findBySQL("select * from Book where pages > ? and price < ?", "400", "20");

END

原文地址:https://www.cnblogs.com/hi-yxiao/p/15044159.html