文章目錄
  1. 1. 数据库
    1. 1.1. SQLite
      1. 1.1.1. What
      2. 1.1.2. Why
      3. 1.1.3. How
    2. 1.2. SQLite4Unity3d实战
      1. 1.2.1. PC实战
      2. 1.2.2. Android实战
      3. 1.2.3. SQLite4Unity3d进阶
    3. 1.3. 结论
  2. 2. Reference
  3. 3. Github地址

数据库

数据存储对于游戏开发来说是必不可少的,在常规的游戏开发中,后端服务器会采用MySQL(关系型数据库),MongoDB(非关系型数据库)等作为数据库存储。

关于关系型和菲关系型数据库的学习理解参考:

网络通信和服务器学习

本章节是在没有后端但又有玩家数据存储需求的前提下诞生的,通过调研SQLite(关系型数据库)正是定位本地高效数据库而非CS结构的数据库,所以本章节通过深入学习SQLite,弄懂如何正确高效的把本地玩家数据存储到本地数据库。

SQLite

What

首先来看看SQLite官网的一个介绍:
SQLite is not directly comparable to client/server SQL database engines such as MySQL, Oracle, PostgreSQL, or SQL Server since SQLite is trying to solve a different problem.
Client/server SQL database engines strive to implement a shared repository of enterprise data. They emphasize scalability, concurrency, centralization, and control. SQLite strives to provide local data storage for individual applications and devices. SQLite emphasizes economy, efficiency, reliability, independence, and simplicity.
SQLite does not compete with client/server databases. SQLite competes with fopen().

从上面的介绍可以看出,SQLite(C语言编写的库)虽然也是关系型数据库,但他的定位和MySQL不一样,定位的是本地高效数据库而非CS结构的数据库。

Why

那么什么时候适用于SQLite?什么时候适用于MySQL(CS)了?
SQLite官网也给出了明确的介绍,详情参见官网SQLite
这里只列举下官网介绍的什么时候适用于MYSQL:

  1. Client/Server Applications(CS结构的程序—需要大量客户端服务器通信存储)
  2. High-volume Websites(高吞吐量的网站)
  3. Very large datasets(数据存储量大—数据大)
  4. High Concurrency(高并发—很多人同时写入)

How

这里本人明确了暂时是打算用于单机版游戏的本地数据库,所以倾向于用SQLite来做。
接下来会接触以下几个知识点:

  1. Unity(游戏开发引擎)
  2. SQLite4Unity3d(第三方给Unity封装好的SQLite库—支持Window,Android(ARM64貌似也支持了)和IOS)
  3. Navicat(可视化数据库工具)
  4. SQL(数据库查询语言)

目标:

  1. 编写一个数据库操作UI界面,支持数据库的增删改查操作(方便学习使用SQLite4Unity3d提供的接口访问SQLite数据库)
  2. 利用Navicat查看Window本地存储的数据库数据(学会Navicat的基本使用和数据库SQL相关操作)
  3. 打包编译Window和Android版本到真机上查看效果(确保多平台的兼容性)

SQLite4Unity3d实战

环境准备:
Unity版本:2018.4.8f1
SQLite4Unity3d: SQLite4Unity3d
Navivat: Navicat Premium
SQL:SQL学习1 or SQL学习2

Note:

  1. SQLite4Unity3d uses only the synchronous part of sqlite-net, so all the calls to the database are synchronous.(SQLite4Unity3d有这么一句官方提示,可以看出所有的数据库操作都是同步的。这里考虑到都是单机本地存储同时数据量也不会很大,所以同步异步不是很重要,所以并不影响个人使用。)

PC实战

接下来结合SQLite4Unity3d来实战学习数据库的增删改查等操作。
第1步
创建空的Unity工程,制作简单的交互UI
SQLiteUserInterface

第2步
拷贝SQLite相关库文件:
SQLitePlugin
还要拷贝SQLite.cs(SQLite4Unity3d作者封装的访问SQLite的相关接口)
通过查看SQLite.cs的源代码可以看出,作者封装的这一层通过CS层多抽象了一层数据库访问(增删改查)和表格数据结构,然后结合反射(反射查找对应类的数据库)和Linq(实现快速的数据库查询)来实现通用的数据访问。

第3步
定义数据库表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// 玩家信息
/// </summary>
public class Player
{
[PrimaryKey]
public int UID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }

public override string ToString()
{

return $"[Player: UId={UID}, FirstName={FirstName}, LastName={LastName}, Age={Age}]";
}
}

第4步
建立SQLite数据库连接(不存在的话会根据Flag决定是否自动创建):

1
2
3
DatabaseFolderPath = $"{Application.persistentDataPath}/";
// 建立数据库连接
mSQLiteConnect = new SQLiteConnection($"{DatabaseFolderPath}/{DatabaseName}", SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create);

可以看到本来StreamingAssets目录下没有数据库文件的这一下就创建成功了。
CreateDatabase

第5步
数据库里创建玩家表:

1
mSQLiteConnect.CreateTable<Player>();

CreatePlayerTable
因为还没有数据所以看不到数据打印,通过Navicat打开数据库,我们可以看到已经成功创建了Player表:
PlayerTableView

第6步
玩家表插入玩家数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
mSQLiteConnect.Insert(new Player
{
UID = 1,
FirstName = "Huan",
LastName = "Tang",
Age = 29,
});
mSQLiteConnect.Insert(new Player
{
UID = 3,
FirstName = "XiaoYun",
LastName = "Zhou",
Age = 28,
});
mSQLiteConnect.Insert(new Player
{
UID = 2,
FirstName = "Jiang",
LastName = "Fan",
Age = 28,
});
mSQLiteConnect.Insert(new Player
{
UID = 5,
FirstName = "ZhenLiang",
LastName = "Li",
Age = 29,
});
mSQLiteConnect.Insert(new Player
{
UID = 4,
FirstName = "XiaoLin",
LastName = "Kuang",
Age = 28,
});

InsertPlayerTableData

第7步
玩家表删除指定玩家ID数据:

1
mSQLiteConnect.Delete<Player>(deleteuid);

DeletePlayerTableData

第7步
玩家表修改指定玩家Age数据:

1
2
valideplayer.Age = newage;
mSQLiteConnect.Update(valideplayer);

ModifyPlayerTableAgeData

第8步
删除玩家表所有数据:

1
var rownumber = mSQLiteConnect.DeleteAll<Player>();

DeleteAllPlayerTableData
可以看到我们成功删除了玩家表里的所有数据,接下来用Navicat验证下数据库里的情况:
DatabaseAfterDeleteAllPlayerTableData

第9步
删除数据库里的玩家表:

1
var rownumberaffected = mSQLiteConnect.DropTable<Player>();

DeletePlayerTable
删除玩家表后通过Navicat查看数据库:
DatabaseViewAfterDeletePlayerTable
通过查看源码,我们可以看到,DraopTable底层实际是通过调用SQL的Drop语句来实现删除表的:

1
2
3
4
5
6
7
8
9
10
11
/// <summary>
/// Executes a "drop table" on the database. This is non-recoverable.
/// </summary>
public int DropTable<T>()
{
var map = GetMapping (typeof (T));

var query = string.Format("drop table if exists \"{0}\"", map.TableName);

return Execute (query);
}

关于SQL更多学习参考:
SQL 教程
SQL Tutorial
可以看到通过使用SQLite4Unity3d的SQLite.cs相关的接口,成功的实现了对于数据库的创建,以及数据库表的创建,增删改查等操作。

第10步
关闭数据库连接:

1
mSQLiteConnect.Close();

Android实战

为了验证SQLite4Unity3d的跨平台能力,接下来打包验证真机:
Android真机数据库操作:
AndroidSQLiteUsing

Android真机数据库文件查看(本人使用的ES文件浏览器):
![DatabaseViewAfterOperation]/img/Database/DatabaseViewAfterOperation.png)

Android真机数据库详细信息PC查看:
AndroidDatabaseView

可以看到Android真机上数据库的一些基础操作都成功了的。IOS这里就暂时不验证了,根据Github上的介绍理论上是支持的。

Note:

  1. 真机可读取目录修改为Application.persistentDataPath,因为Application.streamingAssetsPath在真机上是只读目录。
  2. 真机使用ES文件浏览器来查看程序目录下的数据库文件

SQLite4Unity3d进阶

学习了SQLite4Unity3d的基础使用,接下来要考虑的是实战使用时,如何封装一层数据库管理,方便游戏里的所有数据库数据都成功的加载读取修改保存。

这里主要是对于SQLite.cs的简单封装,用于支持多数据库创建,以及数据库创建路径管理,以及自定义数据库表的封装访问。

上代码之前,先简单看下封装的代码设计:

  • DatabaseManager.cs(对多数据库创建访问以及路径规划进行封装支持)
  • BaseDatabase.cs(数据库基类抽象—实现对数据库操作进行封装)
  • BaseTableData.cs(数据库表数据基类抽象—实现对数据库表操作进行封装)
  • BaseIntTableData.cs(int做主键的表数据抽象—实现对int主键表的操作封装)
  • BaseStringTableData.cs(string做主键的表数据抽象—实现对string主键表的操作封装)

DatabaseManager.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
namespace TH.Modules.Data
{
/// <summary>
/// 数据库管理类
/// 主要功能如下:
/// 1. 数据库路径处理(数据库存储管理)
/// 2. 多数据库支持
/// 3. 数据库连接,关闭,CUID操作等操作依然使用SQLite的封装
/// </summary>
public class DatabaseManager : SingletonTemplate<DatabaseManager>
{
/// <summary>
/// 数据库文件夹地址
/// </summary>
private readonly static string DatabaseFolderPath = Application.persistentDataPath + "/Database/";

/// <summary>
/// 已连接的数据库映射Map
/// Key为数据库名,Value为对应的数据库连接
/// </summary>
private Dictionary<string, SQLiteConnection> ConnectedDatabaseMap;

public DatabaseManager()
{
//检查数据库文件目录是否存在
if(!Directory.Exists(DatabaseFolderPath))
{
Directory.CreateDirectory(DatabaseFolderPath);
}
ConnectedDatabaseMap = new Dictionary<string, SQLiteConnection>();
}

/// <summary>
/// 打开数据库连接
/// </summary>
/// <param name="databasename"></param>
/// <param name="openflags"></param>
/// <param name="storeDateTimeAsTicks"></param>
public void openDatabase(string databasename, SQLiteOpenFlags openflags = SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, bool storeDateTimeAsTicks = false)
{
if(!isDatabaseConnected(databasename))
{
var databasepath = DatabaseFolderPath + databasename;
var connection = new SQLiteConnection(databasepath, openflags, storeDateTimeAsTicks);
ConnectedDatabaseMap.Add(databasename, connection);
Debug.Log($"连接数据库:{databasename}");
}
else
{
Debug.Log($"数据库:{databasename}已连接,请勿重复连接!");
}
}

/// <summary>
/// 获取指定数据库连接
/// </summary>
/// <param name="databasename"></param>
/// <returns></returns>
public SQLiteConnection getDatabaseConnection(string databasename)
{
SQLiteConnection connection;
if (ConnectedDatabaseMap.TryGetValue(databasename, out connection))
{
return connection;
}
else
{
return null;
}
}

/// <summary>
/// 关闭数据库连接
/// </summary>
/// <param name="databasename"></param>
public void closeDatabase(string databasename)
{
SQLiteConnection connection;
if(ConnectedDatabaseMap.TryGetValue(databasename, out connection))
{
connection.Close();
ConnectedDatabaseMap.Remove(databasename);
Debug.Log($"关闭数据库:{databasename}");
}
else
{
Debug.LogError($"未连接数据库:{databasename},关闭失败!");
}
}

/// <summary>
/// 关闭所有已连接的数据库
/// </summary>
public void closeAllDatabase()
{
foreach (var databasename in ConnectedDatabaseMap.Keys)
{
closeDatabase(databasename);
}
}

/// <summary>
/// 清除
/// </summary>
public void clear()
{
closeAllDatabase();
}

/// <summary>
/// 指定数据库是否已连接
/// </summary>
/// <param name="databasename"></param>
/// <returns></returns>
private bool isDatabaseConnected(string databasename)
{
return ConnectedDatabaseMap.ContainsKey(databasename);
}

#region 辅助方法
/// <summary>
/// 获取指定数据库表的所有数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="databasename">数据库名</param>
/// <returns></returns>
public string getTableAllDatasInOneString<T>(string databasename) where T : new()
{
SQLiteConnection sqliteconnection = getDatabaseConnection(databasename);
if (sqliteconnection != null)
{
var querytable = sqliteconnection.Table<T>();
if (querytable != null)
{
var result = string.Empty;
foreach (var data in querytable)
{
result += data.ToString();
result += "\n";
}
return result;
}
else
{
return string.Empty;
}
}
else
{
return string.Empty;
}
}
#endregion
}
}

TODO:

  1. 现阶段是默认存在包外的persistentDataPath,对于数据库如何做好加密管理避免被轻易修改,这个问题还有待学习思考。
  2. 数据库表是否存在还没找到方式判定(暂时是从流程上确保表存在的)

BaseDatabase.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
namespace TH.Modules.Data
{
/// <summary>
/// BaseDatabase.cs
/// 数据库基类抽象
/// Note:
/// 子类自行实现单例模式方便访问
/// </summary>
public abstract class BaseDatabase
{
/// <summary>
/// 数据库连接
/// </summary>
protected SQLiteConnection mSQLiteConnect;

/// <summary>
/// 数据库名
/// </summary>
public virtual string DatabaseName
{
get
{
return $"{GetType().Name}.db";
}
}

protected BaseDatabase()
{


}

/// <summary>
/// 加载数据库
/// </summary>
public void LoadDatabase()
{

mSQLiteConnect = DatabaseManager.Singleton.openDatabase(DatabaseName);
}

/// <summary>
/// 数据库是否已连接
/// </summary>
/// <returns></returns>
public bool IsConnected()
{

return mSQLiteConnect != null;
}

///// <summary>
///// 指定表是否存在
///// </summary>
///// <typeparam name="T"></typeparam>
///// <returns></returns>
//public bool IsTableExist<T>() where T : BaseTableData, new()
//{
// if (!IsConnected())
// {
// Debug.LogError($"数据库:{DatabaseName}未连接,访问类名:{typeof(T).Name}表是否存在失败!");
// return false;
// }
// // TODO: 找到方法判定是否存在
// return false;
//}

/// <summary>
/// 创建指定数据库表
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool CreateTable<T>() where T : BaseTableData, new()
{
if(!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,创建类名:{typeof(T).Name}表失败!");
return false;
}
//if(!IsTableExist<T>())
//{
// Debug.LogError($"数据库:{DatabaseName},类名:{typeof(T).Name}表已存在,请勿重复创建!");
// return false;
//}
mSQLiteConnect.CreateTable<T>();
return true;
}

/// <summary>
/// 插入指定int主键的表数据(如果表不存在则创建表)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool InsertDataI<T>(T data) where T : BaseIntTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,插入类名:{typeof(T).Name}表数据失败!");
return false;
}
//if (!IsTableExist<T>())
//{
// CreateTable<T>();
//}
mSQLiteConnect.Insert(data);
return true;
}

/// <summary>
/// 插入指定int主键的表数据(如果表不存在则创建表,如果主键存在则覆盖更新)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool InsertOrReplaceDataI<T>(T data) where T : BaseIntTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,插入类名:{typeof(T).Name}表数据失败!");
return false;
}
//if (!IsTableExist<T>())
//{
// CreateTable<T>();
//}
mSQLiteConnect.InsertOrReplace(data);
return true;
}

/// <summary>
/// 插入指定int主键的表数据(如果表不存在则创建表)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool InsertDataS<T>(T data) where T : BaseStringTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,插入类名:{typeof(T).Name}表数据失败!");
return false;
}
//if (!IsTableExist<T>())
//{
// CreateTable<T>();
//}
mSQLiteConnect.Insert(data);
return true;
}

/// <summary>
/// 插入指定string主键的表数据(如果表不存在则创建表,如果主键存在则覆盖更新)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool InsertOrReplaceDataS<T>(T data) where T : BaseStringTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,插入类名:{typeof(T).Name}表数据失败!");
return false;
}
//if (!IsTableExist<T>())
//{
// CreateTable<T>();
//}
mSQLiteConnect.InsertOrReplace(data);
return true;
}

/// <summary>
/// 删除指定UID的表数据(整形主键)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool DeleteDataByUIDI<T>(int uid) where T : BaseIntTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,删除类名:{typeof(T).Name}表UID:{uid}数据失败!");
return false;
}
var deletedNumber = mSQLiteConnect.Delete<T>(uid);
Debug.Log($"数据库:{DatabaseName},删除类名:{typeof(T).Name}表UID:{uid}数量:{deletedNumber}");
return deletedNumber > 0;
}

/// <summary>
/// 删除指定UID的表数据(字符串主键)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool DeleteDataByUIDS<T>(string uid) where T : BaseStringTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,删除类名:{typeof(T).Name}表数据失败!");
return false;
}
var deletedNumber = mSQLiteConnect.Delete<T>(uid);
Debug.Log($"数据库:{DatabaseName},删除类名:{typeof(T).Name}表UID:{uid}数量:{deletedNumber}");
return deletedNumber > 0;
}

/// <summary>
/// 更新指定int主键的表数据(如果表不存在则创建表)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool UpdateDataI<T>(T data) where T : BaseIntTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,插入类名:{typeof(T).Name}表数据失败!");
return false;
}
//if (!IsTableExist<T>())
//{
// CreateTable<T>();
//}
var updatedNumber = mSQLiteConnect.Update(data);
Debug.Log($"数据库:{DatabaseName},更新类名:{typeof(T).Name}表UID:{data.UID}数量:{updatedNumber}");
return updatedNumber > 0;
}

/// <summary>
/// 更新指定string主键的表数据(如果表不存在则创建表)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool UpdateDataS<T>(T data) where T : BaseStringTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,插入类名:{typeof(T).Name}表数据失败!");
return false;
}
//if (!IsTableExist<T>())
//{
// CreateTable<T>();
//}
var updatedNumber = mSQLiteConnect.Update(data);
Debug.Log($"数据库:{DatabaseName},更新类名:{typeof(T).Name}表UID:{data.UID}数量:{updatedNumber}");
return updatedNumber > 0;
}

/// <summary>
/// 获取指定UID的表数据(整形主键)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetDataByUIDI<T>(int uid) where T : BaseIntTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,获取类名:{typeof(T).Name}表数据失败!");
return null;
}
var data = mSQLiteConnect.Find<T>((databaseTable) => databaseTable.UID == uid);
if (data == null)
{
Debug.LogError($"数据库:{DatabaseName},获取类名:{typeof(T).Name}的UID:{uid}表数据不存在!");
return null;
}
return data;
}

/// <summary>
/// 获取指定UID的表数据(字符串主键)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetDataByUIDS<T>(string uid) where T : BaseStringTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,获取类名:{typeof(T).Name}表数据失败!");
return null;
}
var data = mSQLiteConnect.Find<T>((databaseTable) => databaseTable.UID == uid);
if (data == null)
{
Debug.LogError($"数据库:{DatabaseName},获取类名:{typeof(T).Name}的UID:{uid}表数据不存在!");
return null;
}
return data;
}


/// <summary>
/// 获取指定表所有数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public TableQuery<T> GetAllData<T>() where T : BaseTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,获取类名:{typeof(T).Name}表数据失败!");
return null;
}
var tbData = mSQLiteConnect.Table<T>();
if (tbData == null)
{
Debug.LogError($"数据库:{DatabaseName},获取类名:{typeof(T).Name}表所有数据不存在!");
return null;
}
return tbData;
}

/// <summary>
/// 删除指定表所有数据
/// </summary>
/// <returns></returns>
public int DeleteTableAll<T>() where T : BaseTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,删除类名:{typeof(T).Name}表所有数据失败!");
return 0;
}
//if (!IsTableExist<T>())
//{
// Debug.LogError($"数据库:{DatabaseName},删除类名:{typeof(T).Name}表不存在,删除所有数据失败!");
// return 0;
//}
var deletedNumber = mSQLiteConnect.DeleteAll<T>();
Debug.Log($"数据库:{DatabaseName},删除类名:{typeof(T).Name}表所有数据数量:{deletedNumber}");
return deletedNumber;
}

/// <summary>
/// 删除指定表
/// </summary>
/// <returns></returns>
public int DeleteTable<T>() where T : BaseTableData, new()
{
if (!IsConnected())
{
Debug.LogError($"数据库:{DatabaseName}未连接,删除类名:{typeof(T).Name}表失败!");
return 0;
}
var deletedNumber = mSQLiteConnect.DropTable<T>();
Debug.Log($"数据库:{DatabaseName},删除类名:{typeof(T).Name}表数量:{deletedNumber}");
return deletedNumber;
}

/// <summary>
/// 关闭数据库
/// </summary>
public void CloseDatabase()
{

DatabaseManager.Singleton.closeDatabase(DatabaseName);
mSQLiteConnect = null;
}
}
}

BaseTableData.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace TH.Modules.Data
{
/// <summary>
/// BaseTableData.cs
/// 数据库表数据基类抽象
/// </summary>
public abstract class BaseTableData
{
/// <summary>
/// 表名
/// </summary>
public string TableName
{
get
{
return GetType().Name;
}
}

public BaseTableData()
{


}

/// <summary>
/// 打印数据
/// </summary>
public override string ToString()
{

return $"[TableName:{TableName}]";
}
}
}

BaseIntTableData.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace TH.Modules.Data
{
/// <summary>
/// BaseIntTableData.cs
/// Int做主键的表数据抽象
/// </summary>
public abstract class BaseIntTableData : BaseTableData
{
/// <summary>
/// 主键UID
/// </summary>
[PrimaryKey]
public int UID
{
get;
set;
}

public BaseIntTableData() : base()
{


}

/// <summary>
/// 构造函数
/// </summary>
/// <param name="uid"></param>
public BaseIntTableData(int uid) : base()
{

UID = uid;
}

/// <summary>
/// 打印所有表数据
/// </summary>
public override string ToString()
{

return $"[TableName:{TableName} UID:{UID}]";
}
}
}

BaseStringTableData.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
namespace TH.Modules.Data
{
/// <summary>
/// BaseStringTableData.cs
/// string做主键的表数据抽象
/// </summary>
public abstract class BaseStringTableData : BaseTableData
{
[PrimaryKey]
public string UID
{
get;
set;
}

public BaseStringTableData() : base()
{


}

/// <summary>
/// 构造函数
/// </summary>
/// <param name="uid"></param>
public BaseStringTableData(string uid) : base()
{

UID = uid;
}

/// <summary>
/// 打印数据
/// </summary>
public override string ToString()
{

return $"[TableName:{TableName} UID:{UID}]";
}
}
}

GameDatabase.cs(自定义游戏数据库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// <summary>
/// GameDatabase.cs
/// 游戏数据库
/// </summary>
public class GameDatabase : BaseDatabase
{
/// <summary>
/// 单例对象
/// </summary>
public static GameDatabase Singleton
{
get
{
if(mSingleton != null)
{
return mSingleton;
}
mSingleton = new GameDatabase();
return mSingleton;
}
}
private static GameDatabase mSingleton;

public GameDatabase() : base()
{


}
}

Player.cs(自定义玩家数据表结构)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/// <summary>
/// 玩家信息表成员结构
/// </summary>
public class Player : BaseIntTableData
{
/// <summary>
///
/// </summary>
public string FirstName
{
get;
set;
}

/// <summary>
///
/// </summary>
public string LastName
{
get;
set;
}

/// <summary>
/// 年龄
/// </summary>
[Indexed]
public int Age
{
get;
set;
}

public Player() : base()
{


}

/// <summary>
/// 构造函数
/// </summary>
/// <param name="uid"></param>
/// <param name="firstName"></param>
/// <param name="lastName"></param>
/// <param name="age"></param>
public Player(int uid, string firstName, string lastName, int age) : base(uid)
{

FirstName = firstName;
LastName = lastName;
Age = age;
}

/// <summary>
/// 打印数据
/// </summary>
/// <returns></returns>
public override string ToString()
{

return $"[Player: UID={UID}, FirstName={FirstName}, LastName={LastName}, Age={Age}]";
}
}

进阶版的实战代码见Github源码:

SQLiteStudy

结论

  1. SQLite适用于非CS结构,存储数据量不大,不会多人并发操作数据库的情况(好比单机游戏的数据库)。
  2. 数据量大或者多人操作频繁或者需要CS架构支持,更适合MySQL+Redis
  3. SQLite4Unity3d通过反射和Linq对SQLite的封装实现了对于SQL无感和ORM(Object Relational Mapping)数据库访问方式。
  4. SQLite4Unity3d支持多平台,适合单机游戏数据存储

Reference

SQLite4Unity3d

Github地址

SQLiteStudy

文章目錄
  1. 1. 数据库
    1. 1.1. SQLite
      1. 1.1.1. What
      2. 1.1.2. Why
      3. 1.1.3. How
    2. 1.2. SQLite4Unity3d实战
      1. 1.2.1. PC实战
      2. 1.2.2. Android实战
      3. 1.2.3. SQLite4Unity3d进阶
    3. 1.3. 结论
  2. 2. Reference
  3. 3. Github地址