คุณคิดอย่างไรเกี่ยวกับเครื่องมือติดตา Java ใหม่นั่นไม่ใช่ ORM จริง ๆ [ปิด]


12

วิริยะใน Java

ในช่วงหลายปีที่ผ่านมาฉันได้รวบรวมประสบการณ์ในด้านการคงอยู่ของนามธรรมใน Java โดยใช้แนวคิดเช่น EJB 2.0, Hibernate, JPA และที่ปลูกในบ้าน พวกเขาดูเหมือนว่าฉันจะมีโค้งการเรียนรู้ที่สูงชันและความซับซ้อน นอกจากนี้ในฐานะที่เป็นแฟนตัวยงของ SQL ฉันคิดว่าตัวแบบนามธรรมจำนวนมากให้นามธรรมที่เป็นนามธรรมมากกว่า SQL สร้างแนวคิดเช่น "เกณฑ์", "เพรดิเคต", "ข้อ จำกัด " ที่เป็นแนวคิดที่ดีมาก แต่ไม่ใช่ SQL

แนวคิดทั่วไปของการคงอยู่ที่เป็นนามธรรมใน Java ดูเหมือนจะเป็นไปตามแบบจำลองเชิงวัตถุสัมพันธ์ที่ RDBMS จะตรงกับโลก OO การถกเถียงกันเรื่อง ORM นั้นเป็นเรื่องทางอารมณ์มาโดยตลอดเพราะดูเหมือนว่าจะไม่มีทางแก้ปัญหาเดียวที่เหมาะกับทุกคน

jOOQ

ความชอบส่วนตัวของฉันในการหลีกเลี่ยงปัญหาที่เกี่ยวกับออมคือการยึดติดกับโลกสัมพันธ์ ตอนนี้การเลือกกระบวนทัศน์ของตัวแบบข้อมูลไม่ควรเป็นหัวข้อของการอภิปรายเนื่องจากเป็นความชอบส่วนตัวหรือเรื่องของแบบจำลองข้อมูลที่เหมาะสมกับปัญหาที่เป็นรูปธรรม การอภิปรายผมอยากจะเริ่มต้นอยู่ที่ประมาณเครื่องมือติดตาของตัวเองมากที่เรียกว่าjOOQ ฉันออกแบบ jOOQ เพื่อให้ข้อดีส่วนใหญ่ของเครื่องมือการคงอยู่ที่ทันสมัยมี:

  • ภาษาเฉพาะโดเมนขึ้นอยู่กับ SQL
  • การสร้างซอร์สโค้ดการแม็พสกีมาฐานข้อมูลพื้นฐานกับ Java
  • รองรับ RDBMS มากมาย

การเพิ่มคุณสมบัติบางอย่างที่มีอยู่ไม่กี่เครื่องมือที่มีอยู่ (แก้ไขให้ฉันถ้าฉันผิด):

  • การสนับสนุน SQL ที่ซับซ้อน - สหภาพ, การเลือกแบบซ้อน, การรวมตัวเอง, นามแฝง, กรณีข้อ, นิพจน์ทางคณิตศาสตร์
  • สนับสนุน SQL ที่ไม่ได้มาตรฐาน - โพรซีเดอร์ที่เก็บไว้, UDT, ENUMS, ฟังก์ชันเนทิฟ, ฟังก์ชันวิเคราะห์

โปรดพิจารณาหน้าเอกสารเพื่อดูรายละเอียดเพิ่มเติมได้ที่: http://www.jooq.org/learn.php คุณจะเห็นว่ามีการใช้วิธีที่คล้ายกันมากใน Linq สำหรับ C # แม้ว่า Linq ไม่ได้ออกแบบมาเฉพาะสำหรับ SQL

คำถาม

ตอนนี้ต้องบอกว่าฉันเป็นแฟนตัวยงของ SQL ฉันสงสัยว่านักพัฒนารายอื่นจะแบ่งปันความกระตือรือร้นของฉันกับ jOOQ (หรือ Linq) หรือไม่ วิธีการแบบนี้ในการคงอยู่สิ่งที่เป็นนามธรรมหรือไม่? อะไรคือข้อดี / ข้อเสียที่คุณอาจเห็น? ฉันจะปรับปรุง jOOQ ได้อย่างไรและความคิดเห็นของคุณขาดหายไปอย่างไร ฉันไปผิดทางแนวความคิดหรือทางปฏิบัติที่ไหน

ชื่นชมคำตอบที่สำคัญ แต่สร้างสรรค์

ฉันเข้าใจว่าการถกเถียงเป็นเรื่องทางอารมณ์ มีเครื่องมือที่ยอดเยี่ยมมากมายที่ทำสิ่งที่คล้ายกันอยู่แล้ว สิ่งที่ฉันสนใจเป็นสิ่งสำคัญ แต่เป็นข้อเสนอแนะที่สร้างสรรค์ขึ้นอยู่กับประสบการณ์ของคุณเองหรือบทความที่คุณอาจอ่าน


มันจัดการธุรกรรมหรือไม่
อาร์มันด์

@Alison: ไม่มันเกี่ยวกับ SQL การจัดการธุรกรรมเป็นธุรกิจที่ซับซ้อนมากซึ่งฉันคิดว่าเครื่องมือ / กรอบงานอื่น ๆ (รวมถึง EJB2 / 3) จะจัดการได้ดีกว่า jOOQ
Lukas Eder

ฉันขอแนะนำให้ใช้กรณีการใช้งานที่แข็งแกร่งและน่าเชื่อถือซึ่งคุณแสดงให้เห็นว่าวิธีการของคุณนั้นยอดเยี่ยมสำหรับการแก้ปัญหา

นอกจากนี้โปรดทราบว่าการแก้ไขหนานี้จะทำให้คำตอบไม่เกี่ยวข้องมากขึ้นหรือน้อยลงและไม่สามารถใช้กับผู้อ่านในอนาคตได้ ถามคำถามใหม่และดีกว่าแทน

สวัสดี Thorbjorn คุณหมายถึงกรณีใช้นอกเหนือจากที่แสดงในหน้าตัวอย่าง? หรือคุณต้องการที่จะเห็นตัวอย่างในคำถามตัวเอง? เกี่ยวกับการแก้ไข: คุณพูดถูกโดยทั่วไป ในกรณีนี้ แต่ผมคิดว่าสองคำตอบที่ฉันมีเพื่อให้ห่างไกลจะได้รับค่อนข้างเดียวกัน ...
Lukas เอ๊ด

คำตอบ:


5

ฉันคิดว่าคุณกำลังอยู่ในเส้นทางที่ถูกต้องและควรดำเนินการแก้ไขต่อไป บทวิจารณ์ก่อนหน้านี้บางบทวิจารณ์รุนแรงเกินไป แต่ก็มีบางประเด็นที่ถูกต้อง

ฉันก็ค้นหายาวและยากสำหรับโซลูชัน ORM แบบเต็มรูปแบบที่จะตอบสนองความต้องการของฉัน แต่ทุกวิธีที่ฉันดูจะสั้นลง แต่ละคนมีข้อดีข้อเสีย แต่ไม่มีใครทำทุกอย่างที่ฉันต้องการ ฉันเห็นด้วยกับการวิจารณ์ของคุณเกี่ยวกับการแก้ปัญหาเหล่านี้ - โดยทั่วไปแล้วพวกเขาจะซับซ้อนและมีช่วงการเรียนรู้ที่สูงชัน

คู่แข่งอันดับต้น ๆ จะต้องเป็นมาตรฐานอย่างแท้จริงไฮเบอร์เนต มันเป็นคุณสมบัติเต็มรูปแบบที่แข็งแกร่งและผ่านการทดสอบอย่างดี ฉันเคารพและชื่นชมมันสำหรับสิ่งที่มันเป็น ตอนนี้ด้วยความเสี่ยงที่จะละเมิดชุมชนการเขียนโปรแกรมครึ่งหนึ่งฉันต้องบอกว่ามันเป็นส่วนที่ซับซ้อนและซับซ้อนของโค้ดที่ไม่มีประสิทธิภาพ ฉันใช้เวลาพอสมควรในการเจาะลำไส้พยายามแก้จุดบกพร่องและเข้าใจมันและฉันก็ไม่ประทับใจกับสิ่งที่ฉันเห็น ต้องบอกว่าฉันยังคงแนะนำว่าเป็นจุดเริ่มต้นสำหรับทุกคนที่กำลังมองหา ORM ที่ดี มันยอดเยี่ยมที่ CRUD ธรรมดา แต่ฉันจะไม่ใช้มันสำหรับการค้นหาจำนวนมากเช่นการขุดข้อมูลหรือการบดตัวเลข

น่าเสียดายที่แอปพลิเคชันของฉันมีความหลากหลายมากกว่า (แม้ว่าจะมี CRUD บางส่วน) ดังนั้นฉันจึงตัดสินใจที่จะม้วนตัวเอง ฉันรู้ตั้งแต่แรกแล้วว่ามันน่าจะน้อยกว่าเมื่อเทียบกับโซลูชันอื่น ๆ ที่นั่น แต่มันจะดีพอและให้ประสิทธิภาพที่ฉันต้องการ ตอนนี้เป็นส่วนที่ยอดเยี่ยมจริงๆ: ดูเหมือนโซลูชันของคุณ!

นี่คือสิ่งที่ฉันคิดว่าคุณทำถูกต้องที่สุด: คุณกล่าวถึงความเชื่อพื้นฐานของคุณเป็นคำแถลงพันธกิจและความเชื่อเหล่านั้นถูกต้อง เห็นได้ชัดว่าฉันใช้เวลาพอสมควรคิดเกี่ยวกับปัญหานี้เช่นกันและมันก็ยากที่จะแก้ไข แต่เมื่อเวลาผ่านไปฉันคิดว่าชุมชนการเขียนโปรแกรมจะเข้าใจดีขึ้นและเราจะสร้างโซลูชันที่ดีขึ้น ความรุ่งโรจน์ของความพยายามก่อนหน้า (โดยเฉพาะอย่างยิ่งไฮเบอร์เนต) แต่ฉันคิดว่าเราทำได้ดีกว่านี้

วิธีแก้ปัญหาของคุณจะแก้ไขปัญหาได้ดีกว่า แต่จะแก้ไขได้เพียงบางส่วนเท่านั้น ฉันคิดว่าวิธีการแบบเลเยอร์อาจให้ทุกสิ่งที่เราต้องการ สิ่งที่คุณคิดด้วย / IMO คือชั้นรากฐาน ตามที่คุณแนะนำฉันคิดว่าเลเยอร์นี้สร้างขึ้นได้ดีที่สุดโดยอัตโนมัติตามสกีมาฐานข้อมูล ควรเป็นโมเดลวัตถุเชิงสัมพันธ์ที่ง่ายมากที่แมปเข้ากับฐานข้อมูลโดยตรงและไม่มาก คุณถูกต้องว่าข้อมูลยังคงมีความยาวมากกว่ารหัสดังนั้นในระดับนี้ข้อมูลควรขับรหัสและไม่ใช่ในทางกลับกัน มันจะสนับสนุน CRUD เช่นเดียวกับการสอบถามกลุ่มนักแสดง ฉันอาจจะใช้แคช L1 บางชนิดที่เลเยอร์นี้

อย่างไรก็ตามสิ่งที่โซลูชัน ORM อื่น ๆ ทำได้ดีก็คือความสามารถในการกำหนดวัตถุของคุณในลักษณะที่ไม่ขึ้นอยู่กับโครงสร้างที่แท้จริงของฐานข้อมูลพื้นฐาน สำหรับฟังก์ชั่นนี้ฉันจะสร้างเลเยอร์อื่น โดยความจำเป็นเลเยอร์นี้จะเป็นนามธรรมมากขึ้นหวังว่าจะง่ายขึ้น (เสียค่าใช้จ่ายในการทำงานที่สูญเสียไป) และสร้างในเลเยอร์ก่อนหน้า มันอาจใช้แคช L2

ฉันเปิดให้มีเลเยอร์เพิ่มขึ้น แต่จุดสำคัญสำหรับฉันคือการให้จุดเข้าหลายจุดในห้องสมุดคุณสามารถตอบสนองทุกความต้องการ สำหรับผู้ที่ต้องการโซลูชัน CRUD อย่างง่ายที่แมปโดยตรงกับวัตถุที่พวกเขาเลือกพวกเขาสามารถสร้างชั้นบนสุด สำหรับผู้ที่มองหาสิ่งที่ทรงพลังและมีประสิทธิภาพ แต่เต็มใจที่จะได้รับความซับซ้อนที่เพิ่มเข้ามาพวกเขาสามารถเสียบชั้นล่าง ฉันจะไม่สร้างภาษาคิวรีพิเศษ แต่จะแสดงออบเจ็กต์ตัวสร้างคิวรีของคุณแทนเพื่อวัตถุประสงค์นี้ และเนื่องจากรหัสดังกล่าวเป็นเลเยอร์ฟังก์ชันนั้นจึงมีอยู่โดยไม่มีการส่งผ่านพิเศษ

ฉันคิดว่าคุณมีความเข้าใจเกี่ยวกับพื้นที่และไม่ได้ปรับเปลี่ยนล้อ แต่มีช่องแตกต่างกันเล็กน้อย และตรงไปตรงมานี่คือวงล้อที่สามารถใช้การปรับปรุงได้ คุณต้องเผชิญกับการต่อสู้ที่ยากลำบากเมื่อเทียบกับโรงไฟฟ้าในอดีต แต่ถ้าทางออกของคุณดีและฉันคิดว่ามันกำลังมุ่งหน้าไปทางนั้นมันจะได้รับความนิยมในข้อดีของมัน


สวัสดี Dale ขอขอบคุณสำหรับกำลังใจของคุณ! ฉันชอบการใช้ถ้อยคำของคุณเกี่ยวกับเลเยอร์และ jOOQ ที่อยู่ในเลเยอร์ที่ต่ำที่สุดและมีสิ่งที่เป็นนามธรรมอยู่ด้านบนของ jOOQ นั่นคือสิ่งที่ฉันต้องการจัดการเมื่อฉันมีโมเมนตัมเพียงพอกับ jOOQ การเชื่อมต่อกับ JPA และ / หรือไฮเบอร์เนตจะอยู่ในแผนงานอย่างชัดเจน ตามที่คุณวางไว้เครื่องมือเหล่านี้มีความเรียบง่ายผ่านนามธรรมและมีคุณสมบัติมากมายที่ฉันไม่ต้องการในเลเยอร์ของฉัน: ธุรกรรมเซสชันแคช L2 เป็นต้น PS: โซลูชันของคุณเป็นสาธารณสมบัติหรือไม่? ฉันจะสนใจมาก!
Lukas Eder

8

"มีกระสุนวิเศษจำนวนมากอยู่ที่นั่นและไม่มีปัญหานักพัฒนาที่ไร้เดียงสา"

ไม่มีความผิดดูเหมือนว่าคุณไม่เข้าใจสิ่งที่เกิดขึ้นในพื้นที่นี้แล้วดังนั้นจึงมีการคิดค้นใหม่ล้อ - ประสบการณ์จะบอกเราว่าล้อที่คุณประดิษฐ์ในขณะที่เรียบร้อยและสนุกไม่น่าจะเป็นไปได้ ดีหรือมีประโยชน์เนื่องจากล้อที่ได้รับการขัดเกลามาอย่างดี


1
ขอบคุณสำหรับความคิดเห็นของคุณ! คุณเคยดูห้องสมุดของฉันในเชิงลึกหรือไม่? ฉันพยายาม "ละทิ้ง O" อย่างแม่นยำบทความของคุณเรียกมันอย่างไร jOOQ ต้องการให้นักพัฒนาเขียน SQL ไม่ใช่รหัสที่แมปหรือ ฉันพยายามบรรลุสิ่งที่ Linq ทำเพื่อ. NET โดยไม่สามารถเขียนคอมไพเลอร์ Java อีกครั้ง ขอแสดงความยินดีกับ Microsoft สำหรับ Linq! และฉันกล้าพูดว่าฉันไม่ไร้เดียงสาเพราะฉันสร้าง jOOQ หลังจากที่ฉันไม่พอใจ EJB 2.0 (เมื่อไม่นานมานี้) หรือกับไฮเบอร์เนต แม่นยำด้วยเหตุผลที่คุณชี้ให้เห็นในบทความของคุณ ฉันลองทำในสิ่งที่ทำแล้วไม่เหมาะกับความต้องการของฉัน
Lukas Eder

jOOQ ต้องการให้นักพัฒนาซอฟต์แวร์ใช้ API ที่คล้ายกับ Criteria ของคุณ นั่นไม่ใช่ SQL มันเป็นวิธีในการสร้าง SQL ที่ในความเป็นจริงนั้นน่าเกลียดและซับซ้อนกว่า SQL โดยมีประโยชน์น้อยมากถ้ามี "q.addCompareCondition (TAuthor.YEAR_OF_BIRTH, 1920, Comparator.GREATER);" ฉันเห็นว่าไม่มีอะไรแตกต่างจากเครื่องมือสร้างคลาสต่อตาราง ซึ่งหมายความว่าอย่างที่ฉันพูดมีความน่าจะเป็นบางอย่างที่มีอยู่แล้วดีกว่า ฟิลด์สตริงที่สร้างเทมเพลตไม่ได้ใกล้เคียงกับนิพจน์แลมบ์ดาที่เปิดใช้งาน
quentin-starin

1
มันยากสำหรับฉันที่จะเข้าใจแรงจูงใจเบื้องหลังคำตอบ / ความคิดเห็นของคุณ ฉันยังคิดว่าคุณไม่ได้ดูตัวอย่างที่ฉันให้มาอย่างละเอียด (คุณยกตัวอย่างแรก) และการเปรียบเทียบผลิตภัณฑ์คู่แข่งของคุณนั้นค่อนข้างคลุมเครือสำหรับฉัน ประเด็นคำถามของฉันคือการได้รับข้อเสนอแนะที่สร้างสรรค์และฉันจะขอบคุณเพียงแค่นั้น คุณพูดถึง "เครื่องมือที่ดีกว่า" และ "แลมบ์ดานิพจน์" คุณช่วยอธิบายรายละเอียดได้ไหม? jOOQ เปรียบเทียบกับเครื่องมือที่คุณพูดถึงอย่างไร นิพจน์แลมบ์ดานำมาใช้ใน Java 6 ได้อย่างไร - ก่อนที่ Oracle จะเพิ่มใน Java 8 ขอขอบคุณอีกครั้ง
Lukas Eder

2

สถานการณ์หนึ่งที่จะทำให้ API ประเภทนี้เหมาะสมคือเมื่อคุณต้องการตัวสร้าง SQL ที่ไม่เชื่อเรื่องพระเจ้าของฐานข้อมูลซึ่ง ORM ส่วนใหญ่ไม่อนุญาตให้คุณมี สถานการณ์ใหญ่อย่างหนึ่งที่ฉันต้องการคือการสร้างรายงาน ฉันต้องการสร้าง SQL ในลักษณะเชิงวัตถุและจะเรียกใช้ sql นี้ในฐานข้อมูลต่าง ๆ สำหรับการทดสอบ Hibernate และ ORM อื่น ๆ นั้นขึ้นอยู่กับการกำหนดค่าดังนั้นการ จำกัด การสร้าง sql ของฉันดังนั้นฉันจึงไม่สามารถรวมตารางที่ไม่มีการเชื่อมโยงอยู่ใน xmls เนื่องจากการเชื่อมโยงใด ๆ เป็นไปได้ในโมดูลรายงานที่ฉันกำลังพัฒนาฉันจึงมองหาสิ่งที่แตกต่าง จากนั้นฉันก็เจอ JOOQ มันแค่แก้ปัญหาของฉัน ฉันต้องการการเข้าถึงแบบอ่านอย่างเดียวไม่เคยประสบปัญหาการแก้ไขข้อบกพร่องที่เจ็บปวดเช่นในโหมดไฮเบอร์เนต

จริง ๆ แล้วฉันวางแผนที่จะพัฒนาวิธีการสร้างแบบจำลองข้อมูลที่แตกต่างกันมากกว่าวิธี POJO ทั่วไปและใช้ JOOQ เป็นหนึ่งในรากฐานซึ่งเป็นชั้นในการสร้าง SQL ดังนั้นด้านบนของ JOOQ ฉันวางแผนที่จะสร้างวิธีที่ยืดหยุ่นมากขึ้นในการสร้างแบบจำลองข้อมูล / เอนทิตีโดยใช้ HashMaps การออกแบบของฉันจะทำให้มันเป็นไปได้ที่จะรวมการออกแบบส่วนหน้าและส่วนหลังได้ง่ายขึ้นในจุดเริ่มต้นเดียวแม้ว่าฉันจะใช้เลเยอร์ต่าง ๆ เพื่อทำให้กรอบงานเสร็จสมบูรณ์เหมือนกับที่ความคิดเห็นด้านบนกล่าวไว้ JOOQ จะเล่นเป็นส่วนที่ดีมากเหมือนในโครงการของฉันมันทำหน้าที่เป็นหนึ่งในรากฐานหรือเสาหลัก

ดังนั้นติดตามการทำงานของ lukas ที่ดี!


1

ถ้าฉันเข้าใจเครื่องมือของคุณอย่างถูกต้องมันจะจับคู่วัตถุกับกรอบแนวคิด SQL โดยตรงแทนที่จะทำแผนที่วัตถุที่ใช้การทำแผนที่กับฟังก์ชันการทำงานของ SQL (ORM) เกือบจะเหมือนกับ ORM - สิ่งที่เป็นนามธรรมสำหรับการควบคุมแบบ geeky ที่ดีขึ้น ออมมักจะไม่ให้คุณ?

ไม่แน่ใจว่าคุณกำลังพยายามแก้ไขปัญหาใด เนื่องจากเครื่องมือของคุณต้องการความรู้ในเชิงลึกเกี่ยวกับ SQL ผู้คนก็จะไม่ใช้ SQL อย่างนั้นเหรอ? มันเป็นรหัสกาวที่คุณพยายามกำจัดใช่ไหม การตรวจสอบความถูกต้องของการสืบค้น SQL ที่ดีขึ้นผ่านการสร้างแบบจำลอง API แทนที่จะเป็นสตริงธรรมดา?

ฉันต้องยอมรับว่าตัวอย่าง JOOQ ของคุณดูเหมือน SQL เวอร์ชัน LISP ไม่ใช่ว่าเป็นสิ่งที่ไม่ดี :) และฉันเห็นบางสิ่งขั้นสูงเป็นที่น่าสนใจ แต่ข้อดีที่คุณแสดงรายการจะหายไปอย่างช้าๆเมื่อคุณก้าวไปสู่ขั้นสูงมากขึ้น (config มากขึ้น, นามธรรมน้อยลง, ฝังลึกมากขึ้นในถ้ำภายในของ SQL เฉพาะ เครื่องยนต์ ฯลฯ )

ข้อเสนอแนะข้อหนึ่งที่ฉันอยากจะเห็นว่าฉันพลาดใน ORM ส่วนใหญ่คือกรอบงานที่สามารถจัดการกับ Objects ในลักษณะที่ไม่สัมพันธ์กันแปลการโต้ตอบ Object เป็นสิ่งที่แบ็กเอนด์อาจเป็น (SQL, NoSQL, Maps Maps, RDF เป็นต้น) .) ซึ่งต้องใช้การแคชของโมเดลที่ใช้แทนการโต้ตอบเฉพาะของ SQL ภาษาถิ่นและการคิดเชิงสัมพันธ์

แน่นอน IMHO :)


สวัสดีขอบคุณสำหรับคำติชมของคุณ คุณเข้าใจถูกต้อง ตามที่ qstarin วางไว้ฉันพยายามลบ "O" ออกจาก ORM และคงความสัมพันธ์เอาไว้ ทำไมไม่ใช้ SQL ธรรมดา คุณหมายถึง JDBC? คุณเคยสอบถามฐานข้อมูลของคุณด้วย 5 ตัวเลือกซ้อนกันและการเชื่อมต่อหลายฟังก์ชั่นการวิเคราะห์ด้วย JDBC หรือไม่? ข้อผิดพลาดทางไวยากรณ์การจับคู่ที่ไม่ตรงกัน ฯลฯ น่าเสียดายที่ jOOQ ไม่สามารถเป็นเครื่องมือที่เหมาะสมสำหรับคุณได้เนื่องจากไม่มีความเป็นไปได้ที่จะแมปโมเดลใด ๆกับวัตถุ และฉันไม่ต้องการสิ่งนั้น jOOQ ควรสัมพันธ์กัน สำหรับผู้พัฒนาที่ชอบวิธีคิดแบบนั้น สำหรับคนอื่นฉันขอแนะนำ JPA .. หรือ Linq ถ้าคุณทำ. NET
Lukas Eder
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.