博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(Google App Engine Datastore API)GAE数据库API翻译
阅读量:2189 次
发布时间:2019-05-02

本文共 14345 字,大约阅读时间需要 47 分钟。

概览

 
具备可伸缩性的
GAE
数据存储中,能够存储和查询的数据对象,称之为
实体:
entities.
一个
entity
有一个或多个属性,表示所支持的几种支持的数据类型的一种值。属性还可以关联到其它
entity
,创建一对多或者多对多的关系。
 
 GAE
数据存储能在一个事务当中进行多个操作,并且可以在失败后回滚。在分布式的
Web
应用程序中这尤其重要。因为可能有多个用户同时读取和操作同样的数据。在多个用户都在改变数据时,
GAE
数据存储尝试重新执行事务。
 
和传统的数据库不一样,
GAE
数据存储利用分布式的结构以存储海量数据。一个
GAE
应用程序可以能够通过定义关联,查询索引来优化数据的分布式存储。
 
GAE
数据存储并不是关系数据库。在提供与传统数据库的相似
介面
的同时,利用它本身具备了可伸缩性的能力,提供了另外的方法来管理和设计数据库。
 
GAE
数据存储
API
用一种别具特色的机制定义数据模型。一个模型描述了一种
实体:
entity
,包括多个属性的类型和配置。应用程序利用
python
类来定义模型,类的
attributes
描述属性。一种类型的
entity
对应了一个模型类的对象实例。实例的
python attributes
则对应了属性值。一个
entity
可以用类的构造函数创建,并通过调用
put()
方法之后保存到服务器。
from google.appengine.ext import dbfrom google.appengine.api import usersclassPet(db.Model):  name = db.StringProperty(required=True)  type = db.StringProperty(required=True, choices=set(["cat","dog","bird"]))  birthdate = db.DateProperty()  weight_in_pounds = db.IntegerProperty()  spayed_or_neutered = db.BooleanProperty()  owner = db.UserProperty()pet =Pet(name="Fluffy",          type="cat",          owner=users.get_current_user())pet.weight_in_pounds =24pet.put()
GAE
数据存储提供了
2
种查询方式介面:一种是查询对象的介面,一种是类
SQL
的语言:
GQL
。一个查询返回了多个
entitiy
,放在对应的类模型的实例里面。可以修改,并返回到存储服务器。
 
if users.get_current_user():  user_pets = db.GqlQuery("SELECT * FROM Pet WHERE pet.owner = :1",                          users.get_current_user()) for pet in user_pets:    pet.spayed_or_neutered =True  db.put(user_pets)

Entities and Models

一个
entity
有一个
key
和属性集。应用程序利用
API
来定义数据模型,并且建立模型的实例来存储
entity
模型为这些
API
创建的
entity
提供一个通用的结构,并能定义规则来效验属性值。

Datastore Entities

大家都知道在
GAE
存储的一个数据对象称为
entity
。一个
entity
有多个属性,表示几种支持的数据类型中的一种的值。
每个
entity
都有一个
key
唯一标识这个
entity
。最简单的
key
有一个表类型和一个唯一的数字
id
,是
GAE
给它提供的。
这个
ID
值也可以是应用程序提供的一个字符串。
更多的信息参考
.
 
一个应用程序可以通过
key
从数据存储中
取出一个
entity
,或者通过匹配
entity
的属性来查询也可以。一个查询还可以匹配
key
的“祖先”。参阅
,一个查询可以返回
0
或者多条记录的
entity
。并可以预先排序。也可以限制返回的结果行数以节约内存。
 
不象传统的数据库,
GAE
不需要所有某种
dentity
都有相同的属性。应用程序能够通过
API
来规约他们的数据模型。

The Model Interface

利用模型,应用程序描述了数据的不同类型。一个模型是一个
python
类,继承自
Model
类。这个模型类定义了一个新的
entity
类型和以及这个类型所期望具备的属性。模型类利用
python
类的
attributes
来定义模型属性。每个类
attribute
Property
子类的实例。通常是一些已经定义好的属性类。一个属性实例包含了属性的配置,例如效验是否必须录入,或者当没有提供值的时候给它一个缺省值。
from
google
.
appengine
.
ext
import
db
class
Pet
(
db
.
Model
):
  name
=
db
.
StringProperty
(
required
=
True
)
  type
=
db
.
StringProperty
(
required
=
True
,
choices
=
set
([
"cat"
,
"dog"
,
"bird"
]))
  birthdate
=
db
.
DateProperty
()
  weight_in_pounds
=
db
.
IntegerProperty
()
  spayed_or_neutered
=
db
.
BooleanProperty
()
  owner
=
db
.
UserProperty
(
required
=
True
)
在编程接口里面,一个模型类对应的实例表现为一个表类型的
entity
。应用程序通过构造方法产生一个
entity
。通过实例的
attributes
来对属性进行存储。构造函数通过参数得到属性的初始值。
from
google
.
appengine
.
api
import
users
pet
=
Pet
(
name
=
"Fluffy"
,
          type
=
"cat"
,
          owner
=
users
.
get_current_user
())
pet
.
weight_in_pounds
=
24
注意:模型类的
attributes
是配置模型的属性的,他们的值是
Property
类的实例。模型实例的
attribute
是实际的属性值,是
python
类能够接受的值。
模型类利用
Property
实例来校验给模型实例的属性赋的值。当模型对象一开始建立校验就起作用了,在修改的时候也会进行校验。这保证了属性永远不会非法。由于实例构建的时候就开始校验,标志为必须录入的属性必须在构造方法里面提供值。在这个例子里面,
name
type
owner
都是
required
的,因此调用构建函数的时候就需要提供。而
weight in pounds
就不需要,所以一开始它们是没有赋值,可以在之后的程序当中赋值。
模型实例创建之后并没有在
datastore
里面存储的,必须在调用
put
方法之后才真正写入数据库。
 
注意:模型属性配置以及所有的
python
attribute
,在模块或者脚本第一次导入的时候初始化。因为
GAE
request
里面缓存了导入的模块,模块配置可能在一个用户请求之后,重新被其它用户请求所利用。
Do not initialize model property configuration, such as default values, with data specific to the request or the current user. See for more information(
不太理解
)

Expando Models

一个模型定义利用模型类建立固定数量的属性。所有的实例都必须具备这些属性。这是通常的做法,但是
GAE
其实并不需要所有的
entity
具有所有同样的属性。
 
有时候,一个
entity
和其它同类的
entity
具有不同的属性,是很有用的。这种
entity
API
里面表示为
expando
模型。一个
expando
模型类是
Expando
的子类。任何赋给
expando
实例的
attribute
的值变成
entity
的同名属性,这些属性叫做动态属性,而那些用
Property
类实例定义了的属性是固定属性。
一个
expando
模型可以同时具备固定属性和动态属性。固定属性是类模型用属性配置来定义的,而动态属性是应用程序在赋值给的时候才建立它们。
class
Person
(
db
.
Expando
):
  first_name
=
db
.
StringProperty
()
  last_name
=
db
.
StringProperty
()
  hobbies
=
db
.
StringListProperty
()
p
=
Person
(
first_name
=
"Albert"
,
last_name
=
"Johnson"
)
p
.
hobbies
=
[
"chess"
,
"travel"
]
p
.
chess_elo_rating
=
1350
p
.
travel_countries_visited
=
[
"Spain"
,
"Italy"
,
"USA"
,
"Brazil"
]
p
.
travel_trip_count
=
13
由于动态属性没有相应的模型属性定义,动态属性不会被校验。任何动态属性可以具备基本类型的值,包括
None
。同类的
2
entity
同一个动态属性可以有不同类型的值,也可以一个设置了属性而另外一个不设置。
                                                                                                
和固定属性不同,动态属性不一定存在。一个具有
None
值的动态属性不同于不存在的动态属性。如果
expando
模型实例没有任何动态属性,对应的数据
entity
就没有这些属性,你可以删除通过删除
attribute
来删除一个动态属性。
del
p
.
chess_elo_rating
一个在过滤里面用了动态属性的查询将只返回数据类型和查询语句一致的那些
entity
。同样的,查询将值返回设置了指定属性的
entities
p1
=
Person
()
p1
.
favorite
=
42
p1
.
put
()
p2
=
Person
()
p2
.
favorite
=
"blue"
p2
.
put
()
p3
=
Person
()
p3
.
put
()
people
=
db
.
GqlQuery
(
"SELECT * FROM Person WHERE favorite < :1"
,
50
)
# people has p1, but not p2 or p3
people
=
db
.
GqlQuery
(
"SELECT * FROM Person WHERE favorite > :1"
,
50
)
# people has no results
Expando
类是
Model
类的子类,并继承所有它的方法。

Properties and Types

Entity
属性支持固定的一些类型,包括
unicode
,整数,浮点,日期,
key
byte
,二进制,和各种
GData
类型。每种类型都对应
Property
的子类,在
google.appengine.ext.db
里面定义。
 
这里:
描述了所有支持的属性类,有几种特殊的类型下面介绍一下。

Strings, Long Strings and Blobs

Datastore
支持
2
中类型的文本:短字符串,
500
字节以下,长字符串,
500
以上。短字符串可以被索引并能够在查询的过滤子句里面用,也可以排序,长的就不行。
 
短字符串可以是
unicode
或者
str
值。如果是一个
str
,缺省是
’ascii’
编码。为
str
值指定一个不同的编码,可以用
unicode()
类型构造函数,把它转换为
unicode
值,参数是
str
和编码的名字。短字符串可以用
StringProperty
类来建模。
class
MyModel
(
db
.
Model
):
 
string
=
db
.
StringProperty
()
obj
=
MyModel
()
# Python Unicode literal syntax fully describes characters in a text string.
obj
.
string
=
u
"kittens"
# unicode() converts a byte string to a Unicode value using the named codec.
obj
.
string
=
unicode
(
"kittens"
,
"latin-1"
)
# A byte string is assumed to be text encoded as ASCII (the 'ascii' codec).
obj
.
string
=
"kittens"
# Short string properties can be used in query filters.
results
=
db
.
GqlQuery
(
"SELECT * FROM MyModel WHERE string = :1"
,
u
"kittens"
)
一个长字符串对应
db.Text
实例。它的构造器可以用
unicode
或者
str
值,和一个类型的可选参数。长字符串可用
TextProperty
类来建模
class
MyModel
(
db
.
Model
):
  text
=
db
.
TextProperty
()
obj
=
MyModel
()
# Text() can take a Unicode value.
obj
.
text
=
db
.
Text
(
u
"lots of kittens"
)
# Text() can take a byte string and the name of an encoding.
obj
.
text
=
db
.
Text
(
"lots of kittens"
,
"latin-1"
)
# If no encoding is specified, a byte string is assumed to be ASCII text.
obj
.
text
=
db
.
Text
(
"lots of kittens"
)
# Text properties can store large values.
obj
.
text
=
db
.
Text
(
open
(
"a_tale_of_two_cities.txt"
).
read
(),
"utf-8"
)
Datastore
还支持非文本的类型,
Blob
,和长字符串一样,
blob
不会被索引,也不可过滤查询、排序。
Blob
实例表现为字符字节的集合,创建时用
str
值来作为参数。用
BlogProperty
建模
class
MyModel
(
db
.
Model
):
  blob
=
db
.
BlobProperty
()
obj
=
MyModel
()
obj
.
blob
=
db
.
Blob
(
open
(
"image.png"
).
read
())

Lists

一个属性可以有多个值,在
datastore API
表现为
Python
list
List
包含任意被
datastore
支持的类型。
 
ListProperty
构建
list
模型,并强制
list
里面所有的值为指定的类型。为了方便,库提供了
StringListProperty
,等同于
ListProperty(basestring)
.
 
class
MyModel
(
db
.
Model
):
  numbers
=
db
.
ListProperty
(
long
)
obj
=
MyModel
()
obj
.
numbers
=
[
2
,
4
,
6
,
8
,
10
]
obj
.
numbers
=
[
"hello"
] 
# ERROR: MyModel.numbers must be a list of longs.
一个查询过滤
List
属性检测
list
的成员。如果至少有一个成员符合条件就通过这个条件过滤。
# Get all entities where numbers contains a 6.
results
=
db
.
GqlQuery
(
"SELECT * FROM MyModel WHERE numbers = 6"
)
# Get all entities where numbers contains at least one element less than 10.
results
=
db
.
GqlQuery
(
"SELECT * FROM MyModel WHERE numbers < 10"
)
查询过滤只作用于成员。没办法测试
2
list
是否一致。
 
内部处理上,
datastore
用多个值来表示
list
属性。如果一个
list
属性值是空列表,那么这个属性就没有表现在
datastore
。编程接口对待于静态的
ListProperty
属性和动态属性,是不一样的处理的。
  • 一个静态的
    ListProperty
    能够把一个空的
    list
    赋值给它。这个值在
    datastore
    不存在,但模型实例表现为好像这个值是一个空的
    list
    。静态的
    ListProperty
    不能够是
    None
    值。
  • 一个
    List
    动态属性的值不能够是一个空的
    list
    。然而它可以是
    None
    ,并可以删除。
 
由于列表属性这种存储方式,对列表属性的排序不常见。
  • 如果
    entities
    对列表属性进行正序排序,用来排序的值是列表的最小元素。
  • 如果
    entities
    倒序排序,用列表最大的值来排序。
  •  
    其它列表元素既不影响排序也,列表长度也不影响排序。
  • In the case of a tie, the key of the entity is used as the tie-breaker. (
    不太理解
    )
    。这个排序会导致
    [1..9]
    不管是倒序还是正序都是在
    [4, 5, 6, 7]
    的前面,这种没意思的结果。

References

一个属性值可以包含扩其它
entity
key
。这个值是一个
Key
类型的实例。
RefrenceProperty
类对
key
值建模,并迫使所有的值对应某个类型的
entity
。为了方便使用,提供了
类型指向自己。
class
FirstModel
(
db
.
Model
):
  prop
=
db
.
IntegerProperty
()
class
SecondModel
(
db
.
Model
):
  reference
=
db
.
ReferenceProperty
(
FirstModel
)
obj1
=
FirstModel
()
obj1
.
prop
=
42
obj1
.
put
()
obj2
=
SecondModel
()
# A reference value is the key of another entity.
obj2
.
reference
=
obj1
.
key
()
# Assigning a model instance to a property uses the entity's key as the value.
obj2
.
reference
=
obj1
一个
ReferenceProperty
属性值象一个模型实例
entity
一样的使用。如果引用的
entity
在内存不存在,访问它的时候自动从存储
里面取出相应记录。
obj2
.
reference
.
prop
=
999
obj2
.
reference
.
put
()
results
=
db
.
GqlQuery
(
"SELECT * FROM FirstModel"
)
another_obj
=
results
.
fetch
(
1
)[
0
]
v
=
another_obj
.
reference
.
prop
当一个
entity
的关联属性值执行了删除,这个关联属性不会被改变。一个关联属性值不能是一个不再有效的
key
。如果应用程序认为可以存在无效的关联,可以用
db.get()
来取出
entity
,并在使用它之前检测它的属性值。
obj1
=
db
.
get
(
obj2
.
reference
)
if
not
obj1
:
 
# Referenced entity was deleted.
ReferenceProperty
有另外的方便特征:往回关联。如果一个模型
ReferenceProperty
了另外一个模型,每个被关联的
entity
得到一个属性,它返回第一个指向它的模型一个查询结果
entities
# To fetch and iterate over every SecondModel entity that refers to the
# FirstModel instance obj1:
for
obj
in
obj1
.
firstmodel_set
:
 
# ...
往回关联属性缺省是
modelname_set(_set 
前面的名字是模型类的小写名字
)
,也可以在
ReferenceProperty
的构造函数的用
collection_name
参数来改变。
 
只有在使用
ReferenceProperty
模型属性类的时候,可以自动关联和废弃关联
,
类型检查和往回关联。用
Expando
动态属性或者
ListProperty
来储存
key
值的就没有这些特征了。

Property Names

Datastore
用前后的
2
个连续下划线保留所有的属性名称,应用程序必须避免这样的命名来定义属性。
 
 
因为缺省情况下
Python API
用模型实例的
attributes
来作为属性名称,所以那些已经被实例方法用过的
attributes
,就不能再作为属性名字了。同样的,模型构造方法里面
keyword
参数里面的名字也都不能用作属性的名字。
 
 
但其实
Datastore
本身是允许这些名字的。如果一个应用程序需要一个
entity
python API
保留的关键字作为属性名称,程序可以在构造函数用
name
参数来指定固定属性。

Creating, Gettingand Deleting Data

DatastoreAPI
entity
用模型类的实例来表示。模型类的方法可以创建,修改和删除
entity
。利用查询,或者直接通过
key
,可以从物理
datastore
服务器里面得到
entity

·         

·         

·         

·         

Creating and Updating an Entity

Model 或者Expando类的实例表示entity。应用程序通过对应模型类的构造方法来创建新的实例 entity

pet=Pet(name="Fluffy",

          type="cat",
          owner
=users.get_current_user())

直到第一次调用 put()方法之后,新的数据entity才写入数据库datastore。可以直接调用对象的put方法,也可以调用 db.put()

pet.put()

db.put(pet)

如果entity已经在数据库存在了,那么put()方法会去更新它。

查询返回多个模型实例。这些实例可以修改并更新回datastore.

if users.get_current_user():

  user_pets = db.GqlQuery("SELECT * FROM Pet WHERE pet.owner = :1",
                         users
.get_current_user())
 
for pet inuser_pets:
    pet
.spayed_or_neutered=True
  db
.put(user_pets)

Getting EntitiesUsing a Query

Datastore能够执行查询某个类型的entity。一个查询可以用条件子句来过滤entity的属性值,并能够返回经过排序的结果集。一个查询也可以通过祖先来限制查询结果的范围。

 

完整对查询语句的描述信息,包括它不能够做什么,请参阅.

DatastoreAPI
通过
2
中界面来执行查询:
Query
,用查询对象的一些方法。还有
GqlQuery
,是一种类似
SQL
的语言。

The Query Interface

Model 或者Expandoall() 方法返回查询对象,对应这种表类所有的entity。应用程序通过Filter()Order()ancesitor()来准备查询。

classStory(db.Model):

  title = db.StringProperty()
  date
= db.DateTimeProperty()
query
=Story.all()
query
.filter('title=','Foo')
query
.order('-date')
query
.ancestor(key)
# These methods can bechained together on one line.
query
.filter('title=','Foo').order('-date').ancestor(key)

The GqlQuery Interface

GqlQuery类构造函数的参数包括查询语句和可选的参数。语句包括数据的种类,条件过滤,排序还有祖先条件。还可以包括对结果集的限制以及偏移。

# Parameters can be bound with positional arguments.

query = db.GqlQuery("SELECT * FROM Story WHERE title = :1 "
                   
"AND ANCESTOR IS :2 "
                   
"ORDER BY date DESC",
                   
'Foo',key)
# Or, parameters can be boundwith keyword arguments.
query
= db.GqlQuery("SELECT * FROM Story WHERE title = :title "
                   
"AND ANCESTOR IS :parent "
                   
"ORDER BY date DESC",
                    title
='Foo', parent=key)
# String, number and Booleanvalues can be literal values in the string.
query
= db.GqlQuery("SELECT * FROM Story WHERE title = 'Foo' "
                   
"AND ANCESTOR IS :parent "
                   
"ORDER BY date DESC",
                    parent
=key)

Model类的Gql()方法也是从查询语句来准备一个GqlQuery对象。相对来说它隐藏了select * from model的语句,因为已经隐含包含了。

 

query=Story.gql("WHERE title= :title "

                  "AND ANCESTOR IS :parent "
                 
"ORDER BY date DESC",
                  title
='Foo', parent=key)

可以用bind()方法来再次绑定参数。应用程序可以通过重新绑定参数的方法来重新利用GqlQuery对象。

Executing the Query and Accessing Results

一直等到应用程序操作结果集的时候,QueryGqlQuery 对象才真正执行查询。当程序操作返回结果的时候,查询就会被执行了。结果将作为模型类的实例存储在内存里面。每个查询类都提供2个途径来执行查询和操作结果,fetch()方法,和迭代器。

Fetch()方法返回最大的数量(limit),一个可选的参数用于跳过(偏移值)。这个方法会执行查询,返回记录,直到没有为止。一旦所有的结果被放到内存里面,结果集作为list列表的形式返回(如果指定了偏移它会忽略响应的记录)。如果调用fetch()都会执行一个完全的查询。

注意:偏移参数不会影响返回的结果行数。所有超过它限制的记录都被返回到内存中。偏移参数只影响返回哪些记录

results= query.fetch(10)

for result in results:
 
print"Title: "+ result.title

Fetch()方法里面的行数限制和偏移会覆盖GQL里面的。

如果是在迭代器里面用,查询的执行将不会有限制和偏移,结果返回到内存中。所有结果在每次的迭代中被返回,迭代变量对应产生模型的实例。

 

for resultin query:

  print"Title: "+ result.title

注意:Query有一个系统的最大限制:1000行。如果没有指定限制,或者指定的限制大于最大限制,那么将采用最大限制。

Getting anEntity Using a Key

Entity
被存储到
datastore
之后,就有了一个唯一的
key
。在
API
里面表现
key
值是用
Key
类的一个实例。
Put()
方法返回它的
key
。在第一次被保存之后,就用
key()
来得到
key
值。

 

entity.put()

key = entity.key()
# ...
entity
= db.get(key)

Key值的常见用处是在其它的entity中用一个属性来存储。类把自动引用和废弃引用key:一个模型实例可以直接赋值给一个ReferenceProperty。并且它的key可以代替值来引用。

classPet(db.Model):

  name =StringProperty()
  owner
=ReferenceProperty(PetOwner)
classPetOwner(db.Model):
  name
=StringProperty()
owner
=PetOwner(name="Albert")
pet
=Pet(name="Fluffy",owner=owner)
# This is equivalent:
pet
=Pet(name="Fluffy",owner=owner.key())

类似的,通过ReferenceProperty属性对它的访问和访问它自身的实例一样,也是在fetch()的时候自动进行的,如果没有访问就不会fetch()

pets=GqlQuery("SELECT * FROM Pet WHERE name =:1","Fluffy")

pet = pets.get()
owner_name
= pet.owner.name

如果不是用ReferenceProperty存储key值,例如 Expando动态属性,或者 ListProperty 元素,就没有自动废弃关联的行为。(好像不太对。。。)

 

Db.get()函数通过key或者key的列表可以从datastore返回entity

Key可以被编码为字符串分发给应用程序的外部。然后把字符编码传递给Key类的构造函数可以重新得到key对象。

obj=MyModel(name="Foo")

self.response.write('<ahref="/view?key=%s">%s</a>'%(str(obj.key()),
                                                    obj
.name()))
# ...
key_name
= self.request.get('key')
obj
= db.get(db.Key(key_name))

注意:对key的字符串编码是虽然是晦涩难懂的,但没有加密,如果你的程序需要key是不能够猜测的,你要在在把key传送给用户之前加密它。

Deleting an Entity

应用程序能够根据模型实例或者key值来删除一个entity。实例方法是:delete()Db.delete()则通过key值或者key值列表来删除。

q= db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)

results = q.fetch(10)
for result in results:
  result
.delete()
# db.delete() requires thatall entities in one call be of the same
# entity group, because allof the deletes happen in one transaction.
q
= db.GqlQuery("SELECT* FROM Message WHERE create_date < :1"+
               
"AND ANCESTOR IS :2",
                earliest_date
, parent)
results
= q.fetch(10)
db
.delete(results)

删除一个entity不会改变引用它的key值。如果需要进行废除关联一个已经删除的entity,应用程序应调用db.get(),在访问属性的时候测试一下返回值。

删除一个其它entity 的祖先 entity不会影响其它的entity。因为程序并不需要根据祖先entity 来构建后代entity 的key,所以后代entity 仍然是可以访问的。

转载地址:http://kwzub.baihongyu.com/

你可能感兴趣的文章
深入理解JVM虚拟机4:Java class介绍与解析实践
查看>>
深入理解JVM虚拟机5:虚拟机字节码执行引擎
查看>>
深入理解JVM虚拟机6:深入理解JVM类加载机制
查看>>
深入了解JVM虚拟机8:Java的编译期优化与运行期优化
查看>>
深入理解JVM虚拟机9:JVM监控工具与诊断实践
查看>>
深入理解JVM虚拟机10:JVM常用参数以及调优实践
查看>>
深入理解JVM虚拟机11:Java内存异常原理与实践
查看>>
深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战
查看>>
深入理解JVM虚拟机13:再谈四种引用及GC实践
查看>>
Spring源码剖析1:Spring概述
查看>>
Spring源码剖析2:初探Spring IOC核心流程
查看>>
Spring源码剖析3:Spring IOC容器的加载过程
查看>>
Spring源码剖析4:懒加载的单例Bean获取过程分析
查看>>
Spring源码剖析5:JDK和cglib动态代理原理详解
查看>>
Spring源码剖析6:Spring AOP概述
查看>>
Spring源码剖析7:AOP实现原理详解
查看>>
Spring源码剖析8:Spring事务概述
查看>>
Spring源码剖析9:Spring事务源码剖析
查看>>
重新学习Mysql数据库1:无废话MySQL入门
查看>>
探索Redis设计与实现2:Redis内部数据结构详解——dict
查看>>