Android数据库:Litepal性能分析及使用中遇到的坑

教程的话当然是去郭神的官方csdn了

总结一下使用经验

20200608更新
2. 配置 litepal.xml
在您项目中创建“assets”目录,并在其中创建“litepal.xml”文件,将下方代码拷贝其中。

<?xml version="1.0" encoding="utf-8"?>
<litepal>
    <!--
        Define the database name of your application. 
        By default each database name should be end with .db. 
        If you didn't name your database end with .db, 
        LitePal would plus the suffix automatically for you.
        For example:    
        <dbname value="demo" />
    -->
    <dbname value="demo" />

    <!--
        Define the version of your database. Each time you want 
        to upgrade your database, the version tag would helps.
        Modify the models you defined in the mapping tag, and just 
        make the version value plus one, the upgrade of database
        will be processed automatically without concern.
            For example:    
        <version value="1" />
    -->
    <version value="1" />

    <!--
        Define your models in the list with mapping tag, LitePal will
        create tables for each mapping class. The supported fields
        defined in models will be mapped into columns.
        For example:    
        <list>
            <mapping class="com.test.model.Reader" />
            <mapping class="com.test.model.Magazine" />
        </list>
    -->
    <list>
    </list>
    
    <!--
        Define where the .db file should be. "internal" means the .db file
        will be stored in the database folder of internal storage which no
        one can access. "external" means the .db file will be stored in the
        path to the directory on the primary external storage device where
        the application can place persistent files it owns which everyone
        can access. "internal" will act as default.
        For example:
        <storage value="external" />
    -->
    
</litepal>

这是唯一的配置文件,并且要配置的属性也非常简单。
dbname 配置该项目数据库名称
version 配置数据库版本号。每次您要更新库时,使其值加一。
list 配置映射类。
storage 配置数据库文件的存储位置。 值为“internal” 或 “external”。
那么Litepal默认存储位置呢,如果你不配置storage,默认就在internal(内部)
也就是/data/data/包名/databases/

D:\Rachel\WorkSpace\MoleV10>adb shell
HWBKL:/ $ cd data/data
HWBKL:/data/data $ ls
ls: .: Permission denied
1|HWBKL:/data/data $ run-as com.snap.vseries
HWBKL:/data/data/com.snap.vseries $ ls
cache code_cache databases files lib shared_prefs
HWBKL:/data/data/com.snap.vseries $ cd databases
HWBKL:/data/data/com.snap.vseries/databases $ ls
pcr.db pcr.db-journal

如果配置了storage为external,那么它的位置
/sdcard/Android/data/包名/files

D:\Rachel\WorkSpace\MoleV10>adb shell
HWBKL:/ $ cd sdcard
HWBKL:/sdcard $ cd a
alipay/  amap/
HWBKL:/sdcard $ cd A
Android/    ArtPlanet/
HWBKL:/sdcard $ cd Android/
HWBKL:/sdcard/Android $ cd data
HWBKL:/sdcard/Android/data $ cd com.s
com.sankuai.meituan/  com.snap.vseries/     com.snssdk.api/
HWBKL:/sdcard/Android/data $ cd com.snap.vseries/
HWBKL:/sdcard/Android/data/com.snap.vseries $ ls
cache files
HWBKL:/sdcard/Android/data/com.snap.vseries $ cd files
HWBKL:/sdcard/Android/data/com.snap.vseries/files $ ls
databases
HWBKL:/sdcard/Android/data/com.snap.vseries/files $ cd databases/
HWBKL:/sdcard/Android/data/com.snap.vseries/files/databases $ ls
pcr.db pcr.db-journal
HWBKL:/sdcard/Android/data/com.snap.vseries/files/databases $

如果你想配置到其它位置,也可以直接

LitePalAttr.getInstance().setStorage(AppConfig.DATABASE_DIRECTORY);

20190705 更新
1、Litepal布尔类型存储
在litepal中,boolean的默认值是false,当你设置为true时,可以使用,但是当赋值为false时,需要使用setToDefault()方法。如果不使用这种方式,更新不会成功。
当然int类型也一样如此。郭婶原话是:
Android数据库:Litepal性能分析及使用中遇到的坑_第1张图片

1、首先是郭神的这个教程,只到2.0。但是现在已经litepal已经3.0了
3.0的文档
3.0主要是有了一个数据库更新时的一个监听
我在使用这个功能的时候遇到了一个这个问题getDatabase called recursively

2、litepal目前不支持建立索引
我观察了一下数据库的字段,发现每张表被建立的时候会建一个唯一索引(id)

3、关联表的数据插入
划重点,建立一对一关联后,首先你要记得,把关联表的数据save!!并不是你直接把数据set赋值之后,关联表就有数据了。set只是给两个表建立关联的啊懂。我搞了好久才发现这个惊天大秘密~~。下面提供模板


        News news=new News();
        news.setTitle("这是一条新闻标题");
        news.setContent("这是一条新闻内容");
        news.setPublishDate(new Date());
        Introduction introduction=new Introduction();
        introduction.setGuide("这是新闻导语");
        introduction.setDigest("这是新闻摘要");
        news.setIntroduction(introduction);//只是建立关联
        introduction.save();//重点
        news.save();

4、关联表的查询
郭神提供了激进查询,去查询关联表,传送门
但是这种激进查询的方法不支持–>迭代查询关联表的关联表数据。
郭神建议大家还是使用默认的懒加载,使用方式同样点击刚刚那个那个传送门
这种方式呢,如果想–>迭代查询关联表的关联表数据–>就很傻了
之所以说它懒呢,是因为比如你有三张表:新闻,新闻简介,新闻简介的作者。从名字可以看出这三张表是一层套一层的。当你一开始去查第一张表(新闻)的数据时,你会发现取到的数据里,新闻简介对象是空的。你想要获取新闻简介的数据,你就必须, news.getIntroduction()。在getIntroduction()方法里面呢是根据关联id去查询对应新闻简介。
这种情况你直接用激进查询的话其实更容易实现。
但是呢,激进查询不支持二级以上不是。于是懒查询的方式你想获得一个完整的对象,你需要一步步把数据查出来,再set回去。
三表联查:

        News news=DBManager.getInstance().queryNews();
        Introduction introduction = news.getIntroduction();
        IntroductionAuthor author=introduction.getIntroductionAuthor();
        List commentList=news.getCommentList();
        introduction.setIntroductionAuthor(author);
        news.setIntroduction(introduction);
        news.setCommentList(commentList);

很蠢的操作

5、我不想用那么蠢的方式,因为我现在要用的对象,是一个十级左右的嵌套。。。
所以我只能把这个对象转成jsonString然后作为一个字段传进去,而不是关联表的方式
但是这也很蠢,因为一个对象被序列化成字符串再从字符创序列化成对象,很耗费性能
所以呢,现在,我要去测试一下,这种办法性能如何。
好了,性能测试出来了。当然啦,蠢办法测试

性能分析
这份测试结果,包含了我的业务逻辑,其中存储包括序列化对象,查询包括取出之后反序列化成对象。等等的基础上的测试结果,并不代表litepal的性能。总结就是查询出一个size为1001的对象列表需要3秒左右。此方案可行。

Android数据库:Litepal性能分析及使用中遇到的坑_第2张图片
测试代码

首先一个对象jsonstring的大小为
{
	"closed": false,
	"createTime": 1551856765000,
	"customerCount": 1,
	"deviceUuid": "POS",
	"discountTotal": 0.0,
	"isBlock": false,
	"isCancelled": false,
	"isPaid": true,
	"isPrinted": false,
	"menuTotal": 1.0,
	"openTime": 1551856765000,
	"orderNumber": "1903061024011001630",
	"orderPromotionBlocks": [],
	"orderPromotions": [],
	"orderSource": 2,
	"orderTotal": 1.0,
	"orderTransactions": [{
		"beginTime": 1551856767000,
		"deviceUuid": "POS",
		"discountTotal": 0.0,
		"isSynced": false,
		"menuTotal": 1.0,
		"orderNumber": "1903061024011001630",
		"orderTransactionDetails": [{
			"addTime": 1551856762000,
			"detailNumber": "19030615192510924011995377",
			"detailTargetName": "打包盒",
			"detailTargetNumber": "1604",
			"detailType": "M",
			"isPrinted": false,
			"isVoid": false,
			"isWeight": false,
			"orderNumber": "1903061024011001630",
			"parentDetailNumber": "",
			"prepared": false,
			"quantity": 1,
			"seq": 1,
			"status": 1,
			"storeNumber": "1040104024",
			"totalPrice": 1.0,
			"transactionNumber": "190306102401100163",
			"unitPrice": 1.0,
			"weightQuantity": 0.0
		}],
		"seq": 1,
		"status": 3,
		"storeNumber": "1040104024",
		"submitTime": 1551856767000,
		"transactionNumber": "190306102401100163",
		"transactionTotal": 1.0,
		"transactionType": 0
	}],
	"outChannel": "",
	"outSeq": 0,
	"paymentInfos": [{
		"amount": 1.0,
		"orderNumber": "1903061024011001630",
		"paymentTime": 1552547967000,
		"paymentmethodAction": "cash"
	}],
	"paymentTime": 1552547967000,
	"printType": 0,
	"seq": 100163,
	"serviceChargeTotal": 0.0,
	"status": 1,
	"storeNumber": "1040104024",
	"tableNumber": "1",
	"timePeriodNumber": "TP004",
	"transType": 1
}
MainActivity.class
ThreadManger.get().add(new ThreadListener() {
                    @Override
                    public void doAction() throws Exception {
                        Long beginTime=new Date().getTime();
                        for (int i=0;i<1000;i++){
                            OrderManager.getInstance().getPos(i);
                        }
                        Long endTime=new Date().getTime();
                        long time=(beginTime-endTime);
                        LogManager.get().getLogger(HomeActivity.class).info("1001单插入耗费时间"+time);
                        Long beginTime1=new Date().getTime();
                        ArrayList orders=OrderManager.getInstance().loadOrder();
                        Long endTime1=new Date().getTime();
                        long time1=(beginTime1-endTime1);
                        LogManager.get().getLogger(HomeActivity.class).info(orders.size()+"单查询耗费时间"+time1);
                        Long beginTime2=new Date().getTime();
                        IOrder order=OrderManager.getInstance().getOrderByNumbers("19030610240110016325");
                        Long endTime2=new Date().getTime();
                        long time2=(beginTime2-endTime2);
                        LogManager.get().getLogger(HomeActivity.class).info(order.getOrderNumber()+"单查询指定订单耗费时间"+time2);
                        Long beginTime3=new Date().getTime();
                        IOrder order3=OrderManager.getInstance().getOrderByNumbers("190306102401100163888");
                        Long endTime3=new Date().getTime();
                        long time3=(beginTime3-endTime3);
                        LogManager.get().getLogger(HomeActivity.class).info(order3.getOrderNumber()+"单查询指定订单耗费时间"+time3);
                    }
                });
    
     public void getPos(int i){
        String msg="{\"orderNumber\":\"190306102401100163"+i+"\",\"timePeriodNumber\":\"TP004\",\"seq\":\"100163\",\"storeNumber\":\"1040104024\",\"people\":1,\"openTime\":\"2019-03-06 15:19:25\",\"menuTotal\":1.00,\"orderTotal\":1.00,\"discountTotal\":0.0,\"transType\":1,\"reservation\":false,\"reservationTime\":\"\",\"tableNumber\":\"1\",\"outChannel\":\"\",\"outSeq\":0,\"transactions\":[{\"transactionNumber\":\"190306102401100163\",\"beginTime\":\"2019-03-06 15:19:27\",\"submitTime\":\"2019-03-06 15:19:27\",\"details\":[{\"detailNumber\":\"19030615192510924011995377\",\"seq\":1,\"addTime\":\"2019-03-06 15:19:22\",\"detailType\":\"M\",\"parentDetailNumber\":\"\",\"quantity\":1.00,\"unitPrice\":1.00,\"totalPrice\":1.0000,\"detailTargetNumber\":\"1604\",\"detailTargetName\":\"打包盒\"}]}],\"deliveryInfo\":[],\"payments\":[{\"amount\":1.00,\"paymentMethodAction\":\"cash\",\"paymentTime\":\"2019-03-14 15:19:27\",\"outterTraxNo\":\"\"}]}";
        // 组装返回数据
        OrderResponseEntity orderResponseEntity;
        try {
            orderResponseEntity = JSON.parseObject(msg, OrderResponseEntity.class);
        } catch (Exception e) {
            cn.tedia.core.util.Log.w("RPCService", "收到订单接口数据,订单数据反序列化失败");
            LogManager.get().getLogger(getClass()).info("收到order接口数据,order数据反序列化失败", e);
            return ;
        }
        // 转换
        IOrder order = OrderManager.getInstance().getOrderByPOSEntity(orderResponseEntity);
        if (order == null) {
            cn.tedia.core.util.Log.w("RPCService", "收到订单接口数据,转换订单数据失败");
            LogManager.get().getLogger(getClass()).info("收到order接口数据,转换成order数据失败");
            return;
        }
        // 保存订单
        if (!OrderManager.getInstance().addPOSOrderToDB(order)) {
            cn.tedia.core.util.Log.w("RPCService", "收到订单接口数据,订单数据保存失败");
            LogManager.get().getLogger(getClass()).info("收到order接口数据,order数据保存失败");
            return ;
        }

        // 组装返回数据
        return;
    }            
查询全部订单
OrderManager.class
    /**
     * 加载本地所有没有关单的订单
     */
    public ArrayList loadOrder() {
            ArrayList orderList = orderController.getOrderByDateAndNotClosed(new Date());
       return orderList;
    }
        public ArrayList getOrderByDateAndNotClosed(Date date) {
        OrderDBModel orderDBModel = OrderManager.getInstance().getOrderDBModel();
        if (orderDBModel == null) {
            return null;
        }

        ArrayList orderStringList = orderDBModel.getOrderByDateAndNotClosed(date);
        if (orderStringList == null) {
            return null;
        }
        ArrayList data = new ArrayList<>();
        for (int i = 0; i < orderStringList.size(); i++) {
            data.add(JSON.parseObject(orderStringList.get(i), IOrder.class));
        }

        return data;
    }
查询指定订单
OrderManager.class
    /**
     * 加载本地所有没有关单的订单
     */
    public IOrder getOrderByNumbers(String s) {
            IOrder orderList = orderController.getOrderByNumber(s);
       return orderList;
    }
    public IOrder getOrderByNumber(String orderNumber) {
        OrderDBModel orderDBModel = OrderManager.getInstance().getOrderDBModel();
        if (orderDBModel == null) {
            return null;
        }

        String data = orderDBModel.getOrder(orderNumber);

        try {
            return JSON.parseObject(data, IOrder.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

以上

最后
放一下我的demo,详细源码可以去我的GitHub上找,但是吧,我一般做测试都在这个项目,所以非常乱,不建议去

OrderDBModel.class
package com.example.pc.testeverything.SqliteManager.litepalmanager;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;

import com.tiidian.log.LogManager;
import com.tiidian.threadmanager.ThreadListener;
import com.tiidian.threadmanager.ThreadManger;

import org.litepal.LitePal;
import org.litepal.tablemanager.Connector;
import org.litepal.tablemanager.callback.DatabaseListener;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;


/**
 * Created by skyshi on 2019/2/18.
 */
public class DBModel {

    public DBModel() {
        LitePal.registerDatabaseListener(new DatabaseListener() {
            @Override
            public void onCreate() {
                LogManager.get().getLogger(LogManager.class).info("数据库操作:创建数据库");
            }

            @Override
            public void onUpgrade(int oldVersion, int newVersion) {

                ThreadManger.get().add(new ThreadListener() {
                    @Override
                    public void doAction() throws Exception {
                        try {
                            //更新数据库时的一些其他操作
                        } catch (Exception e) {
                            e.printStackTrace();
                            LogManager.get().getLogger(LogManager.class).error("数据库操作:版本更新出现异常", e);
                        }
                    }
                });
                LogManager.get().getLogger(LogManager.class).info("数据库操作:版本更新:" + oldVersion + "版本更新到" + newVersion + "版本");
            }
        });
        SQLiteDatabase db = Connector.getDatabase();
    }

    /**
     * 保存新闻数据
     *
     * @param news
     * @return
     */
    public boolean save(News news) {
        if (news == null) {
            return false;
        }
        //根据news的唯一标识(新闻号)查询
        List newsList = LitePal.where("newsNumber= ?", news.getNewsNumber()).find(News.class);
        boolean result = false;
        //已经存在做更新操作
        if (newsList != null && newsList.size() > 0) {
            int affectedRows = news.updateAll("newsNumber = ?", news.getNewsNumber());
            if (affectedRows > 0) {
                result = true;
            }
        } else {
            //不存在做插入操作
            result = news.save();
        }
        return result;
    }


    /**
     * 根据新闻号获取指定新闻
     *
     * @param newsNumber
     * @return
     */
    public List getNews(String newsNumber) {
        if (TextUtils.isEmpty(newsNumber)) {
            return null;
        }
        List newsList = LitePal.where("newsNumber= ?", newsNumber).find(News.class);
        return newsList;
    }

    /**
     * 获取不是今天并且标题为“郭神”的新闻列表
     *
     * @return
     */
    public List getNewsNotTodayAndAboutGuoShen() {
        List newsList = LitePal.where("title= ? and publishDay <> ?", "郭神威武", getDayDate(new Date())).find(News.class);
        if (newsList == null) {
            return null;
        }
        return newsList;
    }

    /**
     * 根据新闻发布日期查找新闻
     *
     * @param publishDay 发布日期
     * @return
     */
    public List getNewsByDate(Date publishDay) {
        List newsList = LitePal.where("publishDay= ?", getDayDate(publishDay)).find(News.class);
        if (newsList == null) {
            return null;
        }
        return newsList;
    }

    /**
     * 获取开单日期
     *
     * @param date
     * @return
     */
    private String getDayDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        return sdf.format(date);
    }


    /**
     * 根据id更新新闻标题
     *
     * @param id
     * @return
     */
    public boolean syncNewsTitle(int id) {
        News news = new News();
        news.setTitle("郭神威武");
        int affectedRows = news.updateAll("id = ?", String.valueOf(id));
        if (affectedRows > 0) {
            return true;
        }
        return false;
    }

    /**
     * 获取News表的数据量
     *
     * @return
     */
    public int getNewsCount() {
        List News = LitePal.select("id").find(News.class);
        return News.size();
    }

    /**
     * 获取News表的最大ID
     *
     * @return
     */
    public int getNewsMaxId() {
        Cursor cursor = LitePal.findBySQL("select max(id) as id from News");
        int id = 0;
        if (cursor.moveToFirst()) {
            do {
                id = cursor.getInt(cursor.getColumnIndex("id"));

            } while (cursor.moveToNext());
        }
        return id;
    }

    /**
     * 删除某个日期之前的新闻
     *
     * @return
     */
    public boolean deleteNewsByDay(Date publishDay) {
        int affectedRows = LitePal.deleteAll(News.class, "publishDay < ?", getDayDate(publishDay));
        boolean result = false;
        if (affectedRows > 0) {
            result = true;
        }
        return result;
    }

    /**
     * 删除某个时间之前的新闻
     *
     * @return
     */
    public boolean deleteNewsByDate(Long updateTime) {
        int affectedRows = LitePal.deleteAll(News.class, "publishDate < ?", String.valueOf(updateTime));
        boolean result = false;
        if (affectedRows > 0) {
            result = true;
        }
        return result;
    }


    /**
     * 根据新闻号删除指定新闻
     *
     * @param newsNumber
     */
    public boolean deleteNews(String newsNumber) {
        int affectedRows = LitePal.deleteAll(News.class, "newsNumber = ?", newsNumber);
        boolean result = false;
        if (affectedRows > 0) {
            result = true;
        }
        return result;
    }
}

你可能感兴趣的:(Android数据库)