用于python的dao:用于创建简单且一致的接口,以访问复杂而多样的数据源的工具。
py2store的Python项目详细描述
py2存储
存储积垢的方式和位置。
列出、读取、写入和删除结构化数据源/目标中的数据, 好像操作简单的python内置代码(dict、list)或通过您想与之交互的接口一样, 不受外形或物理特性的影响。 此外,可以在不更改业务逻辑代码的情况下更改这些特殊性。
快速启动
安装它(例如pip install py2store
)。
想一想你想使用的存储类型,然后像使用dict一样继续。 下面是本地存储的一个示例(您必须仅在此处字符串键)。
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
quickstore
默认情况下将使用pickle作为序列化程序在本地文件中存储内容。
如果未指定根目录,
它将使用它将创建的tmp目录(第一次尝试存储某些内容时)
它将创建任何需要创建以满足任何/key/that/contains/slashes的目录。
当然,一切都是可配置的。
更多示例
看起来像一个dict
下面,我们将创建一个默认存储并演示一些基本操作。 默认存储使用dict作为后端持久器。 dict既不是真正的后端,也不是持久化的。但是尝试一下有助于 足迹。
frompy2store.baseimportStores=Store()assertlist(s)==[]s['foo']='bar'# put 'bar' in 'foo'assert'foo'ins# check that 'foo' is in (i.e. a key of) sasserts['foo']=='bar'# see that the value that 'foo' contains is 'bar'assertlist(s)==['foo']# list all the keys (there's only one)assertlist(s.items())==[('foo','bar')]# list all the (key, value) pairsassertlist(s.values())==['bar']# list all the valuesassertlen(s)==1# Number of items in my stores['another']='item'# store another itemassertlen(s)==2# Now I have two!assertlist(s)==['foo','another']# here they are
上面的代码没有什么特别之处。
我刚刚演示了一个录音机的一些操作。
但py2store的目标正是这种简单性。
现在您可以将s=store()
替换为s=anotherstore(…)
其中anotherstore
现在使用一些其他后端,这些后端可以是远程或本地的,也可以是数据库,或者任何
可以将某物(值)存储在某处的系统。
(键)。
您可以从现有存储中选择(例如本地文件、AWS S3、MongoDB)或 很容易自己制作(稍后将详细介绍)。
但是,看起来你还是在和一个听写员说话。这不仅意味着你可以 与各种存储系统交谈,而不必实际学习如何,但也意味着 您编写的相同业务逻辑代码可以在不做任何修改的情况下重用。
但是py2store提供的不仅仅是一个简单的一致的facade,它可以存储东西, 但也提供了定义如何做到这一点的方法。
在键值存储的情况下,"how"是基于键值定义的(如何引用) 存储的对象和值(如何序列化和反序列化这些对象)。
转换关键点:相对路径和绝对路径
请看下面的示例,它将向存储区添加一层密钥转换。
# defining the storefrompy2store.baseimportStoreclassPrefixedKeyStore(Store):prefix=''def_id_of_key(self,key):returnself.prefix+key# prepend prefix before passing on to storedef_key_of_id(self,_id):ifnot_id.startswith(self.prefix):raiseValueError(f"_id {_id} wasn't prefixed with {self.prefix}")else:return_id[len(self.prefix):]# don't show the user the prefix# trying the store out s=PrefixedKeyStore()s.prefix='/ROOT/'assertlist(s)==[]s['foo']='bar'# put 'bar' in 'foo'assert'foo'ins# check that 'foo' is in (i.e. a key of) sasserts['foo']=='bar'# see that the value that 'foo' contains is 'bar'assertlist(s)==['foo']# list all the keys (there's only one)assertlist(s.items())==[('foo','bar')]# list all the (key, value) pairsassertlist(s.values())==['bar']# list all the valuesassertlen(s)==1# Number of items in my stores['another']='item'# store another itemassertlen(s)==2# Now I have two!assertlist(s)==['foo','another']# here they are
问:这一点也不令人印象深刻!和第一家店一样。这个前缀是什么意思?
A:前缀是隐藏的,这就是重点。你想说"相对的"(即"前缀自由") 语言,但在持久化数据之前,可能需要将此前缀放在键的前面 以及在显示给用户之前要删除的前缀。 考虑一下处理文件。每次存储内容时是否必须指定根文件夹 或者找回什么?
问:证明一下!
A:好的,让我们看看底层商店(dict)在处理什么:
assertlist(s.store.items())==[('/ROOT/foo','bar'),('/ROOT/another','item')]你明白了吗?"backend"使用的键实际上是前缀
"/root/"
序列化/反序列化
现在让我们演示序列化和反序列化。
假设我们想通过将"hello"
附加到存储的所有内容来反序列化存储的任何文本。
# defining the storefrompy2store.baseimportStoreclassMyFunnyStore(Store):def_obj_of_data(self,data):returnf'hello {data}'# trying the store out s=MyFunnyStore()assertlist(s)==[]s['foo']='bar'# put 'bar' in 'foo'assert'foo'ins# check that 'foo' is in (i.e. a key of) sasserts['foo']=='hello bar'# see that the value that 'foo' contains is 'bar'assertlist(s)==['foo']# list all the keys (there's only one)assertlist(s.items())==[('foo','hello bar')]# list all the (key, value) pairsassertlist(s.values())==['hello bar']# list all the values
在下面的代码中,我们希望通过对文本进行大写来序列化文本(并将其视为这样) 当我们检索文本时。
# defining the storefrompy2store.baseimportStoreclassMyOtherFunnyStore(Store):def_data_of_obj(self,obj):returnobj.upper()# trying the store out s=MyOtherFunnyStore()assertlist(s)==[]s['foo']='bar'# put 'bar' in 'foo'assert'foo'ins# check that 'foo' is in (i.e. a key of) sasserts['foo']=='BAR'# see that the value that 'foo' contains is 'bar'assertlist(s)==['foo']# list all the keys (there's only one)assertlist(s.items())==[('foo','BAR')]# list all the (key, value) pairsassertlist(s.values())==['BAR']# list all the values
在最后的序列化示例中,我们只实现了单向转换。 如果你只想有一个作家就可以了(所以只有N需要序列化程序)或读取器(仅此而已 需要反序列化程序)。 但是,在大多数情况下,您需要双向转换,指定对象 应该序列化以存储,以及如何反序列化以取回对象。
腌菜店
假设你想让商店充当你的序列化器。这就是它的样子。
# defining the storeimportpicklefrompy2store.baseimportStoreclassPickleStore(Store):protocol=Nonefix_imports=Trueencoding='ASCII'def_data_of_obj(self,obj):# serializerreturnpickle.dumps(obj,protocol=self.protocol,fix_imports=self.fix_imports)def_obj_of_data(self,data):# deserializerreturnpickle.loads(data,fix_imports=self.fix_imports,encoding=self.encoding)# trying the store out s=PickleStore()assertlist(s)==[]s['foo']='bar'# put 'bar' in 'foo'asserts['foo']=='bar'# I can get 'bar' back# behind the scenes though, it's really a pickle that is stored:asserts.store['foo']==b'\x80\x03X\x03\x00\x00\x00barq\x00.'
再说一次,你能拿回一个储存在dict中的字符串,这似乎没那么令人印象深刻。 有两个原因:(1)不需要序列化字符串来存储它们;(2)不需要序列化python 对象将它们存储在dict中。 但如果您(1)试图存储更复杂的类型,并且(2)实际将它们保存在文件系统或数据库中, 然后需要序列化。 这里的要点是序列化和持久化关注点与存储和检索关注点是分开的。 代码看起来仍在使用dict。
但是你如何改变坚持者呢?
通过使用一个持久化的持久化程序。
你也可以自己写。持久化程序使用py2store所需要的是它遵循接口
python的集合.mutablemapping
(或其子集)。更多关于如何让你自己坚持
您只需遵循collections.mutablemapping界面即可。
下面是一个如何在给定文件夹下的文件中持久化的简单示例。 (警告:如果您想要一个本地文件存储,不要使用它,而是在 存储文件夹!)
importosfromcollections.abcimportMutableMappingclassSimpleFilePersister(MutableMapping):"""Read/write (text or binary) data to files under a given rootdir. Keys must be absolute file paths. Paths that don't start with rootdir will be raise a KeyValidationError """def__init__(self,rootdir,mode='t'):ifnotrootdir.endswith(os.path.sep):rootdir=rootdir+os.path.sepself.rootdir=rootdirassertmodein{'t','b',''},f"mode ({mode}) not valid: Must be 't' or 'b'"self.mode=modedef__getitem__(self,k):withopen(k,'r'+self.mode)asfp:data=fp.read()returndatadef__setitem__(self,k,v):withopen(k,'w'+self.mode)asfp:fp.write(v)def__delitem__(self,k):os.remove(k)def__contains__(self,k):""" Implementation of "k in self" check. Note: MutableMapping gives you this for free, using a try/except on __getitem__, but the following uses faster os functionality."""returnos.path.isfile(k)def__iter__(self):yield fromfilter(os.path.isfile,map(lambdax:os.path.join(self.rootdir,x),os.listdir(self.rootdir)))def__len__(self):"""Note: There's system-specific faster ways to do this."""count=0for_inself.__iter__():count+=1returncountdefclear(self):"""MutableMapping creates a 'delete all' functionality by default. Better disable it!"""raiseNotImplementedError("If you really want to do that, loop on all keys and remove them one by one.")
现在试试这个:
importos# What folder you want to use. Defaulting to the home folder. You can choose another place, but make sure rootdir=os.path.expanduser('~/')# Defaulting to the home folder. You can choose another placepersister=SimpleFilePersister(rootdir)foo_fullpath=os.path.join(rootdir,'foo')persister[foo_fullpath]='bar'# write 'bar' to a file named foo_fullpathassertpersister[foo_fullpath]=='bar'# see that you can read the contents of that file to get your 'bar' backassertfoo_fullpathinpersister# the full filepath indeed exists in (i.e. "is a key of") the persisterassertfoo_fullpathinlist(persister)# you can list all the contents of the rootdir and file foo_fullpath in it
说你自己的crud方言
不喜欢这个类似于dict的接口?想说你自己的话吗? 我们掩护你!只需子类化SimpleFilePersister并进行您想要的更改:
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
0
变换关键点
但是处理完整路径可能会很烦人,并且可能会将代码与特定的本地系统结合得太紧密。
我们想用相对路径代替。
简单:将persister包装在前面定义的prefixedKeystore
中。
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
1
工作原理
py2store提供了三个方面,您可以定义或修改它们来存储您喜欢的内容和方式:
- 持久性:实际存储内容的位置(内存、文件、数据库等)
- 序列化:值转换。 python对象在持久化之前应该如何转换, 以及如何将持久化数据转换为python对象。
- 索引:键转换。如何命名/标识/索引数据。 完整路径或相对路径。参数的唯一组合(例如(国家、城市))。< 等
所有这些都允许您执行诸如"将这个(值)作为那个(键)存储在那里(persitence)", 移动"in-there"的单调特性,以及"this"和"that"如何转换为适合 在那里,完全不受业务逻辑代码的影响。应该是这样的。
注意:数据的实际持久化位置取决于基本crud方法
(\u getitem\uu
,\u setitem\uu
,\u delitem\uu
,\u iter\uu
等)将它们定义为。
您可以使用几个持久化器
我们将介绍一些基本的持久化器,它们可以随时使用。 每个类别中都有更多,我们将添加新类别,但是 这会让你开始。
这里有一个有用的函数,可以在给定键和值的情况下对存储区执行基本测试。 它并没有测试所有的存储方法(参见测试模块),而是演示 几乎每个商店都应该具备的基本功能。
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
2
本地文件
有很多本地文件存储可供选择做。 一个通用(但不是太通用)的本地文件存储是 "py2store.stores.local_store.relativePathFormatStoreEnforcingFormat"。 它可以为你做很多事情,比如在你的键上加一个前缀(这样你就可以用相对路径而不是绝对路径来说话)。 递归列出子目录中的所有文件, 只在列出具有给定模式的文件时显示它们, 并且不允许您写入不符合模式的密钥。 此外,它还具有创建参数化路径或解析路径参数所需的功能。
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
3
localfilestore的签名是:
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
4
通常path_格式只用于指定rootdir,如上所述。 但您可以进一步指定所需的格式。 例如,下面只会产生.wav文件, 只允许您写入以.wav结尾的键:
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
5
下面将添加这些.wav文件具有"somestring"格式的限制 后跟数字:
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
6
你明白了…
localfilestore的其他参数或python的open
函数的其他参数。
稍有不同的是,这里的模式
参数同时适用于读和写。
例如,如果mode='b'
打开文件时,将使用mode='rb'
读取
打开写入时使用mode='wb'
。对于非对称读/写模式,
用户可以指定读取模式
和写入模式
(在这种情况下,忽略模式
参数)。
MongoDB
MongoDB集合不像文件系统那样自然地成为键值存储。 mongodb存储"documents",它们是数据的json,有许多(可能是嵌套的)字段不是 默认情况下由架构强制。因此,为了将mongo作为一个密钥值存储库,我们需要 指定哪些字段应视为键,哪些字段应视为数据。
默认情况下,\u id
字段(默认情况下唯一确保包含唯一值的字段)是单键字段,并且
所有其他字段都被视为数据字段。
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
7
但是,每次都把密钥指定为dict会让人恼火。 键模式是固定的,因此您应该能够指定生成键的值的元组。 您可以使用mongotuplekeystore
>>> from py2store import QuickStore
>>>
>>> store = QuickStore() # will print what (tmp) rootdir it is choosing
>>> # Write something and then read it out again
>>> store['foo'] = 'baz'
>>> store['foo']
'baz'
>>>
>>> # Go see that there is now a file in the rootdir, named 'foo'!
>>>
>>> # Write something more complicated
>>> store['hello/world'] = [1, 'flew', {'over': 'a', "cuckoo's": map}]
>>> store['hello/world'] == [1, 'flew', {'over': 'a', "cuckoo's": map}]
True
>>>
>>> # do you have the key 'foo' in your store?
>>> 'foo' in store
True
>>> # how many items do you have now?
>>> assert len(store) >= 2 # can't be sure there were no elements before, so can't assert == 2
>>>
>>> # delete the stuff you've written
>>> del store['foo']
>>> del store['hello/world']
8
S3
它的工作方式与localstores非常相似,但存储在s3中。你需要在 我们可以用这个。在py2store.stores.s3商店中查找s3商店。
用例
接口读取
有多少次有人以某些嵌套文件夹的zip格式与您共享某些数据 谁的结构和命名选择令人着迷地晦涩难懂?然后你要花多少时间来编写代码 和那个怪胎接触?好吧,py2store的目的之一就是让这件事变得更容易。 您仍然需要了解数据存储的结构,以及如何将这些数据反序列化为python。 可以操纵的对象。但如果有了合适的工具,你不必做更多的事情。
更改存储内容的位置和方式
必须切换持久化对象的位置(比如从文件系统切换到s3),或者更改数据键的方式, 或者数据序列化的方式?如果使用py2store工具来分离不同的存储问题, 这将很容易改变,因为改变将是本地化的。如果你处理的代码 写的,考虑到所有的问题,py2store应该仍然能够提供帮助,因为您可以 更容易给新系统一个外观,使其看起来像旧系统。
所有这些也可以应用到数据库中,只要您使用的是crud操作 基本方法涵盖。
适配器:当学习曲线妨碍学习时
新的存储机制(dbs等)不断诞生,一些人开始使用它们,我们是eventu联盟领导使用它们 如果我们需要和那些人的系统一起工作。尽管我们很想学习 新来的孩子们的能力,有时我们没有时间去做。
如果有人给我们熟悉的新系统写了一个适配器,这不是很好吗? 像mongo一样与sql对话(反之亦然)。像文件系统一样与s3交谈。 现在这不是一个长期的解决方案:如果我们真的要大量使用新系统,我们 应该学会。但是当你刚开始做事的时候 是救命稻草。
py2store希望让您能够更轻松地推出适配器 以您熟悉的方式进入新系统。
以后再考虑存储,如果有的话
您有一个新项目或需要编写一个新应用程序。你需要储存资料并把资料读回来。 内容:你的应用需要运行的不同类型的资源。有些人喜欢思考 如何优化这方面。我没有。到时候我会交给专家来做。 不过,如果有的话,时间往往会晚些。很少有概念和MVP的证明能够成功生产。
因此,我只想继续学习业务逻辑并编写我的程序。 所以我需要一个简单的方法来获得一些最小的存储功能。 但是到了优化的时候,我不应该改变我的代码,而是改变我 道做事。我需要的是py2存储。
商店是虫子吗?刀?< >
你想怎么叫就怎么叫吧,真的。
将py2store转换为ya(p)orm(又一个(python)对象关系映射)是很有诱惑力的, 但那将是误导。py2store的目的不是将对象映射到数据库条目, 而是为基本的存储操作提供一致的接口。
从这个意义上说,py2store更类似于数据访问对象(dao)模式的实现。 当然,orm和dao之间的区别可能很模糊,所以所有这些都应该加上一点盐。
这种抽象的优点和缺点易于搜索和查找。
大多数数据交互机制都可以通过collections.abc接口的子集来满足。 例如,可以将python的collections.mapping接口用于任何键值存储,使数据访问 对象具有dict的外观,而不是使用其他流行的方法名选项,例如 例如读/写、加载/转储等。 其中一个危险是,由于dao的外观和行为类似于dict(但不是),用户可能会低估它 一些操作的运行成本。
一些链接
orm:对象关系映射:https://en.wikipedia.org/wiki/object-relational\u mapping
dao:数据访问对象:https://en.wikipedia.org/wiki/data-access-object" rel="nofollow">https://en.wikipedia.org/wiki/data-access-object