StackExchange.Redis 之 hash 类型示例
StackExchange.Redis 的组件封装示例网上有很多,自行百度搜索即可。
这里只演示如何使用Hash类型操作数据:
// 在 hash 中存入或修改一个值 并设置order_hashkey有效期1分钟,过期自动删除;null为不过期
stopwatch.Start();
var isok = RedisCacheHelper.Instance.HashSet("order_hashkey", "order_hashfield", "", TimeSpan.FromMinutes());
stopwatch.Stop();
Console.WriteLine("在hash中存入一个值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
//判断该字段是否存在 hash 中
var isexists = RedisCacheHelper.Instance.HashExists("order_hashkey", "order_hashfield");
Console.WriteLine("判断该字段是否存在hash中:" + isexists); //返回 True 或 False
//从hash中移除指定字段
var isdel = RedisCacheHelper.Instance.HashDelete("order_hashkey", "order_hashfield");
Console.WriteLine("从hash中移除指定字段:" + isdel); //返回 True 或 False
//从hash中递增 默认按1递增
var getincre = RedisCacheHelper.Instance.HashIncrement("order_hashkey", "order_hashfield");
Console.WriteLine("从hash中递增:" + getincre); //返递增后的值 11 //从hash中递减 默认按1递减
var getdecre = RedisCacheHelper.Instance.HashDecrement("order_hashkey", "order_hashfield");
Console.WriteLine("从hash中递减:" + getdecre); //返递减后的值 10
//保存一个字符串类型集合
string[] strarray = { "", "", "", "", "" };
RedisCacheHelper.Instance.HashSetList("orderlist_hashkey", strarray, m => { return "hashfield_" + m.ToString(); }, new TimeSpan(, , )); //从数据库获取10条数据
stopwatch.Start();
var getlist = TestRedis.GetOrderFormAll();
stopwatch.Stop();
Console.WriteLine("从数据库获取10条数据消耗时间:" + stopwatch.ElapsedMilliseconds.ToString()); //保存多个对象集合 非序列化
stopwatch.Start();
RedisCacheHelper.Instance.HashSetObjectList("objlist_hashkey", getlist, r => { return r.ordercode.ToString(); }, );
stopwatch.Stop();
Console.WriteLine("将10条数据存入HashRedis 消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
//保存或修改一个hash对象(序列化) 对key设置过期时间有时候起作用,有时候不起作用
stopwatch.Start();
RedisCacheHelper.Instance.HashSet<OrderForm>("orderform_hashkey", "orderform_hashfield", getlist.FirstOrDefault(), new TimeSpan(, , ), );
stopwatch.Stop();
Console.WriteLine("保存或修改一个hash对象(序列化)消耗时间:" + stopwatch.ElapsedMilliseconds.ToString()); //保存Hash对象集合 序列化 对key设置过期时间可以起作用
stopwatch.Start();
RedisCacheHelper.Instance.HashSet<OrderForm>("orderformlist_hashkey", getlist, m=> { return m.ordercode.ToString(); }, new TimeSpan(, , ), );
stopwatch.Stop();
Console.WriteLine("保存Hash对象集合(序列化)消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
//从Hash Redis获取某一条订单信息 反序列化
stopwatch.Start();
var getorderinfo = RedisCacheHelper.Instance.HashGet<OrderForm>("orderform_hashkey", "orderform_hashfield", );
stopwatch.Stop();
Console.WriteLine("从Hash Redis获取某一条订单信息消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
Console.WriteLine(getorderinfo.ordercode);
Console.WriteLine(getorderinfo.Area);
Console.WriteLine(getorderinfo.City);
Console.WriteLine(getorderinfo.Province);
Console.WriteLine(getorderinfo.PostTime);
//根据hashkey 获取所有的值 反序列化
stopwatch.Start();
var getorderlist = RedisCacheHelper.Instance.HashGetAll<OrderForm>("orderformlist_hashkey", );
stopwatch.Stop();
Console.WriteLine("根据hashkey 获取所有的值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getorderlist)
{
Console.WriteLine("获取反序列化得到的ordercode: " + item.ordercode);
}
//根据hashkey获取单个字段hashField的值
stopwatch.Start();
var getvalue = RedisCacheHelper.Instance.HashGet("objlist_hashkey1", "City", );
stopwatch.Stop();
Console.WriteLine("获取单个字段hashField的值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
Console.WriteLine("获取City: " + getvalue); Console.WriteLine("------------------");
Console.WriteLine(); //获取多个字段hashField的值
List<RedisValue> fieldlist = new List<RedisValue>() { "ordercode", "Province", "City", "Area" };
stopwatch.Start();
var getlist = RedisCacheHelper.Instance.HashGet("objlist_hashkey1", fieldlist.ToArray(), );
stopwatch.Stop();
Console.WriteLine("获取多个字段hashField的值消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getlist)
{
Console.WriteLine("获取到的值: " + item);
}
//根据hashkey返回所有的HashFiled
stopwatch.Start();
var getkeylist = RedisCacheHelper.Instance.HashKeys("objlist_hashkey1", );
stopwatch.Stop();
Console.WriteLine("消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getkeylist)
{
Console.WriteLine("获取到的HashFiled: " + item);
} //根据hashkey返回所有HashFiled值
stopwatch.Start();
var getvaluelist = RedisCacheHelper.Instance.HashValues("objlist_hashkey1", );
stopwatch.Stop();
Console.WriteLine("消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
foreach (var item in getvaluelist)
{
Console.WriteLine("获取到的HashFiled值: " + item);
}
//模拟并发场景 使用HashDecrement递减功能 使用分布式锁,但是线程线程排队处理,有可能照成Redis超时
该示例会照成1个用户抢多个
stopwatch.Start();
List<Task> taskList = new List<Task>();
for (int i = ; i <= ; i++) //模拟多个用户同时请求
{
var task = Task.Run(() =>
{
try
{
//*************************开始****************
RedisValue token = Environment.MachineName;
while (true)
{
if (RedisCacheHelper.Instance.LockAndDo("lock_key", token,
() =>
{
//在这里处理并发逻辑 //先获取库存数
var getvalues = (int)RedisCacheHelper.Instance.HashGet("order_hashkey", "order_hashfield");
if (getvalues > )
{
//先自减,获取自减后的值
int order_Num = (int)RedisCacheHelper.Instance.HashDecrement("order_hashkey", "order_hashfield", null);
if (order_Num >= )
{
//下面执行订单逻辑(这里不考虑业务出错的情况)
Console.WriteLine("我抢到了,还剩余数量:" + order_Num);
}
else
{
Console.WriteLine("A商品已经被抢光了");
}
return true;
}
else
{
Console.WriteLine("B商品已经被抢光了");
return true;
} }, new TimeSpan(, , )))
{
break;
} } }
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
});
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
stopwatch.Stop();
Console.WriteLine("模拟并发场景消耗时间:" + stopwatch.ElapsedMilliseconds.ToString());
抢购前库存30个
抢购后库存0
针对上边的分布式做抢购固然可以,但是相对的会牺牲一部分性能,还有一个更好的解决办法,那就是Lua脚本:
List<Task> taskList = new List<Task>();
for (int i = ; i <= ; i++) //模拟多个用户同时请求
{
var task = Task.Run(() =>
{
var getguid = Guid.NewGuid().ToString("N"); //模拟用户名
var script = " local isexist= redis.call('hexists',@hid_1,@hfiled_1) " + //检查该用户是否已经抢到
" if tonumber(isexist) == 1 then " + //1 表示已经存在 0表示不存在
" return isexist " + //直接返回
" end " +
" local getstocknumber = redis.call('hget',@hid_2,@hfiled_2) " + //获取库存剩余数量
" if tonumber(getstocknumber) > 0 then " + //如果库存>0
" local decrtystock = redis.call('hincrby',@hid_2,@hfiled_2,@hvalue_2) " + //减去1个库存
" local setuser = redis.call('hset',@hid_1,@hfiled_1,@hvalue_1) " + //记录抢到的用户
" if tonumber(setuser) ==1 then " + //保存成功返回1 0失败
" redis.log(redis.LOG_WARNING,decrtystock) " + //打印redis日志
" end " +
" return setuser " + //返回1 表示true
" end "; //参数
object obj = new
{
@hid_1 = "orderSuccessList",
@hfiled_1 = getguid.ToString(),
@hvalue_1 = "",
@hid_2 = "order_hashkey",
@hfiled_2 = "order_hashfield",
@hvalue_2 = -
}; var getresult = RedisCacheHelper.Instance.HashOrderPut(script, obj);
Console.WriteLine("返回值:" + getresult); });
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
先设置库存数量为10个
操作日志及剩余库存数量
记录的用户数据
应用场景:
我们简单举个实例来描述下 Hash 的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息:
用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息,如果用普通的 key/value 结构来存储,主要有以下 2 种存储方式:
第一种方式将用户 ID 作为查找 key, 把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化 / 反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入 CAS 等复杂问题。
第二种方法是这个用户信息对象有多少成员就存成多少个 key-value 对儿,用用户 ID+ 对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户 ID 为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。
那么 Redis 提供的 Hash 很好的解决了这个问题,Redis 的 Hash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口,如下图:
也就是说,Key 仍然是用户 ID, value 是一个 Map,这个 Map 的 key 是成员的属性名,value 是属性值,这样对数据的修改和存取都可以直接通过其内部 Map 的 Key(Redis 里称内部 Map 的 key 为 field), 也就是通过 key(用户 ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。
这里同时需要注意,Redis 提供了接口 (hgetall) 可以直接取到全部的属性数据, 但是如果内部 Map 的成员很多,那么涉及到遍历整个内部 Map 的操作,由于 Redis 单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。
实现方式:
上面已经说到 Redis Hash 对应 Value 内部实际就是一个 HashMap,实际这里会有 2 种不同实现,这个 Hash 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 value redisObject 的 encoding 为 zipmap, 当成员数量增大时会自动转成真正的 HashMap, 此时 encoding 为 ht。
这里附上RedisHelp
/// <summary>
/// 判断该字段是否存在 hash 中
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public bool HashExists(string redisKey, string hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashExists(redisKey, hashField);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 从 hash 中移除指定字段
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public bool HashDelete(string redisKey, string hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashDelete(redisKey, hashField);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 从 hash 中移除多个指定字段
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public long HashDelete(string redisKey, IEnumerable<RedisValue> hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashDelete(redisKey, hashField.ToArray());
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 递增 默认按1递增 可用于计数
/// </summary>
/// <param name="key"></param>
/// <param name="span"></param>
/// <param name="db"></param>
/// <returns></returns>
public long HashIncrement(string redisKey, string hashField, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
var result = _db.HashIncrement(redisKey, hashField);
//设置过期时间
if (span != null)
{
this.KeyExpire(redisKey, span);
}
return result;
}
catch (Exception)
{ throw;
}
} /// <summary>
/// 递减 默认按1递减 可用于抢购类的案例
/// </summary>
/// <param name="key"></param>
/// <param name="span"></param>
/// <param name="db"></param>
/// <returns></returns>
public long HashDecrement(string redisKey, string hashField, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
var result = _db.HashDecrement(redisKey, hashField);
//设置过期时间
if (span != null)
{
this.KeyExpire(redisKey, span);
}
return result;
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 在 hash 中保存或修改一个值 字符类型
/// set or update the HashValue for string key
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <param name="value"></param>
/// <param name="span">过期时间,可空</param>
/// <returns></returns>
public bool HashSet(string redisKey, string hashField, string value, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
return _db.HashSet(redisKey, hashField, value);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 保存一个字符串类型集合
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey">Redis Key</param>
/// <param name="list">数据集合</param>
/// <param name="getFiledKey">字段key</param>
public void HashSetList(string redisKey, IEnumerable<string> list, Func<string, string> getFiledKey, TimeSpan? span = null, int db = -)
{
try
{
_db = GetDatabase(db);
List<HashEntry> listHashEntry = new List<HashEntry>();
foreach (var item in list)
{
listHashEntry.Add(new HashEntry(getFiledKey(item), item));
}
_db.HashSet(redisKey, listHashEntry.ToArray()); if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
}
catch (Exception ex)
{
throw;
}
} /// <summary>
/// 保存多个对象集合 非序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tableName">表名前缀</param>
/// <param name="list">数据集合</param>
/// <param name="getModelId">字段key</param>
public void HashSetObjectList<T>(string tableName, IEnumerable<T> list, Func<T, string> getModelId, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
foreach (var item in list)
{
List<HashEntry> listHashEntry = new List<HashEntry>();
Type t = item.GetType();//获得该类的Type
foreach (PropertyInfo pi in t.GetProperties())
{
string name = pi.Name; //获得属性的名字,后面就可以根据名字判断来进行些自己想要的操作
var value = pi.GetValue(item, null); //用pi.GetValue获得值
listHashEntry.Add(new HashEntry(name, value.ToString()));
}
_db.HashSet(tableName + getModelId(item), listHashEntry.ToArray());
}
}
catch (Exception ex)
{
throw;
}
} /// <summary>
/// 保存或修改一个hash对象(序列化)
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool HashSet<T>(string redisKey, string hashField, T value, TimeSpan? span = null, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
var json = JsonConvert.SerializeObject(value);
if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
return _db.HashSet(redisKey, hashField, json);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 保存Hash对象集合 序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey">Redis Key</param>
/// <param name="list">数据集合</param>
/// <param name="getModelId"></param>
public void HashSet<T>(string redisKey, IEnumerable<T> list, Func<T, string> getModelId, TimeSpan? span = null, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
List<HashEntry> listHashEntry = new List<HashEntry>();
foreach (var item in list)
{
string json = JsonConvert.SerializeObject(item);
listHashEntry.Add(new HashEntry(getModelId(item), json));
}
_db.HashSet(redisKey, listHashEntry.ToArray()); if (span != null)
{
//设置过期时间
this.KeyExpire(redisKey, span);
}
}
catch (Exception ex)
{
throw;
}
} /// <summary>
/// 从 hash 中获取对象(反序列化)
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public T HashGet<T>(string redisKey, string hashField, int db = -) where T : class
{
try
{
_db = GetDatabase(db);
return JsonConvert.DeserializeObject<T>(_db.HashGet(redisKey, hashField));
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 根据hashkey获取所有的值 序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey"></param>
/// <param name="db"></param>
/// <returns></returns>
public List<T> HashGetAll<T>(string redisKey, int db = -) where T : class
{
List<T> result = new List<T>();
try
{
_db = GetDatabase(db);
HashEntry[] arr = _db.HashGetAll(redisKey);
foreach (var item in arr)
{
if (!item.Value.IsNullOrEmpty)
{
var t = JsonConvert.DeserializeObject<T>(item.Value);
if (t != null)
{
result.Add(t);
}
}
}
}
catch (Exception)
{
throw;
}
return result;
} /// <summary>
/// 根据hashkey获取所有的值 非序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="redisKey"></param>
/// <param name="db"></param>
/// <returns></returns>
public IEnumerable<Dictionary<RedisValue, RedisValue>> HashGetAll(IEnumerable<string> redisKey, int db = -)
{
List<Dictionary<RedisValue, RedisValue>> diclist = new List<Dictionary<RedisValue, RedisValue>>();
try
{
_db = GetDatabase(db);
foreach (var item in redisKey)
{
HashEntry[] arr = _db.HashGetAll(item);
foreach (var he in arr)
{
Dictionary<RedisValue, RedisValue> dic = new Dictionary<RedisValue, RedisValue>();
if (!he.Value.IsNullOrEmpty)
{
dic.Add(he.Name, he.Value);
}
diclist.Add(dic);
}
}
}
catch (Exception)
{
throw;
}
return diclist;
} /// <summary>
/// 根据hashkey获取单个字段hashField的值
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public RedisValue HashGet(string redisKey, string hashField, int db = -)
{
try
{
_db = GetDatabase(db);
return _db.HashGet(redisKey, hashField);
}
catch (Exception)
{
throw;
}
} /// <summary>
/// 根据hashkey获取多个字段hashField的值
/// </summary>
/// <param name="redisKey"></param>
/// <param name="hashField"></param>
/// <returns></returns>
public RedisValue[] HashGet(string redisKey, RedisValue[] hashField, int db = -)
{
_db = GetDatabase(db);
return _db.HashGet(redisKey, hashField);
} /// <summary>
/// 根据hashkey返回所有的HashFiled
/// </summary>
/// <param name="redisKey"></param>
/// <returns></returns>
public IEnumerable<RedisValue> HashKeys(string redisKey, int db = -)
{
_db = GetDatabase(db);
return _db.HashKeys(redisKey);
} /// <summary>
/// 根据hashkey返回所有HashValue值
/// </summary>
/// <param name="redisKey"></param>
/// <returns></returns>
public RedisValue[] HashValues(string redisKey, int db = -)
{
_db = GetDatabase(db);
return _db.HashValues(redisKey);
}
StackExchange.Redis 之 hash 类型示例的更多相关文章
- StackExchange.Redis 之 String 类型示例
String类型很简单,就不做示例演示了,这里只贴出Helper类 /// <summary> /// 判断key是否存在 /// </summary> /// <par ...
- StackExchange.Redis 之 SortedSet 类型示例
1,增加操作 RedisCacheHelper.Instance.ZSortadd(); RedisCacheHelper.Instance.ZSortadd(); RedisCacheHelper. ...
- redis之Hash类型常用方法总结
redis之Hash类型常用方法总结 格式: 存--HMGET key field [field ...] 取--HMGET key field [field ...] M:表示能取多个值,many ...
- 怎样在Redis通过StackExchange.Redis 存储集合类型List
StackExchange 是由StackOverFlow出品, 是对Redis的.NET封装,被越来越多的.NET开发者使用在项目中. 绝大部分原先使用ServiceStack的开发者逐渐都转了过来 ...
- redis的Hash类型以及其操作
hashes类型 hashes类型及操作Redis hash是一个string类型的field和value的映射表.它的添加.删除操作都是0(1)(平均).hash特别适合用于存储对象.相较于将对象的 ...
- 二:redis 的hash类型相关操作
=====================二种:hash类型================== 介绍:redis -> hash是一个string类型的field和value的映射表 hash ...
- redis的hash类型
1.简单描述 hash是一个string类型的field和value的映射表.添加和删除操作都是O(1)(平均)的复杂度.hash类型特别适合用于存储对象.在field的数量在限制的范围内以及valu ...
- redis的hash类型!!!!
Hash类型 redsi的hash是基本类型之一,键值本身又是一对键值结构,是string类型的field和value的映射表,或者说是集合,适合存储对象. Hash的增操作 127.0.0.1:63 ...
- Redis之Hash类型操作
接口IRedisDaoHash: package com.net.test.redis.base.dao; import com.net.test.redis.base.entity.UserPsg; ...
随机推荐
- Kubernetes资源与对象简述
资料 k8s基本对象概念 背景 资源和对象 Kubernetes 中的所有内容都被抽象为"资源",如 Pod.Service.Node 等都是资源."对象" ...
- python利用scapy嗅探流量
能实时监测流量, 只显示有问题的流量, 可疑流量要显示出在那个数据包里 所有流量都保存到为pcap 每5000个包保存一个 第3个自动下载到本地 def sniff(count=0, st ...
- P2871 [USACO07DEC]手链Charm Bracelet(01背包模板)
题目传送门:P2871 [USACO07DEC]手链Charm Bracelet 题目描述 Bessie has gone to the mall's jewelry store and spies ...
- 【GeneXus】开发移动APP时,如何使用Canvas进行布局?
当我们开发移动端APP的时候,经常遇到一种布局方式,那就是层级的布局,比如:一张照片我想在照片的上面显示它的名称,但不影响我照片展示的布局大小,也就是这个名称是浮在照片上的,如图: 如果要实现这样的布 ...
- 使用iview遇到问题记录总结
1.iview设置日期不可用,设置开始开始时间早于结束时间 官网示例,设置今天之前不可选,但是不能识别thisdisabledDate (date) { return date && ...
- 超级火的java自学网站
学靠的是毅力和自律,一定要坚持,否则就会前功尽弃,我自己也一直在边学边工作,当然自学要配合好的学习资料. 我是通过这个地方去学习的,它可以添加学习计划,从java基础到高级,从后台到前端,从细节到框架 ...
- 点分治 (等级排) codeforces 321C
Now Fox Ciel becomes a commander of Tree Land. Tree Land, like its name said, has n cities connected ...
- isStatic:检测数据是不是除了symbol外的原始数据
function isStatic(value) { return( typeof value === 'string' || typeof value === 'number' || typeof ...
- ORM基础2 字段及其参数和meta
一.ORM简介 1.概念:ORM(Object Relational Mappingt ),对象关系映射 2.实质:类与数据库之间的映射 3.优点: 开发人员不用写数据库 4.缺点: 开发人员,数据库 ...
- Java入门 - 语言基础 - 06.变量类型
原文地址:http://www.work100.net/training/java-variable-type.html 更多教程:光束云 - 免费课程 变量类型 序号 文内章节 视频 1 概述 2 ...