데이터 베이스 MySQL 튜토리얼 嵌入式数据库比较(SQLite Birkeley DB Firebird)

嵌入式数据库比较(SQLite Birkeley DB Firebird)

Jun 07, 2016 pm 03:31 PM
sqlite 임베디드 데이터 베이스 비교하다

嵌入式数据库与非嵌入式数据库的差别,在于运行模式的差别。并不是运行在嵌入式手持设备上的数据库就是嵌入式数据库,那种数据库我们通常称做嵌入式移动数据库。理论上讲,嵌入式设备一样可以运行网络数据库的服务端程序。 嵌入式数据库是指运行在本机上、不

嵌入式数据库与非嵌入式数据库的差别,在于运行模式的差别。并不是运行在嵌入式手持设备上的数据库就是嵌入式数据库,那种数据库我们通常称做嵌入式移动数据库。理论上讲,嵌入式设备一样可以运行网络数据库的服务端程序。
      嵌入式数据库是指运行在本机上、不用启动服务端的轻型数据库,它与应用程序紧密集成,被应用程序所启动,并伴随应用程序的退出而终止。
      从这个意义上讲,似乎所有单机数据库都可以算嵌入式数据库,比如Access,Paradox,DBF等等,因为它们都不用启动数据库服务器即可使用。然 而,我们通常不将上述数据库归入嵌入式数据库,而只将它们归入“桌面数据库”,甚至“文件型数据库”,因为这些数据库的完备性、存储容量及性能方面存在较 大的缺陷。
      嵌入式数据库支持的数据都是TB文件级别,更由于嵌入式数据库具备高性能的特点,可以预测,单机数据库的未来将是嵌入式数据库的天下。

嵌入式数据库三雄
     目前,嵌入式数据库市场主要由三个产品分割:SQLite,Birkeley DB,Firebird嵌入服务器版,巧的是,这三个数据库产品都是开源软件。

SQLite    主页:http://www.sqlite.org

SQLite诞生于2000年5月,这几年增长势头迅猛无比,目前版本是3.3.8。

SQLite的特点如下:

1、无需安装配置,应用程序只需携带一个动态链接库。

2、非常小巧,For Windows 3.3.8版本的DLL文件才374KB。

3、ACID事务支持,ACID即原子性、一致性、隔离性、和持久性(Atomic、Consistent、Isolated、和 Durable)。

4、数据库文件可以在不同字节顺序的机器间自由的共享,比如可以直接从Windows移植到Linux或MAC。

5、支持数据库大小至2TB。


Berkeley DB    主页:http://www.oracle.com/database/berkeley-db/index.html

Berkeley DB是由美国Sleepycat Software公司开发的一套开放源码的嵌入式数据库的程序库,它于1991年发布,号称“为应用程序开发者提供工业级强度的数据库服务”,可谓是老牌悍将。Sleepycat现已被甲骨文(ORACLE)公司收购。

Berkeley DB的特点如下:

1、嵌入式,无需安装配置。
2、为多种编程语言提供了API接口,其中包括C、C++、Java、Perl、Tcl、Python和PHP等等。
3、轻便灵活。它可以运行于几乎所有的UNIX和Linux系统及其变种系统、Windows操作系统以及多种嵌入式实时操作系统之下。
4、可伸缩。它的Database library才几百KB大小,但它能够管理规模高达256TB的数据库。它支持高并发度,成千上万个用户可同时操纵同一个数据库。


Firebird 嵌入服务器版(Embedded Server)   主页:http://www.firebirdsql.org
从Interbase开源衍生出的Firebird,充满了勃勃生机。虽然它的体积比前辈Interbase缩小了几十倍,但功能并无阉割。为了体现Firebird短小精悍的特色,开发小组在增加了超级服务器版本之后,又增加了嵌入版本,最新版本为2.0。

Firebird的嵌入版有如下特色:

1、数据库文件与Firebird网络版本完全兼容,差别仅在于连接方式不同,可以实现零成本迁移。
2、数据库文件仅受操作系统的限制,且支持将一个数据库分割成不同文件,突破了操作系统最大文件的限制,提高了IO吞吐量。
3、完全支持SQL92标准,支持大部分SQL-99标准功能。
4、丰富的开发工具支持,绝大部分基于Interbase的组件,可以直接使用于Firebird。
5、支持事务、存储过程、触发器等关系数据库的所有特性。
6、可自己编写扩展函数(UDF)。


嵌入式数据库特性对比

产品名称 SQLite   Berkeley DB   Firebird嵌入服务器版

当前版本: 3.3.8、4.5.20、2.0
速度:最快、快、快
稳定性:好、好、好
数据库容量:2TB、256TB、64TB
SQL支持:大部份SQL- 92、不支持、完全SQL-92与大部份SQL-99
Win32平台下最小体积:374KB、840KB、3.68MB
数据操纵:SQL、仅应用程序接口、SQL

开发接口:C, C++, PHP, Java, Delphi, Python .net(有些是第三方厂商开发的)

从 以上对比中,我们可以看到,最短小精悍的是SQLite,它的性能也是最高的,Berkeley DB比较特殊,因为它不是用SQL语言来操纵数据的,Firebird嵌入版的体积对比之下显得稍大了些,但它对关系数据库特性的支持是最好的,如果要考 虑到今后或许要将数据库升级成网络版本,就要选Firebird了。


嵌入式数据库开发布署举例
例1、用Delphi开发基于SQLite的单机版应用程序

因为SQLite自带C、C++、Java接口,所以我这里举用Delphi开发的例子。

使用组件:第三方组件ASQLite。

下载地址:http://www.aducom.com/cen/download.php?list.2

选择后边是.D的,就是for Delphi的组件。

下载解压之后,即可开始用Delphi打开dpk组件包编译安装。

如果您的Delphi版本比较高,比如是Delphi 2006,那么就编译asqlite3.dpk与asqlite3pkg.dpk,之后再安装asqlite3pkg.dpk包即可。

然后在Delphi组件面板里可以看得到ASQLite组件,如图:



现在就可以用它开发你的程序了,基于SQLite小巧的原因,特别推荐你在布署远程应用时用它,比如监控啊,木马啊什么的。

OK,为了使我们的例子更详细,下面讲讲怎么用ASQLite组件创建、连接数据库,并建立一个数据表。

新 建一个窗体,放一个TASQLite3DB,命名为DB1,将它的Database(数据库)设为test.sqb,放一个 TASQLite3Query,命名为Query1,将它的Connection指向DB1,然后放入两个按钮,第一个按钮的作用是创建或连接数据库,第 二个按钮的作用是建立数据表。

如下图:


“连接”按钮事件的代码如下:

DB1.Open;

执行完这条语句之后,如果应用程序当前目录下不存在test.sqb文件,则自动创建并连接它。

“建表”按钮事件的代码如下:

   Query1.StartTransaction;

   try

Query1.Close;

Query1.SQL.Text:='create table student(sname vchar(30),age integer)';

Query1.ExecSQL;

Query1.Commit;

   except

Query1.RollBack;

   end;

执行完这些语句之后,在Test.sqb数据库里将创建数据表student。

现在,请将您下载的SQLite的动态链接库sqlite3.dll文件复制到应用程序目录下。

之后,可正常运行。

布署时,也应将sqlite3.dll文件一起打包。

例2、使用C++语言开发基于Firebird嵌入版的应用程序。

由于Firebird衍生于Interbase,所以Delphi对它的支持最好,IBX,DBX,Fibplus等都可以直接使用它,只要注意将接口文件改为fbembed.dll即可。在此不再多言。

对于C++这种最通用的语言,我们有一个更好的组件可以选择:IBPP。

IBPP主页:http://www.ibpp.org/

IBPP是用C++封装的Firebird接口,最新版本2.5.2.2。

只要在C++里引用all_in_one.cpp文件,就可以使用它的功能。

可 以用IBPP:database类连接数据库,用IBPP::Transaction类控制事务,IBPP::Statement类可以获取数据集。下面 展示一段代码,功能为:先连接d:\demo.fdb文件,然后从student表里选择所有记录,遍历所有记录之后,显示最后一条记录的sn与 sname字段。为了使演示更直观,省去了异常处理。

示例代码:

#define IBPP_WINDOWS   //运行于Windows平台的预先声明

#include "ibpp/all_in_one.cpp"

……

IBPP:database db1;

db1=IBPP:databaseFactory("","d:/demo.fdb","sysdba","masterkey");

db1->Connect();

IBPP::Transaction tr1=IBPP::TransactionFactory(db1,IBPP::amWrite,

IBPP::ilConcurrency,IBPP::lrWait, IBPP::tfNoAutoUndo);

tr1->Start();

IBPP::Statement st1=IBPP::StatementFactory(db1,tr1);

st1->Prepare("select * from student");

st1->Execute();

std::string sn,sname;

st1->Fetch();

st1->Get(1,sn);

st1->Get(2,sname);

tr1->Commit();

Label1->Caption=sn.c_str();

Label2->Caption=sname.c_str();

最后2行语句为显示结果的,不同开发平台应该使用不同的方法演示,请勿直接复制源代码。

布署时,还应该带上如下文件:fbembed.dll,ib_util.dll,icudt30.dll,icuin30.dll,icuuc30.dll,为了更通用,还可以带上VC++ 7.1的运行库msvcp71.dll,msvcr71.dll两个文件。



工程实例
http://hi.baidu.com/cokee/blog/item/7ac5bc013a5cb50a1d9583da.html

2010年05月25日 星期二 13:07

因 为项目中要用到嵌入式数据库,现在网上找了一些资料了解了一下嵌入式数据。在http://blog.csdn.net/love_study /archive/2009/04/07/4053644.aspx中讲得蛮清楚。综合各自特点,最后决定选用SQLite。借鉴http: //www.codeproject.com/KB/database/CppSQLite.aspx,自己写了一个小例子,实现了创建数据库,创建表, 添加记录,删除记录,查询记录,修改记录等功能。
#include "sqlite3.h"
#include
#include
#pragma comment(lib, "sqlite3.lib")

const char* g_caDBName = "C:\\sqliteDB.db";
const char* g_caTabeName = "TB_Test";
sqlite3* g_pDB = NULL;
FILE* g_pFile = stdout;
char* g_caErrorMsg = NULL;

#define GROUP_NUM 100
char g_groupSqlStr[GROUP_NUM][256] = {0};

int Test1();   //测试创建一个新数据库,并向此数据库添加表,对表中的记录进行增,删,改,查。

int main(int argc, char* argv[])
{
Test1();
system("pause");
return 0;
}

int Test1()
{
int ret = SQLITE_ERROR;
//创建数据库
remove(g_caDBName);
ret = sqlite3_open(g_caDBName, &g_pDB);
if (!g_pDB)
{
fprintf(g_pFile, "创建数据库失败: %d\n", ret);;
return -1;
}
else
{
fprintf(g_pFile, "创建数据库: %s 成功\n", g_caDBName);
}

char sqlStr[512];

//创建表
sprintf(sqlStr, "Create Table %s(fd_id int, fd_name char(20))", g_caTabeName);
ret = sqlite3_exec(g_pDB, sqlStr, NULL, NULL, &g_caErrorMsg);
if (ret != SQLITE_OK)
{
fprintf(g_pFile, "创建表失败:%d\n", ret);
return -1;
}
else
{
fprintf(g_pFile, "创建表: %s 成功%\n", g_caTabeName);
}

//插入数据
int i = 0;
for(; i {
sprintf(g_groupSqlStr[i], "Insert into %s values(%d, '%d')", g_caTabeName, i, i);
}
clock_t startTime = clock();
sqlite3_exec(g_pDB, "Begin Transaction;", NULL, NULL, &g_caErrorMsg);
for(i = 0; i {
sqlite3_exec(g_pDB, g_groupSqlStr[i], NULL, NULL, &g_caErrorMsg);
}
sqlite3_exec(g_pDB, "Commit Trans;", NULL, NULL, &g_caErrorMsg);
clock_t endTime = clock();
fprintf(g_pFile, "插入%d条数据,花费%d毫秒\n", GROUP_NUM, endTime - startTime);

//查询数据条数
const char* pTail;
sqlite3_stmt* stmt;
sprintf(sqlStr, "select count(*) from %s", g_caTabeName);
ret = sqlite3_prepare(g_pDB, sqlStr, strlen(sqlStr), &stmt, &pTail);
if (ret != SQLITE_OK)
{
fprintf(g_pFile, "查询记录条数失败");
return -1;
}
else
{
int recordCount = 0;
ret = sqlite3_step(stmt);
if (ret != SQLITE_ROW)
{
fprintf(g_pFile, "查询记录失败: %d", ret);
return -1;
}

recordCount = sqlite3_column_int(stmt, 0);

fprintf(g_pFile, "共有%d条记录\n", recordCount);
}

sqlite3_finalize(stmt);

//列出表中所有的数据
fprintf(g_pFile, "%10s%10s\n", "ID", "Name");
sprintf(sqlStr, "select FD_ID, FD_Name from %s", g_caTabeName);
ret = sqlite3_prepare(g_pDB, sqlStr, strlen(sqlStr), &stmt, &pTail);
if (ret != SQLITE_OK)
{
fprintf(g_pFile, "查询记录条数失败");
return -1;
}
else
{
int id;
char name[256];

do 
{
ret = sqlite3_step(stmt);
if (ret == SQLITE_DONE)
{
break;
}
else if(ret != SQLITE_ROW)
{    
fprintf(g_pFile, "查询记录失败: %d", ret);
return -1;
}
id = sqlite3_column_int(stmt, 0);
strcpy(name, (const char*)sqlite3_column_text(stmt, 1));
fprintf(g_pFile, "%10d%10s\n", id, name);
} while(true);
}
sqlite3_finalize(stmt);

//修改数据
startTime = clock_t();
sprintf(sqlStr, "Update TB_Test Set FD_Name = 'New Name' where FD_ID > 40 and FD_ID endTime = clock_t();
ret = sqlite3_exec(g_pDB, sqlStr, NULL, NULL, &g_caErrorMsg);
if (ret != SQLITE_OK)
{
fprintf(g_pFile, "修改失败:%d\n", ret);
return -1;
}
int rowsEffect = sqlite3_changes(g_pDB);
fprintf(g_pFile, "修改了%d条数据, 消耗%d毫秒\n", rowsEffect, endTime - startTime);

//删除数据
startTime = clock_t();
sprintf(sqlStr, "Delete from TB_Test where FD_ID > 40 and FD_ID endTime = clock_t();
ret = sqlite3_exec(g_pDB, sqlStr, NULL, NULL, &g_caErrorMsg);
if (ret != SQLITE_OK)
{
fprintf(g_pFile, "删除失败:%d\n", ret);
return -1;
}
rowsEffect = sqlite3_changes(g_pDB);
fprintf(g_pFile, "删除了%d条数据, 消耗%d毫秒\n", rowsEffect, endTime - startTime);


sqlite3_close(g_pDB);

return 0;
}

(#)


본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 채팅 명령 및 사용 방법
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Hibernate는 어떻게 다형성 매핑을 구현합니까? Hibernate는 어떻게 다형성 매핑을 구현합니까? Apr 17, 2024 pm 12:09 PM

Hibernate 다형성 매핑은 상속된 클래스를 데이터베이스에 매핑할 수 있으며 다음 매핑 유형을 제공합니다. Join-subclass: 상위 클래스의 모든 열을 포함하여 하위 클래스에 대한 별도의 테이블을 생성합니다. 클래스별 테이블: 하위 클래스별 열만 포함하는 하위 클래스에 대한 별도의 테이블을 만듭니다. Union-subclass: Joined-subclass와 유사하지만 상위 클래스 테이블이 모든 하위 클래스 열을 통합합니다.

iOS 18에는 손실되거나 손상된 사진을 검색할 수 있는 새로운 '복구된' 앨범 기능이 추가되었습니다. iOS 18에는 손실되거나 손상된 사진을 검색할 수 있는 새로운 '복구된' 앨범 기능이 추가되었습니다. Jul 18, 2024 am 05:48 AM

Apple의 최신 iOS18, iPadOS18 및 macOS Sequoia 시스템 릴리스에는 사진 애플리케이션에 중요한 기능이 추가되었습니다. 이 기능은 사용자가 다양한 이유로 손실되거나 손상된 사진과 비디오를 쉽게 복구할 수 있도록 설계되었습니다. 새로운 기능에는 사진 앱의 도구 섹션에 '복구됨'이라는 앨범이 도입되었습니다. 이 앨범은 사용자가 기기에 사진 라이브러리에 포함되지 않은 사진이나 비디오를 가지고 있을 때 자동으로 나타납니다. "복구된" 앨범의 출현은 데이터베이스 손상으로 인해 손실된 사진과 비디오, 사진 라이브러리에 올바르게 저장되지 않은 카메라 응용 프로그램 또는 사진 라이브러리를 관리하는 타사 응용 프로그램에 대한 솔루션을 제공합니다. 사용자는 몇 가지 간단한 단계만 거치면 됩니다.

SPARKLE, Intel Arc 임베디드 그래픽 카드 출시, 최대 5년 지원 제공 SPARKLE, Intel Arc 임베디드 그래픽 카드 출시, 최대 5년 지원 제공 Apr 22, 2024 am 11:52 AM

4월 22일 이 사이트의 뉴스에 따르면 SPARKLE Technology는 A310E, A380E의 PCIe 버전, A350E 및 A370E 그래픽 카드의 MXM 버전을 포함한 Intel Arc 임베디드 그래픽 카드 출시를 발표했으며 최대 5년 동안 서비스를 지원하겠다고 약속했습니다. . SPARKLE Intel ArcA380E 그래픽 카드 IA30GC-TN4E 이 그래픽 카드는 75W의 TBP(이 사이트 참고: TotalBoardPower)를 가지며 듀얼 슬롯 단일 팬 설계, 6GBGDDR6 비디오 메모리를 사용하고 1개의 HDMI 및 3개의 DisplayPort 인터페이스를 제공하며 5개의 년 지원 서비스. SPARKLE Intel Ruixuan ArcA380E 절반 높이 그래픽 카드 IA30GBL-TN4E 이 절반 높이 그래픽 카드 T

PHP에서 MySQLi를 사용하여 데이터베이스 연결을 설정하는 방법에 대한 자세한 튜토리얼 PHP에서 MySQLi를 사용하여 데이터베이스 연결을 설정하는 방법에 대한 자세한 튜토리얼 Jun 04, 2024 pm 01:42 PM

MySQLi를 사용하여 PHP에서 데이터베이스 연결을 설정하는 방법: MySQLi 확장 포함(require_once) 연결 함수 생성(functionconnect_to_db) 연결 함수 호출($conn=connect_to_db()) 쿼리 실행($result=$conn->query()) 닫기 연결( $conn->close())

HTML이 데이터베이스를 읽는 방법에 대한 심층 분석 HTML이 데이터베이스를 읽는 방법에 대한 심층 분석 Apr 09, 2024 pm 12:36 PM

HTML은 데이터베이스를 직접 읽을 수 없지만 JavaScript 및 AJAX를 통해 읽을 수 있습니다. 단계에는 데이터베이스 연결 설정, 쿼리 보내기, 응답 처리 및 페이지 업데이트가 포함됩니다. 이 기사에서는 JavaScript, AJAX 및 PHP를 사용하여 MySQL 데이터베이스에서 데이터를 읽는 실제 예제를 제공하고 쿼리 결과를 HTML 페이지에 동적으로 표시하는 방법을 보여줍니다. 이 예제에서는 XMLHttpRequest를 사용하여 데이터베이스 연결을 설정하고 쿼리를 보내고 응답을 처리함으로써 페이지 요소에 데이터를 채우고 데이터베이스를 읽는 HTML 기능을 실현합니다.

PHP에서 데이터베이스 연결 오류를 처리하는 방법 PHP에서 데이터베이스 연결 오류를 처리하는 방법 Jun 05, 2024 pm 02:16 PM

PHP에서 데이터베이스 연결 오류를 처리하려면 다음 단계를 사용할 수 있습니다. mysqli_connect_errno()를 사용하여 오류 코드를 얻습니다. 오류 메시지를 얻으려면 mysqli_connect_error()를 사용하십시오. 이러한 오류 메시지를 캡처하고 기록하면 데이터베이스 연결 문제를 쉽게 식별하고 해결할 수 있어 애플리케이션이 원활하게 실행될 수 있습니다.

Arduino는 임베디드 시스템 Mbed 지원 종료의 영향에 대응합니다. 대안이 발견되었으며 첫 번째 베타 버전이 연말 이전에 출시될 예정입니다. Arduino는 임베디드 시스템 Mbed 지원 종료의 영향에 대응합니다. 대안이 발견되었으며 첫 번째 베타 버전이 연말 이전에 출시될 예정입니다. Jul 26, 2024 am 11:32 AM

26일 본 홈페이지 소식에 따르면, Arm은 7월 9일 공지를 통해 오픈소스 임베디드 운영체제인 MbedOS에 대한 지원을 2026년 7월 종료하고, 더 이상 유지하지 않겠다고 밝혔다. Mbed 웹사이트는 보관되며 더 이상 온라인 도구를 통해 프로젝트를 구축할 수 없습니다. 이 소식은 임베디드 개발 커뮤니티에서 광범위한 논의를 불러일으켰고 micro:bit, Arduino 및 Raspberry Pi와 같은 Arm 지원 프로젝트에 영향을 미쳤습니다. 아두이노 회사는 7월 24일 블로그 게시물을 통해 몇 년 전부터 대체 솔루션을 찾기 시작했다고 밝히며 2023년에 Zephyr 프로젝트에 참여해 프로젝트의 실버 멤버가 되었고 ZephyrOS에서 좋은 대안을 찾았다고 밝혔습니다. 아르두

Aetina, Arc A380E 내장형 그래픽 카드 출시: 단일 슬롯, 절반 높이 디자인, 50W 전력 소비 Aetina, Arc A380E 내장형 그래픽 카드 출시: 단일 슬롯, 절반 높이 디자인, 50W 전력 소비 Apr 26, 2024 am 08:04 AM

4월 25일 이 사이트의 소식에 따르면, AIoT 장비 제조업체인 Aetina는 오늘 단일 슬롯, 절반 높이 디자인의 Intel ArcA380E 임베디드 그래픽 카드 모델 IA380E-QUFL을 출시했으며 5년 장기 보증을 제공합니다. 제품 공급 약속. 이 그래픽 카드는 Intel ArcA380E 코어, 8개의 Xe 코어 및 128개의 Intel XMX 코어를 사용하며 기본 주파수는 2000MHz, 가속 주파수는 2250MHz이며 4.096TFLOPS의 컴퓨팅 성능을 갖추고 있으며 96비트 폭을 지원합니다. 6GBGDDR6 비디오 메모리는 엣지 애플리케이션 및 인공 지능 추론에 적합합니다. 이 그래픽 카드는 절반 높이 단일 슬롯 설계를 채택하고 50W만 소비하며 외부 전원 없이 PCIe 슬롯에서 직접 전력을 끌어올 수 있습니다.

See all articles