Android数据库之SharedPreferences、SQLite、Room
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
一、SharedPreferences
要想使用SharePreferences来存储数据首先需要获取到SharedPreferences对象。Android中提供了三种方法用于得到SharedPreferences对象
1.Context类中的getSharedPreferences()方法
第一个参数用于指定SharedPreferences文件名称如果指定的文件不存在则会创建一个。第二个参数用于指定操作模式
MODE_PRIVATE表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。
例如
//获取对象
SharePreferences.Editor editor=getSharedPreferences("data",MODE_PRIVATE).edit();
//进行存储
editor.putString("name","Tom);
editor.putInt("age",28);
editor.apply();
//读取数据
SharedPreference pref=getSharedPreferences("data",MODE_PRIVATE);
/*
通过getSharedPreferences()方法得到了SharedPreferences对象然后再调用getString(),getInt()获取前面所存储的姓名和年龄如果没有找到相应的值就会使用方法中传入的默认值来代替
*/
String name=pref.getString("name","");
int age=pref.getInt("age",0);
如果存储的key值一样后存储的会覆盖前存储的比如
pref.getString(“sex”,“女”)
pref.getString(“sex”,“男”)
最后shared_prefs文件夹文件只会显示
<map>
<string name="sex">男</string>
</map>
2.Activity类中的getPreferences()方法
这个方法和Context类中的getSharedPreferences()方法很相似不过它只接受一个操作模式这个方法会自动将当前活动的类名作为SharedPreferences的文件名
3.PreferenceManager类中的getDefaultSharedPreferences()方法
这是一个静态方法他接收一个Context参数并自动使用当前应用程序的包作为前缀来命名SharedPreferences文件
例如
//获取对象
SharedPreferences.Editor editor = PreferenceManager.
getDefaultSharedPreferences(WeatherActivity.this)
.edit();
//添加数据
editor.putString("weather", responseText);
//将添加的数据提交从而完成数据存储操作
editor.apply();
//得到SharedPreferences对象
SharedPreferences prefs = PreferenceManager.
getDefaultSharedPreferences(this);
//读取数据
String weatherString = prefs.getString("weather", null);
String bingPic=prefs.getString("bing_pic",null);
向SharedPreferences文件存储数据分为三步
1.调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象
2.向SharedPreferences.Editor对象添加数据比如添加一个布尔型数据就使用putBoolean()方法添加一个字符串则使用putString()方法以此类推
3.调用apply()方法将添加的数据提交从而完成数据存储操作
二、SQLite
1.建立数据库
Android提供了一个SQLiteOpenHelper帮助类,借助这个类我们可以对数据库进行创建和升级。SQLiteOpenHelper是一个抽象类我们要想使用他的话就需要创建一个自己的帮助类去继承他。SQLiteOpenHelper中有两个抽象方法分别是onCreate和onUpgrade我们必须在自己的帮助类中重写这两个方法分别在这两个方法去实现创建、升级数据库。
SQLiteOpenHelper中有两个构造方法重写我们一般使用参数少的那个这个构造方法中接受4个参数第一个是Context第二个是数据库名第三个是查询数据库时返回的一个自定义的Cursor一般都是传入null。第四个参数表示当前数据库的版本号可用于对数据库进行升级操作。构件出SQLiteOpenHelper的实例之后再调用它的getReadableDatabase()或getWrittableDatabase()方法就能创建数据库了。
SQLiteDatabase的execSQL()方法去执行建表语句
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;
}
//表如果已经存在就不会在执行onCreate()方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);//这是创建表的语句又因为在onCreate方法中所以会
//在创建数据库链接getWritableDatabase的时候调用这样的话就能创建表
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_LONG).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public class MainActivity extends AppCompatActivity {
//implements View.OnClickListener{
private MyDatabaseHelper dbHelper;
private Button createDatabase,addData,updateData,delete,query;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建数据库
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,1);
//当版本从1变到2的时候就会让onUpgrade()方法执行
// init();
createDatabase=(Button) findViewById(R.id.ceate_database);
addData=(Button) findViewById(R.id.Add_data);
updateData=(Button) findViewById(R.id.update_data);
delete=(Button) findViewById(R.id.delete_data);
query=(Button) findViewById(R.id.query_data);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
2.升级数据库
onUpgrade()方法是用于对数据库进行升级的。目前就已经有一张Book表用于存放书的各种详细数据如果我们想再添加一张Category表用于记录图书的分类该怎么做呢在之前的代码中添加以下语句
public static final String CREATE_CATEGORY = "create table Category(" + "id integer primary key autoincrement,"
+ "category_name text, " + "category_code integer)";
//表如果已经存在就不会在执行onCreate()方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);//这是创建表的语句又因为在onCreate方法中所以会
//在创建数据库的时候调用这样的话创建的数据库的同时也能创建表
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_LONG).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//如果存在两张表中的一个就都删除掉然后再调用onCreate方法重新创建
db.execSQL("drop table if exists Book");//如果存在Book表删掉
db.execSQL("drop table if exists Category");//如果存在Category表删掉
onCreate(db);
}
因为之前的BookStore.db数据库已经存在了之后不管怎么点击createDatabase按钮MyDatabaseHelper的onCreate()方法都不会再执行因此填加的新表也无法创建了。
此时我们可以在onUpgrade方法里执行两条DROP语句如果发现创建了Book表或者Category表。就将这张表删除然后再调用onCreate方法重新创建。
==如何让onUpgrade方法得到执行==直接将SQLiteOpenHelper的构造方法里接受的第四个参数。他表示当前数据库的版本号之前我们传入的是1现在传入一个比1大的数一般要比原来的大就可以让onUpgrade()方法得到执行。如dbHelper=new MyDatabaseHelper(this,“BookStore.db”,null,2);
3.添加数据库
SQLiteDatabase中提供了一个insert()方法,这个方法就是专门用于添加数据。他接收3个参数第一个参数是表名第二个参数是用于未指定添加数据的情况下给某些可为空的列自动赋值为NULL一般用不到直接传入null即可。。第三个参数是一个ContentValues对象它提供了一系列的put()方法重载用于向contentValues中添加数据。
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db1= 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.69);
db1.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);
db1.insert("Book",null,values);
}
});
4.更新数据库
update()方法用于対数据进行更新。这个方法接收4个参数第一个是表名在这里是指定去更新哪张表里的数据。第二个参数是ContentValues对象用于把更新数据在这里组装进去。第三、第四个参数用于约束更新某一行或某几行中的数据不知道的话默认是更新所有行
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db2 = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price",10.99);
db2.update("Book",values,"name = ?",new String[]{"The Da Vinci Code"});
}
});
5.删除数据库
delete()方法专门用来删除数据这个方法接收三个参数第一个参数是表名第二第三个参数用于约束删除某一行或某几行数据不指定的话就是默认删除所有行。
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db3=dbHelper.getWritableDatabase();
db3.delete("Book","pages>?",new String[]{"500"});
db3.delete("Book","author=?",new String[]{"Dan Brown"});
}
});
6.查询数据库
query()方法用于对数据库进行查询。最短的一个方法重载需要传入7个参数。第一个参数表名第二个参数指定去查询哪几列如果不指定则默认查询所有列。第三四个参数用于约束查询某一行或某几行的数据不指定则默认查询所有行第五个参数用于指定需要去group by的列不指定则表示不对查询结果进行group by操作。第六个参数用于对group by之后的数据进行进一步的过滤不指定则表示不进行过滤。第七个参数用于对查询结果的排序方式不指定则表示使用默认的排序方式。
cursor.moveToFirst()将指针指向第一个数据的第一行数据。然后进入循环去遍历
query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db=dbHelper.getWritableDatabase();
//查询表中所有的数据
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("MainActivity","book name is"+name);
Log.d("MainActivity","book author is"+author);
Log.d("MainActivity","book pages is"+pages);
Log.d("MainActivity","book price is"+price);
}while (cursor.moveToNext());
}
cursor.close();
}
});
//遍历方法1:
if (cursor.moveToFirst()) {
do{
//取出数据,调用cursor.getInt/getString等方法
}while(cusor.moveToNext());
}
//遍历方法2:
if(cusor != null){
while(cursor.moveToNext()) {
//读取数据
}
}
三、Room
使用Room进行增删改查
通过下面这三个就可以完成对数据库的基本操作:
Room的整体结构它主要由Entity、Dao和Database这3部分组成。
- Entity。用于定义封装实际数据的实体类每个实体类都会在数据库中有一张对应的表并且表中的列是根据实体类中的字段自动生成的。
- Dao。Dao是数据访问对象的意思通常会在这里对数据库的各项操作进行封装在实际编程的时候逻辑层就不需要和底层数据库打交道了直接和Dao层进行交互。
- Database。用户定义数据库中的关键信息包括数据库的版本号包括哪些实体类以及提供Dao层的访问实例。
新建一个User实体类
@Entity
data class User(var firstName:String,var lastName:String,var age:Int){
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
这里我们在User的类名上使用@Entity注解将它声明成了一个实体类然后在User类中添加了一个id字段并使用@PrimaryKey注解将它设为了主键再把autoGenerate参数指定为true使得主键的值是自动生成的。
接下来定义Dao因为所有访问数据库的操作都在这里封装的。
访问数据库的操作无非就是增删改查这4种但是业务需求千变万化。而Dao要做的事情就是覆盖所有的业务需求使得业务永远只需要与Dao层进行交互而不必和底层的数据库打交道。
新建一个UserDao接口
@Dao
interface UserDao {
@Insert
fun insertUser(user: User):Long
@Update
fun updateUser(newUser: User)
@Query("select * from User")
fun loadAllUsers():List<User>
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age:Int):List<User>
@Delete
fun deleteUser(user:User)
@Query("delete from User where lastName=:lastName")
fun deleteUserByLastName(lastName:String):Int
}
UserDao 接口的上面使用了一个@Dao注解这样Room才能将它识别成一个Dao。UserDao的内部就是根据业务需求对各种数据库操作进行的封装。数据库操作通常有增删改查4种因此Room也提供了@Insert@Delete @Update @Query这4种相应的注解。
insertUser()方法上面使用了@Insert注解表示会将参数中传入的User对象插入数据库中插入完成后还会将自动生成的主键id值返回。updateUser()方法上面使用了@Update注解表示会将参数中传入的User对象更新到数据库当中。deleteUser()方法上面使用了@Delete注解表示会将参数传入的User对象从数据库中删除。以上几种数据库操作都是直接使用注解标识不需要编写SQL语句。
如果想要从数据库查询数据或者使用非实体类参数来增删改查数据就必须编写SQL语句了。比如loadAllUsers()方法如果只使用一个@Query注解Room将无法知道我们想要查询哪些数据。我们还可以将方法中传入的参数指定到SQL语句当中比如loadUsersOlderThan()方法就可以查询所有年龄大于指定参数的用户。而如果使用非实体类的参数来增删改数据也必须编写SQL语句只能使用@Query注解参考deleteUserByLastName()方法的写法。
定义Database。只需定义好3个部分的内容数据库版本号、包含哪些实体类以及提供Dao层的访问实例。新建一个AppDatabase.kt文件代码如下
@Database(version = 1,entities = [User::class])
abstract class AppDatabase: RoomDatabase() {
abstract fun userDao():UserDao
companion object{
private var instance:AppDatabase?=null
fun getDatabase(context: Context):AppDatabase{
//instance不为空直接执行let函数返回instance为空则不执行let函数
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).build().apply { instance=this }
//apply函数中是构建AppDatabase的实例的上下文会自动返回调用对象本身
}
}
}
我们在AppDatabase类的头部使用了@Database注解并在注解中声明了数据库的版本号以及包含哪些实体类多个实体类之间用逗号隔开。
AppDatabase类必须继承自RoomDatabase类并且一定要使用abstract关键字将它声明成抽象类然后提供相应的抽象方法用于获取之前编写的Dao的实例比如这里提供的userDao()方法。这里只需声明具体的实现由Room在底层自动完成的(不用自己去实现这个抽象方法)。
接着在companion object结构体中编写了一个单例模式因为原则上全局应该只存在一份AppDatabase实例。这里使用了instance变量来缓存AppDatabase的实例然后再getDatabase()方法中判断如果instance变量不为空就直接返回否则就调用Room.databaseBuilder()方法来构建一个AppDatabase实例。databaseBuilder()方法接收3个参数第一个参数是applicationContext避免出现内存泄露。第二个参数是AppDatabase的Class类型第三个参数是数据库名。最后调用build()方法完成构建并将创建出来的实例赋值给instance变量然后返回当前实例。
activity_main.xml代码
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/addDataBtn"
android:layout_gravity="center_horizontal"
android:text="Add Data"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/updateDataBtn"
android:layout_gravity="center_horizontal"
android:text="Update Data"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/deleteDataBtn"
android:layout_gravity="center_horizontal"
android:text="Delete Data"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/queryDataBtn"
android:layout_gravity="center_horizontal"
android:text="Query Data"
/>
</LinearLayout>
MainActivity代码
class MainActivity : AppCompatActivity() {
private val TAG:String="MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var userDao=AppDatabase.getDatabase(this).userDao()
val user1=User("Tom","Brandy",40)
val user2=User("Tom","Hanks",63)
addDataBtn.setOnClickListener {
thread {
user1.id=userDao.insertUser(user1)
user2.id=userDao.insertUser(user2)
}
}
updateDataBtn.setOnClickListener {
thread {
user1.age=2
userDao.updateUser(user1)
}
}
deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("Hanks")
}
}
queryDataBtn.setOnClickListener {
thread {
for(user in userDao.loadAllUsers()){
Log.d(TAG, user.toString())
}
}
}
}
}
点击"Add Data"按钮再点击"Query Data"按钮
可以看到两条用户数据添加进来了
点击"Update Data"按钮再重新点击"Query Data"按钮
可以看到第一条数据中用户的年龄被成功修改成了2岁。
点击"Delete Data"按钮再重新点击"Query Data"按钮
只剩下一条数据了
Room数据库升级
如果你处于开发测试阶段Room提供了一个简单粗暴的方法如下所示
Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).fallbackToDestructiveMigration().build()
在构建AppDatabase实例的时候加入一个fallbackToDestructiveMigration()方法。这样只要数据库进行了升级Room就会将之前的数据库销毁然后再重新创建随之而来的副作用就是将之前数据库中的所有数据就全部丢失了。
接下来是Room数据库升级的正规写法
随着业务升级现在我们打算在数据库添加一张Book表那么首先就是创建一个Book的实体类如下所示
@Entity
data class Book (var name:String,var pages:Int){
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
然后创建一个BookDao接口
@Dao
interface BookDao {
@Insert
fun insertBook(book:Book):Long
@Query("select * from Book")
fun loadAllBooks():List<Book>
}
接下来修改AppDatabase中的代码在里面编写数据库升级的逻辑
@Database(version = 2,entities = [User::class,Book::class])
abstract class AppDatabase: RoomDatabase() {
abstract fun userDao():UserDao
abstract fun bookDao():BookDao
companion object{
val MIGRATION_1_2=object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book(id integer primary key autoincrement not null,name text not null,pages integer not null)")
}
}
private var instance:AppDatabase?=null
fun getDatabase(context: Context):AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).addMigrations(MIGRATION_1_2).build().apply { instance=this }//apply函数中是构建AppDatabase的实例的上下问
// 会自动返回调用对象本身
}
}
}
首先在@Database注解中我们将版本号升级成了2并将Book类添加到了实体类声明中然后又提供了一个bookDao()方法用于获取BookDao的实例。
在 companion object结构体中我们实现了一个Migration的匿名类并传入了1和2这两个参数表示当数据库从1升级到2的时候就执行这个匿名类的时候就执行这个匿名类的升级逻辑。由于我们新增一张Book表所以需要在migrate()方法中编写相应的建表语句。Book表的建表语句和Book实体类中声明的结构要完全一致。
最后在构建AppDatabase实例的时候加入一个addMigrations()方法并把MIGRATION_1_2传入。
现在当我们进行任何数据库操作时Room就会自动根据当前数据库的版本号执行这些升级逻辑从而让数据库始终保证是最新的版本。
每次数据库升级并不一定都要新增一张表也有可能是向现有的表中添加新的列。这个时候只需要使用alter语句修改表结构就可以了。
比如添加一个作者字段
@Entity
data class Book (var name:String,var pages:Int,var author:String){
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
修改AppDatabase中的代码
@Database(version = 3,entities = [User::class,Book::class])
abstract class AppDatabase: RoomDatabase() {
abstract fun userDao():UserDao
abstract fun bookDao():BookDao
companion object{
val MIGRATION_1_2=object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book(id integer primary key autoincrement not null,name text not null,pages integer not null)")
}
}
val MIGRATION_2_3=object :Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Book add column author text not null default 'unknown'")
}
}
private var instance:AppDatabase?=null
fun getDatabase(context: Context):AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).addMigrations(MIGRATION_1_2, MIGRATION_2_3).build().apply { instance=this }//apply函数中是构建AppDatabase的实例的上下问
// 会自动返回调用对象本身
}
}
}