zoukankan      html  css  js  c++  java
  • python使用pymongo访问MongoDB的基本操作,以及CSV文件导出

    1. 环境。

    Python:3.6.1
    Python IDE:pycharm
    系统:win7

    2. 简单示例

    import pymongo

    # mongodb服务的地址和端口号
    mongo_url = "127.0.0.1:27017"

    # 连接到mongodb,如果参数不填,默认为“localhost:27017”
    client = pymongo.MongoClient(mongo_url)

    #连接到数据库myDatabase
    DATABASE = "myDatabase"
    db = client[DATABASE]

    #连接到集合(表):myDatabase.myCollection
    COLLECTION = "myCollection"
    db_coll = db[COLLECTION ]

    # 在表myCollection中寻找date字段等于2017-08-29的记录,并将结果按照age从大到小排序
    queryArgs = {'date':'2017-08-29'}
    search_res = db_coll.find(queryArgs).sort('age',-1)
    for record in search_res:
    print(f"_id = {record['_id']}, name = {record['name']}, age = {record['age']}")
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    3. 要点

    针对读操作,进行数据统计,尽量使用多线程,节省时间,只是要注意线程数量,会大量吃内存。
    4. mongoDB的数据类型

    MongoDB支持许多数据类型,如下:

    字符串 - 用于存储数据的最常用的数据类型。MongoDB中的字符串必须为UTF-8。
    整型 - 用于存储数值。 整数可以是32位或64位,具体取决于服务器。
    布尔类型 - 用于存储布尔值(true / false)值。
    双精度浮点数 - 用于存储浮点值。
    最小/最大键 - 用于将值与最小和最大BSON元素进行比较。
    数组 - 用于将数组或列表或多个值存储到一个键中。
    时间戳 - ctimestamp,当文档被修改或添加时,可以方便地进行录制。
    对象 - 用于嵌入式文档。
    对象 - 用于嵌入式文档。
    Null - 用于存储Null值。
    符号 - 该数据类型与字符串相同; 但是,通常保留用于使用特定符号类型的语言。
    日期 - 用于以UNIX时间格式存储当前日期或时间。您可以通过创建日期对象并将日,月,年的 - 日期进行指定自己需要的日期时间。
    对象ID - 用于存储文档的ID。
    二进制数据 - 用于存储二进制数据。
    代码 - 用于将JavaScript代码存储到文档中。
    正则表达式 - 用于存储正则表达式。
    不支持的数据类型:

    python中的集合(set)
    5. 对表(集合collection)的操作

    import pymongo

    # mongodb服务的地址和端口号
    mongo_url = "127.0.0.1:27017"

    # 连接到mongodb,如果参数不填,默认为“localhost:27017”
    client = pymongo.MongoClient(mongo_url)
    #连接到数据库myDatabase
    DATABASE = "amazon"
    db = client[DATABASE]

    #连接到集合(表):myDatabase.myCollection
    COLLECTION = "galance20170801"
    db_coll = db[COLLECTION]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    5.1. 查找记录:find

    (5.1.1)指定返回哪些字段
    # 示例一:所有字段
    # select * from galance20170801
    searchRes = db_coll.find()
    # 或者searchRes = db_coll.find({})
    1
    2
    3
    4
    # 示例二:用字典指定要显示的哪几个字段
    # select _id,key from galance20170801
    queryArgs = {}
    projectionFields = {'_id':True, 'key':True} # 用字典指定
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    # 结果{'_id': 'B01EYCLJ04', 'key': 'pro audio'}
    1
    2
    3
    4
    5
    6
    # 示例三:用字典指定去掉哪些字段
    queryArgs = {}
    projectionFields = {'_id':False, 'key':False} # 用字典指定
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    # 结果{'activity': False, 'avgStar': 4.3, 'color': 'Yellow & Black', 'date': '2017-08-01'}
    1
    2
    3
    4
    5
    # 示例四:用列表指定要显示哪几个字段
    # select _id,key,date from galance20170801
    queryArgs = {}
    projectionFields = ['key','date'] # 用列表指定,结果中一定会返回_id这个字段
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    # 结果{'_id': 'B01EYCLJ04', 'date': '2017-08-01', 'key': 'pro audio'}
    1
    2
    3
    4
    5
    6
    (5.1.2)指定查询条件
    (5.1.2.1). 比较:=,!=,>, <, >=, <=
    $ne:不等于(not equal)
    $gt:大于(greater than)
    $lt:小于(less than)
    $lte:小于等于(less than equal)
    $gte:大于等于(greater than equal)
    1
    2
    3
    4
    5
    # 示例一:相等
    # select _id,key,sales,date from galance20170801 where key = 'TV & Video'
    queryArgs = {'key':'TV & Video'}
    projectionFields = ['key','sales','date']
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    # 结果:{'_id': '0750699973', 'date': '2017-08-01', 'key': 'TV & Video', 'sales': 0}
    1
    2
    3
    4
    5
    6
    # 示例二:不相等
    # select _id,key,sales,date from galance20170801 where sales != 0
    queryArgs = {'sales':{'$ne':0}}
    projectionFields = ['key','sales','date']
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    # 结果:{'_id': 'B01M996469', 'date': '2017-08-01', 'key': 'stereos', 'sales': 2}
    1
    2
    3
    4
    5
    6
    # 示例三:大于
    # where sales > 100
    queryArgs = {'sales':{'$gt':100}}
    # 结果:{'_id': 'B010OYASRG', 'date': '2017-08-01', 'key': 'Sound Bar', 'sales': 124}
    1
    2
    3
    4
    # 示例四:小于
    # where sales < 100
    queryArgs = {'sales':{'$lt':100}}
    # 结果:{'_id': 'B011798DKQ', 'date': '2017-08-01', 'key': 'pro audio', 'sales': 0}
    1
    2
    3
    4
    # 示例五:指定范围
    # where sales > 50 and sales < 100
    queryArgs = {'sales':{'$gt':50, '$lt':100}}
    # 结果:{'_id': 'B008D2IHES', 'date': '2017-08-01', 'key': 'Sound Bar', 'sales': 66}
    1
    2
    3
    4
    # 示例六:指定范围,大于等于,小于等于
    # where sales >= 50 and sales <= 100
    queryArgs = {'sales':{'$gte':50, '$lte':100}}
    # 结果:{'_id': 'B01M6DHW26', 'date': '2017-08-01', 'key': 'radios', 'sales': 50}
    1
    2
    3
    4
    (5.1.2.2). and
    # 示例一:不同字段,并列条件
    # where date = '2017-08-01' and sales = 100
    queryArgs = {'date':'2017-08-01', 'sales':100}
    # 结果:{'_id': 'B01BW2YYYC', 'date': '2017-08-01', 'key': 'Video', 'sales': 100}
    1
    2
    3
    4
    # 示例二:相同字段,并列条件
    # where sales >= 50 and sales <= 100
    # 正确:queryArgs = {'sales':{'$gte':50, '$lte':100}}
    # 错误:queryArgs = {'sales':{'$gt':50}, 'sales':{'$lt':100}}
    # 结果:{'_id': 'B01M6DHW26', 'date': '2017-08-01', 'key': 'radios', 'sales': 50}
    1
    2
    3
    4
    5
    (5.1.2.3). or
    # 示例一:不同字段,或条件
    # where date = '2017-08-01' or sales = 100
    queryArgs = {'$or':[{'date':'2017-08-01'}, {'sales':100}]}
    # 结果:{'_id': 'B01EYCLJ04', 'date': '2017-08-01', 'key': 'pro audio', 'sales': 0}
    1
    2
    3
    4
    # 示例二:相同字段,或条件
    # where sales = 100 or sales = 120
    queryArgs = {'$or':[{'sales':100}, {'sales':120}]}
    # 结果:
    # {'_id': 'B00X5RV14Y', 'date': '2017-08-01', 'key': 'Chargers', 'sales': 120}
    # {'_id': 'B0728GGX6Y', 'date': '2017-08-01', 'key': 'Glasses', 'sales': 100}
    1
    2
    3
    4
    5
    6
    (5.1.2.4). in,not in,all
    # 示例一:in
    # where sales in (100,120)
    # 这个地方一定要注意,不能用List,只能用元组,因为是不可变的
    # 如果用了 {'$in': [100,120]},就会出现异常:TypeError: unhashable type: 'list'
    queryArgs = {'sales':{'$in': (100,120)}}
    # 结果:
    # {'_id': 'B00X5RV14Y', 'date': '2017-08-01', 'key': 'Chargers', 'sales': 120}
    # {'_id': 'B0728GGX6Y', 'date': '2017-08-01', 'key': 'Glasses', 'sales': 100}
    1
    2
    3
    4
    5
    6
    7
    8
    # 示例二:not in
    # where sales not in (100,120)
    queryArgs = {'sales':{'$nin':(100,120)}}
    # 结果:{'_id': 'B01EYCLJ04', 'date': '2017-08-01', 'key': 'pro audio', 'sales': 0}
    1
    2
    3
    4
    # 示例三:匹配条件内所有值 all
    # where sales = 100 and sales = 120
    queryArgs = {'sales':{'$all':[100,120]}} # 必须同时满足
    # 结果:无结果
    1
    2
    3
    4
    # 示例四:匹配条件内所有值 all
    # where sales = 100 and sales = 100
    queryArgs = {'sales':{'$all':[100,100]}} # 必须同时满足
    # 结果:{'_id': 'B01BW2YYYC', 'date': '2017-08-01', 'key': 'Video', 'sales': 100}
    1
    2
    3
    4
    (5.1.2.5). 字段是否存在
    # 示例一:字段不存在
    # where rank2 is null
    queryArgs = {'rank2':None}
    projectionFields = ['key','sales','date', 'rank2']
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    # 结果:{'_id': 'B00ACOKQTY', 'date': '2017-08-01', 'key': '3D TVs', 'sales': 0}

    # mongodb中的命令
    db.categoryAsinSrc.find({'isClawered': true, 'avgCost': {$exists: false}})
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 示例二:字段存在
    # where rank2 is not null
    queryArgs = {'rank2':{'$ne':None}}
    projectionFields = ['key','sales','date','rank2']
    searchRes = db_coll.find(queryArgs, projection = projectionFields).limit(100)
    # 结果:{'_id': 'B014I8SX4Y', 'date': '2017-08-01', 'key': '3D TVs', 'rank2': 4.0, 'sales': 0}
    1
    2
    3
    4
    5
    6
    (5.1.2.6). 正则表达式匹配:$regex(SQL:like)
    # 示例一:关键字key包含audio子串
    # where key like "%audio%"
    queryArgs = {'key':{'$regex':'.*audio.*'}}
    # 结果:{'_id': 'B01M19FGTZ', 'date': '2017-08-01', 'key': 'pro audio', 'sales': 1}
    1
    2
    3
    4
    (5.1.2.7). 数组中必须包含元素:$all
    # 查询记录,linkNameLst是一个数组,指定linkNameLst字段必须包含 'Electronics, Computers & Office' 这个元素。
    db.getCollection("2018-01-24").find({'linkNameLst': {'$all': ['Electronics, Computers & Office']}})

    # 查询记录,linkNameLst是一个数组,指定linkNameLst字段必须同时包含 'Wearable Technology' 和 'Electronics, Computers & Office' 这两个元素。
    db.getCollection("2018-01-24").find({'linkNameLst': {'$all': ['Wearable Technology', 'Electronics, Computers & Office']}})
    1
    2
    3
    4
    5
    (5.1.2.8). 按数组大小查询
    两个思路:
    第一个思路:使用$where(具有很大的灵活性,但是速度会慢一些)
    # priceLst是一个数组, 目标是查询 len(priceLst) < 3
    db.getCollection("20180306").find({$where: "this.priceLst.length < 3"})
    1
    2
    关于$where,请参考官方文档:http://docs.mongodb.org/manual/reference/operator/query/where/。
    第二个思路:判断数组中的某个指定索引的元素是否存在(会比较高效)
    例如:如果要求 len(priceLst) < 3:就意味着 num[ 2 ]不存在
    # priceLst是一个数组, 目标是查询 len(priceLst) < 3
    db.getCollection("20180306").find({'priceLst.2': {$exists: 0}})
    1
    2
    例如:如果要求 len(priceLst) > 3:就意味着 num[ 3 ]存在
    # priceLst是一个数组, 目标是查询 len(priceLst) > 3
    db.getCollection("20180306").find({'priceLst.3': {$exists: 1}})
    1
    2
    (5.1.3)指定查询条件
    (5.1.3.1). 限定数量:limit
    # 示例一:按sales降序排列,取前100
    # select top 100 _id,key,sales form galance20170801 where key = 'speakers' order by sales desc
    queryArgs = {'key':'speakers'}
    projectionFields = ['key','sales']
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    topSearchRes = searchRes.sort('sales',pymongo.DESCENDING).limit(100)
    1
    2
    3
    4
    5
    6
    (5.1.3.2). 排序:sort
    # 示例二:按sales降序,rank升序
    # select _id,key,date,rank from galance20170801 where key = 'speakers' order by sales desc,rank
    queryArgs = {'key':'speakers'}
    projectionFields = ['key','sales','rank']
    searchRes = db_coll.find(queryArgs, projection = projectionFields)
    # sortedSearchRes = searchRes.sort('sales',pymongo.DESCENDING) # 单个字段
    sortedSearchRes = searchRes.sort([('sales', pymongo.DESCENDING),('rank', pymongo.ASCENDING)]) # 多个字段
    # 结果:
    # {'_id': 'B000289DC6', 'key': 'speakers', 'rank': 3.0, 'sales': 120}
    # {'_id': 'B001VRJ5D4', 'key': 'speakers', 'rank': 5.0, 'sales': 120}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    (5.1.3.3). 统计:count
    # 示例三:统计匹配记录总数
    # select count(*) from galance20170801 where key = 'speakers'
    queryArgs = {'key':'speakers'}
    searchResNum = db_coll.find(queryArgs).count()
    # 结果:
    # 106
    1
    2
    3
    4
    5
    6
    5.2. 添加记录

    5.2.1. 单条插入

    # 示例一:指定 _id,如果重复,会产生异常
    ID = 'firstRecord'
    insertDate = '2017-08-28'
    count = 10
    insert_record = {'_id':ID, 'endDate': insertDate, 'count': count}
    insert_res = db_coll.insert_one(insert_record)
    print(f"insert_id={insert_res.inserted_id}: {insert_record}")
    # 结果:insert_id=firstRecord: {'_id': 'firstRecord', 'endDate': '2017-08-28', 'count': 10}
    1
    2
    3
    4
    5
    6
    7
    8
    # 示例二:不指定 _id,自动生成
    insertDate = '2017-10-10'
    count = 20
    insert_record = {'endDate': insertDate, 'count': count}
    insert_res = db_coll.insert_one(insert_record)
    print(f"insert_id={insert_res.inserted_id}: {insert_record}")
    # 结果:insert_id=59ad356d51ad3e2314c0d3b2: {'endDate': '2017-10-10', 'count': 20, '_id': ObjectId('59ad356d51ad3e2314c0d3b2')}
    1
    2
    3
    4
    5
    6
    7
    5.2.2. 批量插入

    # 更高效,但要注意如果指定_id,一定不能重复
    # ordered = True,遇到错误 break, 并且抛出异常
    # ordered = False,遇到错误 continue, 循环结束后抛出异常
    insertRecords = [{'i':i, 'date':'2017-10-10'} for i in range(10)]
    insertBulk = db_coll.insert_many(insertRecords, ordered = True)
    print(f"insert_ids={insertBulk.inserted_ids}")
    # 结果:insert_ids=[ObjectId('59ad3ba851ad3e1104a4de6d'), ObjectId('59ad3ba851ad3e1104a4de6e'), ObjectId('59ad3ba851ad3e1104a4de6f'), ObjectId('59ad3ba851ad3e1104a4de70'), ObjectId('59ad3ba851ad3e1104a4de71'), ObjectId('59ad3ba851ad3e1104a4de72'), ObjectId('59ad3ba851ad3e1104a4de73'), ObjectId('59ad3ba851ad3e1104a4de74'), ObjectId('59ad3ba851ad3e1104a4de75'), ObjectId('59ad3ba851ad3e1104a4de76')]
    1
    2
    3
    4
    5
    6
    7
    5.3. 修改记录

    # 根据筛选条件_id,更新这条记录。如果找不到符合条件的记录,就插入这条记录(upsert = True)
    updateFilter = {'_id': item['_id']}
    updateRes = db_coll.update_one(filter = updateFilter,
    update = {'$set': dict(item)},
    upsert = True)
    print(f"updateRes = matched:{updateRes.matched_count}, modified = {updateRes.modified_count}")
    1
    2
    3
    4
    5
    6
    7
    # 根据筛选条件,更新部分字段:i是原有字段,isUpdated是新增字段
    filterArgs = {'date':'2017-10-10'}
    updateArgs = {'$set':{'isUpdated':True, 'i':100}}
    updateRes = db_coll.update_many(filter = filterArgs, update = updateArgs)
    print(f"updateRes: matched_count={updateRes.matched_count}, "
    f"modified_count={updateRes.modified_count} modified_ids={updateRes.upserted_id}")
    # 结果:updateRes: matched_count=8, modified_count=8 modified_ids=None
    1
    2
    3
    4
    5
    6
    7


    5.4. 删除记录

    5.4.1. 删除一条记录

    # 示例一:和查询使用的条件一样
    queryArgs = {'endDate':'2017-08-28'}
    delRecord = db_coll.delete_one(queryArgs)
    print(f"delRecord={delRecord.deleted_count}")
    # 结果:delRecord=1
    1
    2
    3
    4
    5
    5.4.2. 批量删除

    # 示例二:和查询使用的条件一样
    queryArgs = {'i':{'$gt':5, '$lt':8}}
    # db_coll.delete_many({}) # 清空数据库
    delRecord = db_coll.delete_many(queryArgs)
    print(f"delRecord={delRecord.deleted_count}")
    # 结果:delRecord=2
    1
    2
    3
    4
    5
    6
    6. 将数据库文档写入csv文件。

    6.1. 标准代码

    读csv文件
    import csv

    with open("phoneCount.csv", "r") as csvfile:
    reader = csv.reader(csvfile)
    # 这里不需要readlines
    for line in reader:
    print(f"# line = {line}, typeOfLine = {type(line)}, lenOfLine = {len(line)}")
    # 输出结果如下:
    line = ['850', 'rest', '43', 'NN'], typeOfLine = <class 'list'>, lenOfLine = 4
    line = ['9865', 'min', '1', 'CD'], typeOfLine = <class 'list'>, lenOfLine = 4
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    写csv文件
    # 导出数据库所有记录的标准模版
    import pymongo
    import csv

    # 初始化数据库
    mongo_url = "127.0.0.1:27017"
    DATABASE = "databaseName"
    TABLE = "tableName"

    client = pymongo.MongoClient(mongo_url)
    db_des = client[DATABASE]
    db_des_table = db_des[TABLE]

    # 将数据写入到CSV文件中
    # 如果直接从mongod booster导出, 一旦有部分出现字段缺失,那么会出现结果错位的问题

    # newline='' 的作用是防止结果数据中出现空行,专属于python3
    with open(f"{DATABASE}_{TABLE}.csv", "w", newline='') as csvfileWriter:
    writer = csv.writer(csvfileWriter)
    # 先写列名
    # 写第一行,字段名
    fieldList = [
    "_id",
    "itemType",
    "field_1",
    "field_2",
    "field_3",
    ]
    writer.writerow(fieldList)

    allRecordRes = db_des_table.find()
    # 写入多行数据
    for record in allRecordRes:
    print(f"record = {record}")
    recordValueLst = []
    for field in fieldList:
    if field not in record:
    recordValueLst.append("None")
    else:
    recordValueLst.append(record[field])
    try:
    writer.writerow(recordValueLst)
    except Exception as e:
    print(f"write csv exception. e = {e}")
    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
    6.2. 可能出现的问题以及解决方案

    6.2.1. 写csv文件编码问题

    参考文章:Python UnicodeEncodeError: ‘gbk’ codec can’t encode character 解决方法 :
    http://www.jb51.net/article/64816.htm

    重要点:目标文件的编码是导致标题所指问题的罪魁祸首。如果我们打开一个文件,在windows下面,新文件的默认编码是gbk,这样的话,python解释器会用gbk编码去解析我们的网络数据流txt,然而txt此时已经是decode过的unicode编码,这样的话就会导致解析不了,出现上述问题。 解决的办法就是,改变目标文件的编码。
    解决方案:
    ###### 确实最推荐的做法是在open文件时,指定编码格式:
    with open(f"{DATABASE}_{TABLE}.csv", "w", newline='', encoding='utf-8') as csvfileWriter:
    # 就像我们在windows环境下,写csv文件时,默认编码是'gbk',而从网上获取的数据大部分是'utf-8',这就可能出现某些编码不兼容的问题。比如:write csv exception. e = 'gbk' codec can't encode character 'xae' in position 80: illegal multibyte sequence
    1
    2
    3
    6.2.2. 写csv文件出现空白行(存在一行间一行)

    python2.x 版本
    描述及解决方案,请参考:https://www.cnblogs.com/China-YangGISboy/p/7339118.html
    # 为了解决这个问题,查了下资料,发现这是和打开方式有关,将打开的方法改为wb,就不存在这个问题了,也就是
    在read/write csv 文件是要以binary的方式进行。
    with open('result.csv','wb') as cf:
    writer = csv.writer(cf)
    writer.writerow(['shader','file'])
    for key , value in result.items():
    writer.writerow([key,value])
    1
    2
    3
    4
    5
    6
    7
    python2.x要用‘wb’模式写入的真正原因:
    python2.x中写入CSV时,CSV文件的创建必须加上‘b’参数,即open('result.csv','wb'),不然会出现隔行的现象。原因是:python正常写入文件的时候,每行的结束默认添加'n’,即0x0D,而 writerow 命令的结束会再增加一个0x0D0A,因此对于windows系统来说,就是两行,而采用’ b'参数,用二进制进行文件写入,系统默认是不添加0x0D的

    而且在python2.x中,str和bytes是存在很多隐性转换的,所以虽然CSV是文本文件,也是可以正常写入。
    1
    2
    3
    4
    python3 版本
    在python3中,str和bytes有了清晰的划分,也没有任何隐性的转换,csv 是文本格式的文件,不支持二进制的写入,所以不要用二进制模式打开文件,数据也不必转成bytes。
    描述及解决方案,请参考:https://segmentfault.com/q/1010000006841656?_ea=1148776
    # 解决方案就是 newline 配置成空即可
    with open('result.csv', 'w', newline='') as csvfile:
    1
    2
    总结一下:出现空白行的根本原因是Python版本问题,解决方案上python2.x中要求用‘wb’,python3.x中要求用 ‘w’ 和newline参数。
    拓展:关于python3中bytes和string之间的互相转换:http://www.jb51.net/article/105064.htm
    ---------------------
    作者:Kosmoo
    来源:CSDN
    原文:https://blog.csdn.net/zwq912318834/article/details/77689568
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    javaweb学习总结(二十四)——jsp传统标签开发
    javaweb学习总结(二十三)——jsp自定义标签开发入门
    javaweb学习总结(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册
    javaweb学习总结(二十一)——JavaWeb的两种开发模式
    javaweb学习总结(二十)——JavaBean总结
    javaweb学习总结(十九)——JSP标签
    javaweb学习总结(十八)——JSP属性范围
    JavaWeb学习总结(十七)——JSP中的九个内置对象
    javaweb学习总结(十六)——JSP指令
    javaweb学习总结(十五)——JSP基础语法
  • 原文地址:https://www.cnblogs.com/williamjie/p/10173407.html
Copyright © 2011-2022 走看看