LINQ๋ ๊ฐ๋ ฅํ ์ ๋ฐ์ดํฐ ์กฐ์ ์ธ์ด๋ก .NET์ ๋์
ํ์ต๋๋ค. LINQ to SQL์ ์ฌ์ฉํ๋ฉด Entity Framework ๋ฑ์ ์ฌ์ฉํ์ฌ DBMS์ ๋งค์ฐ ํธ๋ฆฌํ๊ฒ ํต์ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ฅผ ์์ฃผ ์ฌ์ฉํ๋ฉด ๊ฐ๋ฐ์๋ ์ฟผ๋ฆฌ ๊ฐ๋ฅํ ๊ณต๊ธ์(์: Entity Framework)๊ฐ ์ด๋ค ์ข
๋ฅ์ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋์ง ํ์ธํ๋ ๊ฒ์ ์์ด๋ฒ๋ฆฝ๋๋ค.
์์ ๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๊ฐ์ง ์ฃผ์ ์ฌํญ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ด๋ ๊ฒ ํ๋ ค๋ฉด SQL Server์์ ํ
์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ง๋ค๊ณ ๋ค์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๊ทธ ์์ ๋ ๊ฐ์ ํ
์ด๋ธ์ ๋ง๋ญ๋๋ค.
ํ ์ด๋ธ ์์ฑ
USE [TEST]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Ref](
[ID] [int] NOT NULL,
[ID2] [int] NOT NULL,
[Name] [nvarchar](255) NOT NULL,
[InsertUTCDate] [datetime] NOT NULL,
CONSTRAINT [PK_Ref] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Ref] ADD CONSTRAINT [DF_Ref_InsertUTCDate] DEFAULT (getutcdate()) FOR [InsertUTCDate]
GO
USE [TEST]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Customer](
[ID] [int] NOT NULL,
[Name] [nvarchar](255) NOT NULL,
[Ref_ID] [int] NOT NULL,
[InsertUTCDate] [datetime] NOT NULL,
[Ref_ID2] [int] NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Customer] ADD CONSTRAINT [DF_Customer_Ref_ID] DEFAULT ((0)) FOR [Ref_ID]
GO
ALTER TABLE [dbo].[Customer] ADD CONSTRAINT [DF_Customer_InsertUTCDate] DEFAULT (getutcdate()) FOR [InsertUTCDate]
GO
์ด์ ๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ฌ Ref ํ ์ด๋ธ์ ์ฑ์๋ณด๊ฒ ์ต๋๋ค.
Ref ํ ์ด๋ธ ์ฑ์ฐ๊ธฐ
USE [TEST]
GO
DECLARE @ind INT=1;
WHILE(@ind<1200000)
BEGIN
INSERT INTO [dbo].[Ref]
([ID]
,[ID2]
,[Name])
SELECT
@ind
,@ind
,CAST(@ind AS NVARCHAR(255));
SET @ind=@ind+1;
END
GO
๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ง์ฐฌ๊ฐ์ง๋ก Customer ํ ์ด๋ธ์ ์ฑ์ ๋ณด๊ฒ ์ต๋๋ค.
๊ณ ๊ฐ ํ ์ด๋ธ ์ฑ์ฐ๊ธฐ
USE [TEST]
GO
DECLARE @ind INT=1;
DECLARE @ind_ref INT=1;
WHILE(@ind<=12000000)
BEGIN
IF(@ind%3=0) SET @ind_ref=1;
ELSE IF (@ind%5=0) SET @ind_ref=2;
ELSE IF (@ind%7=0) SET @ind_ref=3;
ELSE IF (@ind%11=0) SET @ind_ref=4;
ELSE IF (@ind%13=0) SET @ind_ref=5;
ELSE IF (@ind%17=0) SET @ind_ref=6;
ELSE IF (@ind%19=0) SET @ind_ref=7;
ELSE IF (@ind%23=0) SET @ind_ref=8;
ELSE IF (@ind%29=0) SET @ind_ref=9;
ELSE IF (@ind%31=0) SET @ind_ref=10;
ELSE IF (@ind%37=0) SET @ind_ref=11;
ELSE SET @ind_ref=@ind%1190000;
INSERT INTO [dbo].[Customer]
([ID]
,[Name]
,[Ref_ID]
,[Ref_ID2])
SELECT
@ind,
CAST(@ind AS NVARCHAR(255)),
@ind_ref,
@ind_ref;
SET @ind=@ind+1;
END
GO
๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ ๋ ๊ฐ์ ํ ์ด๋ธ์ ์ป์๋๋ฐ, ๊ทธ ์ค ํ๋์๋ 1๋ง ๊ฐ๊ฐ ๋๋ ๋ฐ์ดํฐ ํ์ด ์๊ณ ๋ค๋ฅธ ํ๋์๋ 10์ฒ๋ง ๊ฐ๊ฐ ๋๋ ๋ฐ์ดํฐ ํ์ด ์์ต๋๋ค.
์ด์ Visual Studio์์ ํ ์คํธ Visual C# ์ฝ์ ์ฑ(.NET Framework) ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
๋ค์์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํธ ์์ฉํ Entity Framework์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
์ด๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด ํ๋ก์ ํธ๋ฅผ ๋ง์ฐ์ค ์ค๋ฅธ์ชฝ ๋ฒํผ์ผ๋ก ํด๋ฆญํ๊ณ ์ํฉ์ ๋ง๋ ๋ฉ๋ด์์ NuGet ํจํค์ง ๊ด๋ฆฌ๋ฅผ ์ ํํฉ๋๋ค.
๊ทธ๋ฐ ๋ค์ ๋ํ๋๋ NuGet ํจํค์ง ๊ด๋ฆฌ ์ฐฝ์ ๊ฒ์ ์ฐฝ์ "Entity Framework"๋ผ๋ ๋จ์ด๋ฅผ ์
๋ ฅํ๊ณ Entity Framework ํจํค์ง๋ฅผ ์ ํํ์ฌ ์ค์นํฉ๋๋ค.
๋ค์์ผ๋ก App.config ํ์ผ์์ configSections ์์๋ฅผ ๋ซ์ ํ ๋ค์ ๋ธ๋ก์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
<connectionStrings>
<add name="DBConnection" connectionString="data source=ะะะฏ_ะญะะะะะะะฏะ ะ_MSSQL;Initial Catalog=TEST;Integrated Security=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
ConnectionString์ ์ฐ๊ฒฐ ๋ฌธ์์ด์ ์ ๋ ฅํด์ผ ํฉ๋๋ค.
์ด์ ๋ณ๋์ ํ์ผ์ 3๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
- IBaseEntityID ์ธํฐํ์ด์ค ๊ตฌํ
namespace TestLINQ { public interface IBaseEntityID { int ID { get; set; } } }
- IBaseEntityName ์ธํฐํ์ด์ค ๊ตฌํ
namespace TestLINQ { public interface IBaseEntityName { string Name { get; set; } } }
- IBaseNameInsertUTCDate ์ธํฐํ์ด์ค ๊ตฌํ
namespace TestLINQ { public interface IBaseNameInsertUTCDate { DateTime InsertUTCDate { get; set; } } }
๊ทธ๋ฆฌ๊ณ ๋ณ๋์ ํ์ผ์์ ๊ณตํต ํ๋๋ฅผ ํฌํจํ๋ ๋ ์ํฐํฐ์ ๋ํ ๊ธฐ๋ณธ ํด๋์ค BaseEntity๋ฅผ ์์ฑํฉ๋๋ค.
๊ธฐ๋ณธ ํด๋์ค BaseEntity ๊ตฌํ
namespace TestLINQ
{
public class BaseEntity : IBaseEntityID, IBaseEntityName, IBaseNameInsertUTCDate
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime InsertUTCDate { get; set; }
}
}
๋ค์์ผ๋ก ๋ณ๋์ ํ์ผ์ ๋ ์ํฐํฐ๋ฅผ ๋ง๋ญ๋๋ค.
- Ref ํด๋์ค ๊ตฌํ
using System.ComponentModel.DataAnnotations.Schema; namespace TestLINQ { [Table("Ref")] public class Ref : BaseEntity { public int ID2 { get; set; } } }
- Customer ํด๋์ค ๊ตฌํ
using System.ComponentModel.DataAnnotations.Schema; namespace TestLINQ { [Table("Customer")] public class Customer: BaseEntity { public int Ref_ID { get; set; } public int Ref_ID2 { get; set; } } }
์ด์ ๋ณ๋์ ํ์ผ์ UserContext ์ปจํ ์คํธ๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
UserContex ํด๋์ค ๊ตฌํ
using System.Data.Entity;
namespace TestLINQ
{
public class UserContext : DbContext
{
public UserContext()
: base("DbConnection")
{
Database.SetInitializer<UserContext>(null);
}
public DbSet<Customer> Customer { get; set; }
public DbSet<Ref> Ref { get; set; }
}
}
์ฐ๋ฆฌ๋ MS SQL Server์ฉ EF๋ฅผ ํตํด LINQ to SQL๋ก ์ต์ ํ ํ ์คํธ๋ฅผ ์ํํ๊ธฐ ์ํ ๊ธฐ์ฑ ์๋ฃจ์ ์ ๋ฐ์์ต๋๋ค.
์ด์ Program.cs ํ์ผ์ ๋ค์ ์ฝ๋๋ฅผ ์
๋ ฅํฉ๋๋ค.
Program.cs ํ์ผ
using System;
using System.Collections.Generic;
using System.Linq;
namespace TestLINQ
{
class Program
{
static void Main(string[] args)
{
using (UserContext db = new UserContext())
{
var dblog = new List<string>();
db.Database.Log = dblog.Add;
var query = from e1 in db.Customer
from e2 in db.Ref
where (e1.Ref_ID == e2.ID)
&& (e1.Ref_ID2 == e2.ID2)
select new { Data1 = e1.Name, Data2 = e2.Name };
var result = query.Take(1000).ToList();
Console.WriteLine(dblog[1]);
Console.ReadKey();
}
}
}
}
๋ค์์ผ๋ก ํ๋ก์ ํธ๋ฅผ ์์ํ๊ฒ ์ต๋๋ค.
์์ ์ด ๋๋๋ฉด ์ฝ์์ ๋ค์์ด ํ์๋ฉ๋๋ค.
์์ฑ๋ SQL ์ฟผ๋ฆฌ
SELECT TOP (1000)
[Extent1].[Ref_ID] AS [Ref_ID],
[Extent1].[Name] AS [Name],
[Extent2].[Name] AS [Name1]
FROM [dbo].[Customer] AS [Extent1]
INNER JOIN [dbo].[Ref] AS [Extent2] ON ([Extent1].[Ref_ID] = [Extent2].[ID]) AND ([Extent1].[Ref_ID2] = [Extent2].[ID2])
์ฆ, ์ผ๋ฐ์ ์ผ๋ก LINQ ์ฟผ๋ฆฌ๋ MS SQL Server DBMS์ ๋ํ SQL ์ฟผ๋ฆฌ๋ฅผ ๋งค์ฐ ์ ์์ฑํ์ต๋๋ค.
์ด์ LINQ ์ฟผ๋ฆฌ์์ AND ์กฐ๊ฑด์ OR๋ก ๋ณ๊ฒฝํด ๋ณด๊ฒ ์ต๋๋ค.
LINQ ์ฟผ๋ฆฌ
var query = from e1 in db.Customer
from e2 in db.Ref
where (e1.Ref_ID == e2.ID)
|| (e1.Ref_ID2 == e2.ID2)
select new { Data1 = e1.Name, Data2 = e2.Name };
๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ค์ ์คํํด ๋ณด๊ฒ ์ต๋๋ค.
30์ด๋ฅผ ์ด๊ณผํ๋ ๋ช ๋ น ์คํ ์๊ฐ์ผ๋ก ์ธํด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด์ ์คํ์ด ์ค๋จ๋ฉ๋๋ค.
LINQ์์ ์์ฑ๋ ์ฟผ๋ฆฌ๋ฅผ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
, ๊ทธ๋ฌ๋ฉด ๋ ์ธํธ(ํ
์ด๋ธ)์ ๋ฐ์นด๋ฅดํธ ๊ณฑ์ ํตํด ์ ํ์ด ๋ฐ์ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
์์ฑ๋ SQL ์ฟผ๋ฆฌ
SELECT TOP (1000)
[Extent1].[Ref_ID] AS [Ref_ID],
[Extent1].[Name] AS [Name],
[Extent2].[Name] AS [Name1]
FROM [dbo].[Customer] AS [Extent1]
CROSS JOIN [dbo].[Ref] AS [Extent2]
WHERE [Extent1].[Ref_ID] = [Extent2].[ID] OR [Extent1].[Ref_ID2] = [Extent2].[ID2]
LINQ ์ฟผ๋ฆฌ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ค์ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
์ต์ ํ๋ LINQ ์ฟผ๋ฆฌ
var query = (from e1 in db.Customer
join e2 in db.Ref
on e1.Ref_ID equals e2.ID
select new { Data1 = e1.Name, Data2 = e2.Name }).Union(
from e1 in db.Customer
join e2 in db.Ref
on e1.Ref_ID2 equals e2.ID2
select new { Data1 = e1.Name, Data2 = e2.Name });
๊ทธ๋ฐ ๋ค์ ๋ค์๊ณผ ๊ฐ์ SQL ์ฟผ๋ฆฌ๋ฅผ ์ป์ต๋๋ค.
SQL ์ฟผ๋ฆฌ
SELECT
[Limit1].[C1] AS [C1],
[Limit1].[C2] AS [C2],
[Limit1].[C3] AS [C3]
FROM ( SELECT DISTINCT TOP (1000)
[UnionAll1].[C1] AS [C1],
[UnionAll1].[Name] AS [C2],
[UnionAll1].[Name1] AS [C3]
FROM (SELECT
1 AS [C1],
[Extent1].[Name] AS [Name],
[Extent2].[Name] AS [Name1]
FROM [dbo].[Customer] AS [Extent1]
INNER JOIN [dbo].[Ref] AS [Extent2] ON [Extent1].[Ref_ID] = [Extent2].[ID]
UNION ALL
SELECT
1 AS [C1],
[Extent3].[Name] AS [Name],
[Extent4].[Name] AS [Name1]
FROM [dbo].[Customer] AS [Extent3]
INNER JOIN [dbo].[Ref] AS [Extent4] ON [Extent3].[Ref_ID2] = [Extent4].[ID2]) AS [UnionAll1]
) AS [Limit1]
์์ฝ๊ฒ๋ LINQ ์ฟผ๋ฆฌ์๋ ํ๋์ ์กฐ์ธ ์กฐ๊ฑด๋ง ์์ ์ ์์ผ๋ฏ๋ก ์ฌ๊ธฐ์์๋ ๊ฐ ์กฐ๊ฑด์ ๋ํด ๋ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋๋ฑํ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ ๋ค์ Union์ ํตํด ๊ฒฐํฉํ์ฌ ํ ๊ฐ์ ์ค๋ณต์ ์ ๊ฑฐํ๋ ๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค.
์, ์์ ํ ์ค๋ณต ํ์ด ๋ฐํ๋ ์ ์๋ค๋ ์ ์ ๊ณ ๋ คํ๋ฉด ์ฟผ๋ฆฌ๋ ์ผ๋ฐ์ ์ผ๋ก ๋์ผํ์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ค์ ์ํ์์๋ ์์ ํ ์ค๋ณต ๋ผ์ธ์ด ํ์ํ์ง ์์ผ๋ฉฐ ์ฌ๋๋ค์ ์ด๋ฅผ ์ ๊ฑฐํ๋ ค๊ณ ํฉ๋๋ค.
์ด์ ์ด ๋ ์ฟผ๋ฆฌ์ ์คํ ๊ณํ์ ๋น๊ตํด ๋ณด๊ฒ ์ต๋๋ค.
- CROSS JOIN์ ๊ฒฝ์ฐ ํ๊ท ์คํ ์๊ฐ์ 195์ด์
๋๋ค.
- INNER JOIN-UNION์ ๊ฒฝ์ฐ ํ๊ท ์คํ ์๊ฐ์ 24์ด ๋ฏธ๋ง์
๋๋ค.
๊ฒฐ๊ณผ์์ ๋ณผ ์ ์๋ฏ์ด ์๋ฐฑ๋ง ๊ฐ์ ๋ ์ฝ๋๊ฐ ํฌํจ๋ ๋ ํ ์ด๋ธ์ ๊ฒฝ์ฐ ์ต์ ํ๋ LINQ ์ฟผ๋ฆฌ๊ฐ ์ต์ ํ๋์ง ์์ ์ฟผ๋ฆฌ๋ณด๋ค ๋ช ๋ฐฐ ๋ ๋น ๋ฆ ๋๋ค.
์กฐ๊ฑด์ AND๊ฐ ํฌํจ๋ ์ต์ ์ ๊ฒฝ์ฐ ๋ค์ ํ์์ LINQ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
LINQ ์ฟผ๋ฆฌ
var query = from e1 in db.Customer
from e2 in db.Ref
where (e1.Ref_ID == e2.ID)
&& (e1.Ref_ID2 == e2.ID2)
select new { Data1 = e1.Name, Data2 = e2.Name };
๊ฑฐ์ ํญ์ ์ฌ๋ฐ๋ฅธ SQL ์ฟผ๋ฆฌ๊ฐ ์์ฑ๋๋ฉฐ ํ๊ท ์ ์ผ๋ก ์ฝ 1์ด ๋ด์ ์คํ๋ฉ๋๋ค.
๋ํ ๋ค์๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ ๋์ LINQ to Objects ์กฐ์์ ๊ฒฝ์ฐ:
LINQ ์ฟผ๋ฆฌ(์ฒซ ๋ฒ์งธ ์ต์ )
var query = from e1 in seq1
from e2 in seq2
where (e1.Key1==e2.Key1)
&& (e1.Key2==e2.Key2)
select new { Data1 = e1.Data, Data2 = e2.Data };
๋ค์๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
LINQ ์ฟผ๋ฆฌ(์ฒซ ๋ฒ์งธ ์ต์ )
var query = from e1 in seq1
join e2 in seq2
on new { e1.Key1, e1.Key2 } equals new { e2.Key1, e2.Key2 }
select new { Data1 = e1.Data, Data2 = e2.Data };
์ง์ญ :
๋ ๊ฐ์ ๋ฐฐ์ด ์ ์
Para[] seq1 = new[] { new Para { Key1 = 1, Key2 = 2, Data = "777" }, new Para { Key1 = 2, Key2 = 3, Data = "888" }, new Para { Key1 = 3, Key2 = 4, Data = "999" } };
Para[] seq2 = new[] { new Para { Key1 = 1, Key2 = 2, Data = "777" }, new Para { Key1 = 2, Key2 = 3, Data = "888" }, new Para { Key1 = 3, Key2 = 5, Data = "999" } };
, Para ์ ํ์ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค.
ํ๋ผ ์ ํ ์ ์
class Para
{
public int Key1, Key2;
public string Data;
}
๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ MS SQL Server์ ๋ํ LINQ ์ฟผ๋ฆฌ๋ฅผ ์ต์ ํํ๋ ๋ฐ ์์ด ๋ช ๊ฐ์ง ์ธก๋ฉด์ ์กฐ์ฌํ์ต๋๋ค.
๋ถํํ๊ฒ๋ ๊ฒฝํ์ด ๋ง๊ณ ์ ๋์ ์ธ .NET ๊ฐ๋ฐ์๋ผ๋ ์์ ์ด ์ฌ์ฉํ๋ ์ง์นจ์ด ์ด๋ฉด์์ ์ํ๋๋ ์์ ์ ์ดํดํด์ผ ํ๋ค๋ ์ ์ ์์ด๋ฒ๋ฆฝ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ๊ทธ๋ค์ ๊ตฌ์ฑ์๊ฐ ๋์ด ์ํํธ์จ์ด ์๋ฃจ์ ์ ํ์ฅํ ๋๋ ์ธ๋ถ ํ๊ฒฝ ์กฐ๊ฑด์ด ์ฝ๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋ฏธ๋์ ์ํํญํ์ ์ฌ์ ์ ์์ต๋๋ค.
๊ฐ๋จํ ๋ฆฌ๋ทฐ๋ ์งํ๋์์ต๋๋ค
ํ
์คํธ ์์ค - ํ๋ก์ ํธ ์์ฒด, TEST ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ
์ด๋ธ ์์ฑ, ์ด๋ฌํ ํ
์ด๋ธ์ ๋ฐ์ดํฐ ์ฑ์ฐ๊ธฐ ๋ฑ์ด ์์ต๋๋ค.
๋ํ ์ด ์ ์ฅ์์ Plans ํด๋์๋ OR ์กฐ๊ฑด์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๊ธฐ ์ํ ๊ณํ์ด ์์ต๋๋ค.
์ถ์ฒ : habr.com