《第一行代码》学习笔记:使用LitePal操作数据库

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

一、配置LitePal

编辑app/build.gradle文件,在dependencies闭包中添加如下内容:(旧版已废弃)注意要点击Sync now

implementation 'org.litepal.guolindev:core:3.1.1'

前面部分是固定的,最后的3.1.1是版本号的意思,最新的版本号可以到LitePal的项目主页上去查看。

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

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

标签用于指定数据库名, 标签用于指定数据库版本号,标签用于指定所有的映射模型

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.example.litepaltest">
	<application
		android:name="org.litepal.LitePalApplication"
		android:allowBackup="true"
		android:icon="@mipmap/ic_launcher"
		android:label="@string/app_name"
		android:supportsRtl="true"
		android:theme="@style/AppTheme">
		...
	</application>
</manifest>

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

二、创建和升级数据库

- 创建

之前创建数据库是通过自定义一个类继承自SQLiteOpenHelper,然后在onCreate() 方法中编写建表语句来实现的,而使用LitePal就不用再这么麻烦了。首先将上节activity_main.xml布局文件从DatabaseTest项目复制到LitePalTest项目。

LitePal采取的是对象关系映射(ORM)的模式。我们使用的编程语言是面向对象语言,而使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射。因此可以用面向对象的思维来操作数据库,而不用再和SQL语句打交道。

定义一个Book 类:

public class Book {
	private int id;
	private String author;
	private double price;
	private int pages;
	private String name;
	//get和set方法等省略
}

Book 类会对应数据库中的Book表,而类中的每一个字段分别对应了表中的每一个列。接下来还需要将Book 类添加到映射模型列表当中,修改litepal.xml中的代码,如下所示:(在list中增加了mapping)

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

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

这样就已经把所有工作都完成了,现在只要进行任意一次数据库的操作,BookStore.db数据库应该就会自动创建出来。那么我们修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Button createDatabase = (Button) findViewById(R.id.create_database);
		createDatabase.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
					LitePal.getDatabase();
			}
		});
	}
}

其中,调用LitePal.getDatabase() 方法就是一次最简单的数据库操作,只要点击一下按钮,数据库就会自动创建完成。可使用adb shell查看数据库创建情况、sqlite3 命令打开BookStore.db文件、.schema 命令来查看建表语句。

- 升级

使用LitePal来升级数据库非常非常简单,你完全不用思考任何的逻辑,只需要改你想改的任何内容,然后将版本号加1就行了。比如想要向Book表中添加一个press(出版社)列,直接修改Book 类中的代码,添加一个press 字段即可,如下所示:

public class Book {
	...
	private String press;
	...
}

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

public class Category {
	private int id;
	private String categoryName;
	private int categoryCode;
	//get和set方法等省略
}

改完了所有我们想改的东西,只需要记得将版本号加1就行了。当然由于这里还添加了一个新的
模型类,因此也需要将它添加到映射模型列表中。修改litepal.xml中的代码(version和mapping),如下所示:

<litepal>
	<dbname value="BookStore" ></dbname>
	<version value="2" ></version>
	<list>
		<mapping class="com.example.litepaltest.Book"></mapping>
		<mapping class="com.example.litepaltest.Category"></mapping>
	</list>
</litepal>

SQLiteOpenHelper来升级数据库的方式,虽说功能是实现了,但是升级数据库的时候需要先把之前的表drop掉,然后再重新创建才行。这其实是一个非常严重的问题,因为这样会造成数据丢失。LitePal能自动保留之前表中的所有数据,这样就再也不用担心数据丢失的问题。

三、使用LitePal添加数据

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

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

LitePal进行表管理操作时不需要模型类有任何的继承结构,但是进行CRUD操作时就不行了,必须要继承自LitePalSupport类(DataSupport 已舍弃)。修改Book 类中的代码,如下所示:

public class Book extends LitePalSupport {
	...
}

接着我们开始向Book表中添加数据,修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		...
		Button addData = (Button) findViewById(R.id.add_data);
		addData.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				Book book = new Book();
				book.setName("The Da Vinci Code");
				book.setAuthor("Dan Brown");
				book.setPages(454);
				book.setPrice(16.96);
				book.setPress("Unknow");
				book.save();
			}
		});
	}
}

在添加数据按钮的点击事件里面,首先是创建出了一个Book的实例,然后调用Book 类中的各种set 方法对数据进行设置,最后再调用book.save() 方法就能完成数据添加操作了。这个save() 方法是从LitePalSupport 类中继承而来的了。除了save() 方法之外,LitePalSupport类还给我们提供了丰富的CRUD方法。

四、使用LitePal更新数据

更新数据要比添加数据稍微复杂一点,因为它的API接口比较多。

  1. 方法:对已存储的对象重新设值

首先,最简单的一种更新方式就是对已存储的对象重新设值,然后重新调用save() 方法即可。对于LitePal来说,对象是否已存储就是根据调用model.isSaved() 方法的结果来判断的,返回true 就表示已存储,返回false 就表示未存储。实际上只有在两种情况下model.isSaved() 方法才会返回true ,一种情况是已经调用过model.save() 方法去添加数据了,此时model 会被认为是已存储的对象。另一种情况是model 对象是通过LitePal提供的查询API查出来的,由于是从数据库中查到的对象,因此也会被认为是已存储的对象。在此通过第一种情况来进行验证。在MainActivity中增加代码,如下所示:

Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		Book book = new Book();
		book.setName("The Lost Symbol");
		book.setAuthor("Dan Brown");
		book.setPages(510);
		book.setPrice(19.95);
		book.setPress("Unknow");
		book.save();
		book.setPrice(10.99);
		book.save();
	}
});

在更新数据按钮的点击事件里面,我们先是添加了一条Book数据,然后调用setPrice() 方法将这本书的价格进行了修改,之后再次调用了save() 方法。此时LitePal会发现当前的Book 对象是已存储的,因此不会再向数据库中去添加一条新数据,而是会直接更新当前的数据。但是这种更新方式只能对已存储的对象进行操作,限制性比较大。

  1. 方法:使用updateAll方法

在MainActivity中增加代码,如下所示:

Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		Book book = new Book();
		book.setPrice(14.95);
		book.setPress("Anchor");
		book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown");
	}
});

可以看到,这里我们首先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() 方法来删除数据的。
在MainActivity中增加代码,如下所示:

Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		DataSupport.deleteAll(Book.class, "price < ?", "15");
	}
});

这里调用了DataSupport.deleteAll() 方法来删除数据,其中deleteAll() 方法的第一个参数用于指定删除哪张表中的数据,Book.class就意味着删除Book表中的数据,后面的参数用于指定约束条件,应该不难理解。那么这行代码的意思就是,删除Book表中价格低于15的书。

六、使用LitePal查询数据

之前的查询代码(查询这张表中的所有数据):

Cursor cursor = db.query("Book", null, null, null, null, null, null);

使用LitePal如何完成同样的功能:

List<Book> books = DataSupport.findAll(Book.class)

在MainActivity中增加代码,如下所示:

 Button queryButton = (Button) findViewById(R.id.query_data);
queryButton.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
			List<Book> books = DataSupport.findAll(Book.class);
			for (Book book: books) {
				Log.d("MainActivity", "book name is " + book.getName());
				Log.d("MainActivity", "book author is " + book.getAuthor());
				Log.d("MainActivity", "book pages is " + book.getPages());
				Log.d("MainActivity", "book price is " + book.getPrice());
				Log.d("MainActivity", "book press is " + book.getPress());
		}
	}
});

这段代码将遍历List集合中的Book 对象,并将其中的信息全部打印出来。

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

Book firstBook = DataSupport.findFirst(Book.class);

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

Book lastBook = DataSupport.findLast(Book.class);

还可以通过连缀查询来定制更多的查询功能。
select() 方法用于指定查询哪几列的数据,对应了SQL当中的select 关键字。比如只查name 和author 这两列的数据,就可以这样写:

List<Book> books = DataSupport.select("name", "author").find(Book.class);

where() 方法用于指定查询的约束条件,对应了SQL当中的where 关键字。比如只查页数大于400的数据,就可以这样写:

List<Book> books = DataSupport.where("pages > ?", "400").find(Book.class);

order() 方法用于指定结果的排序方式,对应了SQL当中的order by 关键字。比如将查询结果按照书价从高到低排序,就可以这样写(其中desc 表示降序排列,asc 或者不写表示升序排列):

List<Book> books = DataSupport.order("price desc").find(Book.class);

limit() 方法用于指定查询结果的数量,比如只查表中的前3条数据,就可以这样写:

List<Book> books = DataSupport.limit(3).find(Book.class);

offset() 方法用于指定查询结果的偏移量,比如查询表中的第2条、第3条、第4条数据,就可以这样写:

List<Book> books = DataSupport.limit(3).offset(1).find(Book.class);

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

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

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

如果有一些特殊需求,上述的API都满足不了的时候,LitePal仍然支持使用原生的SQL来进行查询:

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

调用DataSupport.findBySQL() 方法来进行原生查询,其中第一个参数用于指定SQL语句,后面的参数用于指定占位符的值。注意findBySQL() 方法返回的是一个Cursor 对象,接下来你还需要通过之前所学的方式将数据一一取出才行。

你可能感兴趣的:(Android)