本文大部分参考自:秦元培http://blog.csdn.net/qinyuanpei/article/details/46812655#t1,本文有增删修改,请点击链接查看楼主原文,尊重楼主版权。
参考链接:http://blog.csdn.net/glunoy/article/details/52037598
Unity中Sqlite数据库的使用
环境:Unity5.5
需要文件:
System.Data.dll (Unity安装目录下/Editor/Data/MonoBleedingEdge/lib/mono/2.0-api/System.Data.dll)
Mono.Data.Sqlite.dll (Unity安装目录下/Editor/Data/MonoBleedingEdge/lib/mono/2.0-api/Mono.Data.Sqlite.dll)
上述两个文件找到了放到Unity里新建的Plugins文件夹。
Sqlite3.dll (下载链接:https://www.sqlite.org/download.html,下载最新版的sqlite Precompiled Binaries解压,注意windows64 32位的区别)
在Android下使用sqlite3 必须libsqlite3.so这个文件,然后将它放到Assets/Plugins/Android下。(这个文件来源不清楚,之前老师给的,后来换版本不能用了,知道哪里有最新的请告知)
一、什么是SQLite?
SQLite是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中,以嵌入式作为它的设计目标,它占用资源非常的低,因此适合在嵌入式设备如Android、Ruby on Rails等中使用。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟和C、C++、Ruby、Python、C#、PHP、Java等编程语言相结合。SQLite是一个以文件形式存在的关系型数据库,尽管无法实现分布式和横向扩展,可是作为一个轻量级的嵌入式数据库,它不需要系统提供服务支持,通过SDK直接操作文件避免了对数据库维护的相关事务,从这个角度来讲它是一个出色的数据库。
二、为什么要选择SQLite
好了,在了解了SQLite后,我们来了解下SQLite有哪些让我们心动的特性,或者说我们为什么要选择SQLite,因为在这个世界上我们有太多的数据库可以选择,诸如Oracle、MySQL、SQLServer、DB2、NoSQL、MongoDB等等:
- ACID事务
- 零配置 – 无需安装和管理配置
- 储存在单一磁盘文件中的一个完整的数据库
- 数据库文件可以在不同字节顺序的机器间自由的共享
- 支持数据库大小至2TB
- 足够小, 大致13万行C代码, 4.43M
- 比一些流行的数据库在大部分普通数据库操作要快—SQLite读写效率如此之高,会使用其他数据库的理由是?
- 简单, 轻松的API
- 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定
- 良好注释的源代码, 并且有着90%以上的测试覆盖率
- 独立: 没有额外依赖
- 源码完全的开源, 你可以用于任何用途, 包括出售它
- 支持多种开发语言,C, C++, PHP, Perl, Java, C#,Python, Ruby等
三、Unity中的SQLite
在Unity3D中使用SQLite,我们首先要明白这样一件事情,即我们这里的使用的SQLite并非是通常意义上的SQLite.NET,而是经过移植后的Mono.Data.Sqlite。因为Unity3D基于Mono,因此使用移植后的Mono.Data.Sqlite能够减少我们的项目在不同平台上出现各种各样的问题。在Unity3D中使用的SQLite以Mono.Data.Sqlite.dll即动态链接库的形式给出,因此我们需要将这个文件放置在项目目录下的Plugins文件夹中,此外我们需要System.Data.dll或者Mono.Data.dll这两个文件添加到Plugins目录中,因为我们需要的部分数据相关的API或者类都定义在这两个文件当中,这些文件可以从文首位置找到。
PS:博主注意到在网上有使用Mono.Data.SQLiteClient.dll这个库实现在Unity3D操作SQLite数据库的相关文章,博主大概看了下,感觉和使用Mono.Data.Sqlite.dll这个库大同小异,大家喜欢哪个就用哪个吧!哈哈!博主在开源社区找到一个版本库,据说可以同时支持.NET和Mono,如果大家感兴趣欢迎大家去测试啊,哈哈!
在正式开始写代码前,我们首先来回顾下通常情况下数据库读写的基本流程吧!
- 定义数据库连接字符串(ConnectionString)完成数据库连接的构造,建立或者打开一个数据库。(连接字符串conn = "URI=file:" + Application.persistentDataPath + "/test.db";
- 定义相关的SQL命令(Command)通过这些命令实现对数据库的增加、删除、更新、读取四种基本功能。
- 在完成各种数据库操作后及时关闭数据库连接,解除对数据库的连接和引用。
注:(一般移动端数据库文件可以放在StreamingAssets文件夹下打包,然后第一次运行时拷贝到持久化目录persistentDataPath;编辑器和PC可以直接放到Assets文件夹或跟前面一样)
SQLite作为一款优秀的数据库,在为其编写数据库相关代码时同样遵循这样的流程,考虑到对数据库的增加、删除、更新、读取四种操作具有类似性和统一性,因此在动手写Unity3D脚本前,首先让我们来编写一个SQLite的辅助类SQLiteHelper.cs。该类代码定义如下:
using UnityEngine;
using System.Collections;
using Mono.Data.Sqlite;
using System;
public class SQLiteHelper
{
/// <summary>
/// 数据库连接定义
/// </summary>
private SqliteConnection dbConnection;
/// <summary>
/// SQL命令定义
/// </summary>
private SqliteCommand dbCommand;
/// <summary>
/// 数据读取定义
/// </summary>
private SqliteDataReader dataReader;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionString">数据库连接字符串</param>
public SQLiteHelper(string connectionString)
{
try{
//构造数据库连接
dbConnection=new SqliteConnection(connectionString);
//打开数据库
dbConnection.Open();
}catch(Exception e)
{
Debug.Log(e.Message);
}
}
/// <summary>
/// 执行SQL命令
/// </summary>
/// <returns>The query.</returns>
/// <param name="queryString">SQL命令字符串</param>
public SqliteDataReader ExecuteQuery(string queryString)
{
dbCommand = dbConnection.CreateCommand();
dbCommand.CommandText = queryString;
dataReader = dbCommand.ExecuteReader();
return dataReader;
}
/// <summary>
/// 关闭数据库连接
/// </summary>
public void CloseConnection()
{
//销毁Command
if(dbCommand != null){
dbCommand.Cancel();
}
dbCommand = null;
//销毁Reader
if(dataReader != null){
dataReader.Close();
}
dataReader = null;
//销毁Connection
if(dbConnection != null){
dbConnection.Close();
}
dbConnection = null;
}
/// <summary>
/// 读取整张数据表
/// </summary>
/// <returns>The full table.</returns>
/// <param name="tableName">数据表名称</param>
public SqliteDataReader ReadFullTable(string tableName)
{
string queryString = "SELECT * FROM " + tableName;
return ExecuteQuery (queryString);
}
/// <summary>
/// 向指定数据表中插入数据
/// </summary>
/// <returns>The values.</returns>
/// <param name="tableName">数据表名称</param>
/// <param name="values">插入的数值</param>
public SqliteDataReader InsertValues(string tableName,string[] values)
{
//获取数据表中字段数目
int fieldCount=ReadFullTable(tableName).FieldCount;
//当插入的数据长度不等于字段数目时引发异常
if(values.Length!=fieldCount){
throw new SqliteException("values.Length!=fieldCount");
}
string queryString = "INSERT INTO " + tableName + " VALUES (" + values[0];
for(int i=1; i<values.Length; i++)
{
queryString+=", " + values[i];
}
queryString += " )";
return ExecuteQuery(queryString);
}
/// <summary>
/// 更新指定数据表内的数据
/// </summary>
/// <returns>The values.</returns>
/// <param name="tableName">数据表名称</param>
/// <param name="colNames">字段名</param>
/// <param name="colValues">字段名对应的数据</param>
/// <param name="key">关键字</param>
/// <param name="value">关键字对应的值</param>
public SqliteDataReader UpdateValues(string tableName,string[] colNames,string[] colValues,string key,string operation,string value)
{
//当字段名称和字段数值不对应时引发异常
if(colNames.Length!=colValues.Length) {
throw new SqliteException("colNames.Length!=colValues.Length");
}
string queryString = "UPDATE " + tableName + " SET " + colNames[0] + "=" + colValues[0];
for(int i=1; i<colValues.Length; i++)
{
queryString+=", " + colNames[i] + "=" + colValues[i];
}
queryString += " WHERE " + key + operation + value;
return ExecuteQuery(queryString);
}
/// <summary>
/// 删除指定数据表内的数据
/// </summary>
/// <returns>The values.</returns>
/// <param name="tableName">数据表名称</param>
/// <param name="colNames">字段名</param>
/// <param name="colValues">字段名对应的数据</param>
public SqliteDataReader DeleteValuesOR(string tableName,string[] colNames,string[] operations,string[] colValues)
{
//当字段名称和字段数值不对应时引发异常
if(colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length) {
throw new SqliteException("colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length");
}
string queryString = "DELETE FROM " + tableName + " WHERE " + colNames[0] + operations[0] + colValues[0];
for(int i=1; i<colValues.Length; i++)
{
queryString+="OR " + colNames[i] + operations[0] + colValues[i];
}
return ExecuteQuery(queryString);
}
/// <summary>
/// 删除指定数据表内的数据
/// </summary>
/// <returns>The values.</returns>
/// <param name="tableName">数据表名称</param>
/// <param name="colNames">字段名</param>
/// <param name="colValues">字段名对应的数据</param>
public SqliteDataReader DeleteValuesAND(string tableName,string[] colNames,string[] operations,string[] colValues)
{
//当字段名称和字段数值不对应时引发异常
if(colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length) {
throw new SqliteException("colNames.Length!=colValues.Length || operations.Length!=colNames.Length || operations.Length!=colValues.Length");
}
string queryString = "DELETE FROM " + tableName + " WHERE " + colNames[0] + operations[0] + colValues[0];
for(int i=1; i<colValues.Length; i++)
{
queryString+=" AND " + colNames[i] + operations[i] + colValues[i];
}
return ExecuteQuery(queryString);
}
/// <summary>
/// 创建数据表
/// </summary> +
/// <returns>The table.</returns>
/// <param name="tableName">数据表名</param>
/// <param name="colNames">字段名</param>
/// <param name="colTypes">字段名类型</param>
public SqliteDataReader CreateTable(string tableName,string[] colNames,string[] colTypes)
{
string queryString = "CREATE TABLE " + tableName + "( " + colNames [0] + " " + colTypes [0];
for (int i=1; i<colNames.Length; i++)
{
queryString+=", " + colNames[i] + " " + colTypes[i];
}
queryString+= " ) ";
return ExecuteQuery(queryString);
}
/// <summary>
/// Reads the table.
/// </summary>
/// <returns>The table.</returns>
/// <param name="tableName">Table name.</param>
/// <param name="items">Items.</param>
/// <param name="colNames">Col names.</param>
/// <param name="operations">Operations.</param>
/// <param name="colValues">Col values.</param>
public SqliteDataReader ReadTable(string tableName,string[] items,string[] colNames,string[] operations, string[] colValues)
{
string queryString = "SELECT " + items [0];
for (int i=1; i<items.Length; i++)
{
queryString+=", " + items[i];
}
queryString += " FROM " + tableName + " WHERE " + colNames[0] + " " + operations[0] + " " + colValues[0];
for (int i=0; i<colNames.Length; i++)
{
queryString+=" AND " + colNames[i] + " " + operations[i] + " " + colValues[0] + " ";
}
return ExecuteQuery(queryString);
}
}
SQLiteHelper类主要实现了数据库、数据表的创建以及数据表中记录的增加、删除、更新、读取四种基本功能。该类最初由国外的Unity3D开发者发布在Unity3D官方论坛,后来经宣雨松使用C#进行重写,我在此基础上进行了完善,再此对两位大神的无私付出表示感谢。这里要说明的有三点:
一、在Unity3D编辑器下生成数据库文件(.db)默认位于和Assets目录同级的位置,即项目的工程文件夹中。我们可以通过修改路径在改变数据库文件的存储位置,具体来讲:
Windows平台:data source=Application.dataPath/数据库名称.db
IOS平台:data source=Application.persistentDataPath/数据库名称.db
Android平台:URL=file:Application.persistentDataPath/数据库名称.db(我想说Android平台就是个奇葩,搞什么特殊化嘛)
二、确保Unity3D编辑器中的.NET版本和MonoDevelop中的.NET版本都为2.0版本,在Unity3D中打包导出的程序可能不会保留数据库文件,因此需要手动将数据库文件拷贝到相应的位置,当然更加合理的方案是将数据库文件存放到StreamingAssets文件夹下,然后在第一次加载游戏的时候将数据库文件复制到对应平台上的存放位置。
三、在使用InsertValues方法时请参考SQLite中字段类型与C#中数据类型的对应关系,博主目前测试了int类型和string类型都没有什么问题,更多类型的数据请大家自行测试然后告诉博主测试的结果,如果大家有兴趣扩展这个辅助类的话可以自行去扩展哦,嘿嘿
好了,千呼万唤始出来的时候到了,下面我们以一个实例来完成今天的项目讲解,因为我们已经定义好了SQLite的辅助类,因此我们可以快速地编写出下面的脚本代码:
using UnityEngine;
using System.Collections;
using System.IO;
using Mono.Data.Sqlite;
public class SQLiteDemo : MonoBehaviour
{
/// <summary>
/// SQLite数据库辅助类
/// </summary>
private SQLiteHelper sql;
void Start ()
{
//创建名为sqlite4unity的数据库
sql = new SQLiteHelper("data source=sqlite4unity.db");
//创建名为table1的数据表
sql.CreateTable("table1",new string[]{"ID","Name","Age","Email"},new string[]{"INTEGER","TEXT","INTEGER","TEXT"});
//插入两条数据
sql.InsertValues("table1",new string[]{"'1'","'张三'","'22'","'Zhang3@163.com'"});
sql.InsertValues("table1",new string[]{"'2'","'李四'","'25'","'Li4@163.com'"});
//更新数据,将Name="张三"的记录中的Name改为"Zhang3"
sql.UpdateValues("table1", new string[]{"Name"}, new string[]{"'Zhang3'"}, "Name", "=", "'张三'");
//插入3条数据
sql.InsertValues("table1",new string[]{"3","'王五'","25","'Wang5@163.com'"});
sql.InsertValues("table1",new string[]{"4","'王五'","26","'Wang5@163.com'"});
sql.InsertValues("table1",new string[]{"5","'王五'","27","'Wang5@163.com'"});
//删除Name="王五"且Age=26的记录,DeleteValuesOR方法类似
sql.DeleteValuesAND("table1", new string[]{"Name","Age"}, new string[]{"=","="}, new string[]{"'王五'","'26'"});
//读取整张表
SqliteDataReader reader = sql.ReadFullTable ("table1");
while(reader.Read())
{
//读取ID
Debug.Log(reader.GetInt32(reader.GetOrdinal("ID")));
//读取Name
Debug.Log(reader.GetString(reader.GetOrdinal("Name")));
//读取Age
Debug.Log(reader.GetInt32(reader.GetOrdinal("Age")));
//读取Email
Debug.Log(reader.GetString(reader.GetOrdinal("Email")));
}
//读取数据表中Age>=25的所有记录的ID和Name
reader = sql.ReadTable ("table1", new string[]{"ID","Name"}, new string[]{"Age"}, new string[]{">="}, new string[]{"'25'"});
while(reader.Read())
{
//读取ID
Debug.Log(reader.GetInt32(reader.GetOrdinal("ID")));
//读取Name
Debug.Log(reader.GetString(reader.GetOrdinal("Name")));
}
//自定义SQL,删除数据表中所有Name="王五"的记录
sql.ExecuteQuery("DELETE FROM table1 WHERE NAME='王五'");
//关闭数据库连接
sql.CloseConnection();
}
}
在上面的代码中我们是在Start方法中创建了数据库和数据表,然而在实际使用中我们需要判断数据库和数据表是否存在,因此如果你使用这段脚本提示错误信息,请确保数据库和数据表是否已经存在。好了,下面的截图展示了程序运行的结果:
作为一个强大的数据库怎么能没有图形化的数据库管理工具呢?所以这里博主向大家推荐一个免安装的小工具SqliteStudio,使用这个工具可以帮助我们方便地管理Sqlite数据库里的数据,这样是不是比较方便呢?哈哈!这个工具可以从这里下载哦,或者使用SQLite Expert Personal 免费版!
补充:
上面操作命令封装好后,我们可以在新写个 单例封装 数据库操作管理类:
这里简单写下 初始将数据库放在StreamingAssets下,初次运行复制到持久化目录的情况(运行时新建的数据表不需要考虑这个),然后开始时读取数据库取数据。
using System.Collections;
using System.Collections.Generic;
using Mono.Data.Sqlite;
using UnityEngine;
using DevelopEngine;
using System.IO;
public class OperatingDB : MonoSingleton<OperatingDB>
{
public DbAccess db;
string appDBPath;
/// <summary>
/// 获取数据库引用
/// </summary>
public void CreateDataBase()
{
if (db != null)
return;
#if UNITY_STANDALONE_WIN || UNITY_EDITOR
appDBPath = Application.streamingAssetsPath + "Eam.db";
#elif UNITY_ANDROID || UNITY_IPHONE
appDBPath = Application.persistentDataPath + "/ARPG.db";
if(!File.Exists(appDBPath))
{
StartCoroutine(CopyDB());
}
#endif
db = new DbAccess("URI=file:" + appDBPath);
}
IEnumerator CopyDB()
{
string loadPath = string.Empty;
#if UNITY_STANDALONE_WIN || UNITY_EDITOR
loadPath = Application.streamingAssetsPath + "/Eam.db";
#elif UNITY_ANDROID
loadPath = "jar:file://" + Application.dataPath + "!/assets" + "/Eam.db";
#elif UNITY_IPHONE
loadPath = + Application.dataPath + "/Raw" + "/Eam.db";
#endif
WWW www = new WWW(loadPath);
yield return www;
File.WriteAllBytes(appDBPath, www.bytes);
}
}
可以在运行时在开始函数里调用,去初始化数据库数据。
单例:
using UnityEngine;
using System.Collections;
namespace DevelopEngine
{
public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
private static T instance = null;
public static T Instance {
get {
if (instance == null)
{
instance = FindObjectOfType(typeof(T)) as T;
if (instance == null)
{
instance = new GameObject("_" + typeof(T).Name).AddComponent<T>();
DontDestroyOnLoad(instance);
}
if (instance == null)
{
}
}
return instance;
}
}
void OnApplicationQuit () { if (instance != null) instance = null; }
public static T CreateInstance ()
{
if (Instance != null) Instance.OnCreate();
return Instance;
}
protected virtual void OnCreate ()
{
}
}
}
补充函数:
/// <summary>
/// 获取所有表名
/// </summary>
public List<string> GetAllTableNames()
{
string queryString = "select name from sqlite_master where type='table'";
var a = ExecuteQuery(queryString);
List<string> ownTableNames = null;
while (a.Read())
{
if (ownTableNames == null)
{
ownTableNames = new List<string>();
}
ownTableNames.Add(a.GetString(0));
}
return ownTableNames;
}
/// <summary>
/// 判断一个表是否存在
/// </summary>
/// <param name="tableName"></param>
/// <returns></returns>
public bool IsTableExist(string tableName)
{
string queryString = "select name from sqlite_master where type='table'";
var a = ExecuteQuery(queryString);
while (a.Read())
{
if (a.GetString(0) == tableName)
return true;
}
return false;
}
上述代码:http://pan.baidu.com/s/1nvMJQXV