End

数据库 SQLite ORM框架 LitePal

本文地址


目录

数据库 SQLite ORM框架 LitePal

An Android library that makes developers use SQLite database extremely easy.

LitePal是一个开源的Android库,允许开发人员非常容易地使用SQLite数据库。 您可以完成大部分数据库操作,而无需编写SQL语句[statement],包括创建或升级表,crud操作,聚合函数[aggregate functions]等。LitePal的设置也非常简单,您可以在不到5分钟内将其集成到项目中。

特性

  • 使用对象关系映射[object-relational mapping,ORM]模式。
  • 很“轻”,jar包只有100k不到,使用起来比较简单
  • 几乎为零配置,只有一个属性很少的配置文件。
  • 自动维护[Maintains]所有表。例如,创建、更改或删除表。
  • 支持多数据库。
  • 封装的API,用于避免编写SQL语句。
  • 极好用的流式查询API。
  • 作为替代选择,你仍可使用SQL,但使用起来比原始API更易用。

LitePal不管是创建数据库、创建表还是执行增删改查,都是根据Model的类名和属性名,每次都需要进行反射拼装,然后调用Android原生的数据库操作,实现相应的功能。

使用过程

  • 首先引入lib,可以通过gradle引入也可以将下载的litepal.jar包直接放入libs目录下。
  • 然后在assets目录下新建一个 litepal.xml 文件,文件名称不能更改。
  • 然后在 AndroidManifest 中配置一下 LitePalApplication,如果已使用自定义的Application,只需在你的Application的onCreate方法中调用 LitePal.initialize(this) 即可。
  • 接下来创建继承自DataSupport的Mode类,之后这些实体类就拥有了进行CRUD操作的能力,LitePal框架会自动创建好相应的数据表,以及表数据类型和非空约束等。

配置

添加依赖

implementation 'org.litepal.android:java:3.0.0' //2018-10月最新版本
//implementation 'org.litepal.android:kotlin:3.0.0' //kotlin

配置 litepal.xml

在项目的 assets 目录中创建一个 litepal.xml文件,并按以下方式配置。

几个标签的作用:

  • dbname:配置数据库名称
  • version:配置数据库版本每次要升级数据库时,此处的值加1
  • list:配置映射类
  • storag:配置数据库文件的存储位置,只能选择【internal】或【external】

示例:

<?xml version="1.0" encoding="utf-8"?>
<litepal>
    <!--定义应用程序的数据库名称。默认情况下,每个数据库名称应以.db结尾。如果您没有使用.db命名数据库端,LitePal会自动为您添加后缀。-->
    <dbname value="demo" />

    <!--定义数据库的版本。每次要升级数据库时,version标记都会有所帮助。修改您在映射标记中定义的模型,只需将版本值加1,数据库升级将自动处理,你无需关心。-->
    <version value="1" />

    <!--定义模型,LitePal将为每个映射类创建表。模型中定义的受支持字段将映射到列中。-->
    <list>
        <mapping class="com.test.model.Reader" />
        <mapping class="com.test.model.Magazine" />
    </list>

    <!--定义.db文件的位置。【internal】表示.db文件将存储在内部存储的数据库文件夹中,无人可以访问(默认值)。【external】表示.db文件将存储在主外部存储设备所在目录的路径中,应用程序可以放置它拥有的每个人都可以访问的持久文件。-->
    <storage value="external" />
</litepal>

配置 LitePalApplication

如果没有自定义Application:android:name="org.litepal.LitePalApplication"
如果已自定义Application:LitePal.initialize(this);

始终记得使用 application 作为参数。不要将任何 activity 或 service 等的实例用作参数,以避免内存泄漏。

API

创建表

首先定义模型,例如:

public class Album extends LitePalSupport {
    @Column(unique = true, defaultValue = "unknown")
    private String name;
    private float price;
    private byte[] cover;
    private List<Song> songs = new ArrayList<Song>();
    // generated getters and setters.
    ...
}
public class Song extends LitePalSupport {
    @Column(nullable = false)
    private String name;
    private int duration;
    @Column(ignore = true)
    private String uselessField;
    private Album album;
    // generated getters and setters.
    ...
}

注意,3.0.0之后是继承自 LitePalSupport,之前是继承自 DataSupport

然后将这些 models 添加到litepal.xml的 list 标签中:

<list>
    <mapping class="org.litepal.litepalsample.model.Album" />
    <mapping class="org.litepal.litepalsample.model.Song" />
</list>

下次操作数据库时将生成上面添加的数据库表。

LitePal不管是创建数据库、创建表还是执行增删改查,都是根据Model的类名和属性名,每次都需要进行反射拼装,然后调用Android原生的数据库操作,实现相应的功能。

更新表

LitePal中的升级表非常简单,只需修改你想要的模型:

public class Album extends DataSupport {
    ...
    @Column(ignore = true)
    private float price;//忽略掉此字段
    private Date releaseDate;//添加此字段
}

添加了 releaseDate 字段,并给 price 字段添加了 ignore 注解用以忽略。
然后增加 litepal.xml 中的版本号:<version value="2" />

这些表将在您下次运行数据库时升级,升级后 releaseDate 列将添加到 album 表中,原始的 price 列将被删除。
升级后,除了那些删除的列之外,album 表中的所有数据都将被保留。

但是有些升级条件下LitePal无法兼容的处理数据,这些情况下,升级后表中的所有数据都将被清除:

  • 添加了一个带有 unique = true 注解的字段(因为旧的数据的这个字段的值不满足 unique 条件)
  • 为一个已存在的字段添加了 unique = true 注解(同样是因为旧的数据的这个字段的值不满足 unique 条件)
  • 为一个已存在的字段添加了 nullable = false 注解(因为旧的数据的这个字段的值不满足非 nullable 条件)

一定要注意,进行上述操作会导致数据丢失!

保存数据

所有继承自LitePalSupport的model都有save()方法用于保存数据到数据库:

Album album = new Album();
album.setName("album");
album.setPrice(10.99f);
album.setCover(getCoverImageBytes());
album.save();

更新数据

最简单的方法是使用 save 方法更新 find 找到的记录:

Album albumToUpdate = LitePal.find(Album.class, 1); //3.0.0之前是通过 DataSupport 查找的
albumToUpdate.setPrice(20.99f); //更改 price
albumToUpdate.save();

从LitePalSupport 继承的每个model也都具有 updateupdateAll 方法。 您可以通过指定 ID 更新单个记录:

Album albumToUpdate = new Album();
albumToUpdate.setPrice(20.99f);
albumToUpdate.update(id);

你也可以通过使用 where 语句一次更新多条数据:

Album albumToUpdate = new Album();
albumToUpdate.setPrice(20.99f);
albumToUpdate.updateAll("name = ?", "album");

删除数据

LitePal.delete(Song.class, id); //删除一条数据
DataSupport.deleteAll(Song.class, "duration > ?" , "350"); //删除多条数据

查找数据

Song song = LitePal.find(Song.class, id); //通过id查找单条数据
List<Song> allSongs = LitePal.findAll(Song.class); //查找全部数据
List<Song> songs = LitePal.where("name like ? and duration < ?", "song%", "200") //条件查询
    .order("duration")
    .find(Song.class);

异步操作

默认情况下,每个数据库操作都在主线程上,如果您的操作可能花费很长时间,例如保存或查询大量记录,您可能希望使用异步操作。LitePal支持所有crud方法的异步操作。

例如在后台线程中异步查找song表中的所有记录:

LitePal.findAllAsync(Song.class).listen(new FindMultiCallback<Song>() {
    @Override
    public void onFinish(List<Song> t) {
        //异步返回查询结果
    }
});

在后台线程中异步保存数据:

album.saveAsync().listen(new SaveCallback() {
    @Override
    public void onFinish(boolean success) {
        //异步返回保存结果
    }
});

多库操作(动态创建数据库)

如果您的应用需要多个数据库,LitePal会完全支持它。您可以在运行时创建任意数量的数据库。例如:

LitePalDB litePalDB = new LitePalDB("demo2", 1);
litePalDB.addClassName(Singer.class.getName());
litePalDB.addClassName(Album.class.getName());
litePalDB.addClassName(Song.class.getName());
LitePal.use(litePalDB);

这将创建一个包含 singer, album 和 song 表的名为 demo2 的数据库。

创建一个配置与 litepal.xml 相同的新的数据库:LitePal.use(LitePalDB.fromDefault("newdb"));
切换回默认数据库:LitePal.useDefault();
通过数据库名称删除数据库LitePal.deleteDatabase("newdb");

监听数据库的创建和更新

LitePal.registerDatabaseListener(new DatabaseListener() {
    @Override
    public void onCreate() {
        // fill some initial data
    }

    @Override
    public void onUpgrade(int oldVersion, int newVersion) {
        // upgrade data in db
    }
});

混淆

如果您要在项目中启用ProGuard,您需要添加以下内容:

-keep class org.litepal.** {
    *;
}

-keep class * extends org.litepal.crud.DataSupport {
    *;
}

-keep class * extends org.litepal.crud.LitePalSupport {
    *;
}

测试demo

实体类

public class Album extends LitePalSupport {
    @Column(unique = true, defaultValue = "包青天")
    private String name;
    
    private int age;//新增一个字段时需要更改数据库版本,否则操作数据时会失败
    private float price;
    
    @Column(ignore = true)//忽略掉的字段,此字段的值不会保存到数据库中
    private int size;
    
    //必须有一个默认的、public的无参构造器,否则执行 update 等语句时会崩溃
    public Album() {
    }
    //省略了 set/get 方法,以及我添加的 Build 相关的代码
}

测试类

public class LitePalActivity extends ListActivity {
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"findAll()",
            "save() ",
            "find() 和 save()",
            "update()",
            "updateAll()",
            "delete() 和 deleteAll()",
            "fluent query",
            "Async operations: saveAsync() + listen()",
            "Async operations: findAllAsync() + listen()",
        };
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
    }
    
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        int random = new Random().nextInt(5);
        switch (position) {
            case 0:  //findAll()
                showTips(new Gson().toJson(LitePal.findAll(Album.class)));    //[{"age":1,"name":"包青天1","price":1.0,"size":0,"baseObjId":1}]
                break;
            case 1:  //save()
                Album saveAlbum = Album.newBuilder().name("包青天" + random).age(random).price(random).size(random).build();
                showTips(saveAlbum.save() + "  " + new Gson().toJson(saveAlbum));
                break;
            case 2:  //find() 和 save() 和 findAll
                Album findAlbum = LitePal.find(Album.class, random);
                if (findAlbum != null) {
                    findAlbum.setPrice(9.99f); //仅更改指定的值
                    showTips(random + "  " + findAlbum.save());
                } else {
                    showTips("没有找到id=" + random + "的数据");
                }
                break;
            case 3: //update()
                Album updateAlbum = Album.newBuilder().name("包青天0").age(random + 100).build();
                //注意:如果指定为 unique 字段的值已存在,则会报 DataSupportException: UNIQUE constraint failed: album.name
                try {
                    int rowsAffected = updateAlbum.update(random); //未指定的字段的值不会更改,比如这里不会更改 price的值
                    showTips(random + "  更新" + (rowsAffected == 0 ? "失败(未找到指定 id 的数据)" : rowsAffected + "条"));
                } catch (Exception e) {
                    e.printStackTrace();
                    showTips(random + "  更新失败(主键冲突 UNIQUE constraint failed )");
                }
                break;
            case 4: //updateAll()
                Album whereAlbum = Album.newBuilder().age(random + 200).price(random + 200).size(random + 200).build();
                int rowsAffected = whereAlbum.updateAll("name like ? and age > ?", "包青天%", Integer.toString(3));
                showTips(random + "  更新" + (rowsAffected == 0 ? "失败(未找到指定 id 的数据)" : rowsAffected + "条"));
                break;
            case 5://delete() 和 deleteAll()
                int rowsAffected1 = LitePal.delete(Album.class, random);
                int rowsAffected2 = LitePal.deleteAll(Album.class, "age < ?", Integer.toString(200));
                showTips(random + "  " + rowsAffected1 + "  " + rowsAffected2);
                break;
            case 6: //fluent query
                List<Album> albumList = LitePal.where("name like ? and price <= ?", "包青天%", Float.toString(3.0f))
                    .order("name")
                    .find(Album.class);
                showTips(new Gson().toJson(albumList));
                break;
            case 7: //Async operations: saveAsync() + listen()
                Album saveAsyncAlbum = Album.newBuilder().name("包青天" + random).age(random).price(random).size(random).build();
                saveAsyncAlbum.saveAsync()
                    .listen(success -> showTips(random + "," + success + "," + (Looper.myLooper() == Looper.getMainLooper())));//主线程
                break;
            case 8: //Async operations: findAllAsync() + listen()
                LitePal.findAllAsync(Album.class)
                    .listen(list -> showTips(new Gson().toJson(list)));
                break;
        }
    }
    
    private void showTips(String s) {
        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
        Log.i("bqt", s);
    }
}

2018-5-28

posted @ 2018-05-28 19:08  白乾涛  阅读(639)  评论(0编辑  收藏  举报