最近一段時間,我使用golang
開發了一個新的ORM
庫。
為了讓這個庫更好用,我比較研究了各語言的主流ORM
庫,發現有一些語言的ORM
庫確實很好用,而有另外一些語言的庫那不是一般的難用。
然後我總結了他們呢的一些共性和差異點,於是形成了本文的主要內容。
本文會先說明什麼是SQL編寫難題,以及探討一下 code first
和 database first
的優缺點。
然後依據這兩個問題的結論去審視目前主流後端語言java
, c#
, php
, python
, go
各自的orm庫,對比研究下他們的優缺點。最後給出總結和參考檔案。
如果你需要做技術選型,或者做技術研究,或者類似於我做框架開發,或者單純地瞭解各語言的差異,或者就是想吹個牛,建議儲存或收藏。如果本文所涉及到的內容有任何不正確,歡迎批評指正。
溫馨提示,本文會有一些戲謔或者調侃成分,並非對某些語言或者語言的使用者有任何歧視意見。
如果對你造成了某些傷害,請多包涵。
如果你是做web開發,那麼必然需要儲存資料到資料庫,這個時候你必須熟悉使用sql語句來讀寫資料庫。
sql本身不難,命令也就那幾個,關鍵字也不算多,但是為什麼編寫sql會成為難題呢?
比如下面的sql
select * from user
insert user (name,mobile) values ('tang','18600000000')
它有什麼難題? 簡單的單表操作嘛,一點難題沒有,但凡學過點sql
的程式設計師都能寫出來,並且保證正確。我估計比例能超過90%
但是,如果你需要寫下面的sql呢?
SELECT
article.*,
person.name as person_name
FROM article
LEFT JOIN person ON person.id=article.person_id
WHERE article.type = 0
AND article.age IN (18,20)
這個也不復雜,就是你在做查詢列表的時候,會經常用到的聯表查詢。你是否還有勇氣說,寫出來的sql
絕對正確。我估計比例不超過70%
再稍微複雜點,如果是下面的sql?
SELECT
o.*,
d.department_name,
(SELECT Sum(so.goods_fee) AS task_detail_target_completed_tem
FROM sale_order so
WHERE so.merchant_id = '356469725829664768'
AND so.create_date BETWEEN (20230127) AND (20230212)
AND so.delete_state = 2
AND so.department_id = o.department_id
) AS task_detail_target_completed
FROM task_detail o
LEFT JOIN department d ON d.department_id=o.department_id
WHERE o.merchant_id = '356469725829664768'
AND o.task_id = '356469725972271104768'
這是我專案裡真實的sql語句,目的是統計出所有部門在某時間段內各自的業績。邏輯上也不太複雜,但你是否還有勇氣說,寫出來的sql
絕對正確。我估計比例不超過40%
如上面的sql所示,SQL編寫難題在於以下幾方面。
要保證欄位正確
應該有的欄位不能少,不應該有的欄位不能多。
比如你把mobile
誤打成mobike
,這屬於拼寫錯誤,但是這個拼寫錯誤只有在實際執行的時候才會告訴你欄位名錯了。
並且專案越大,表越多,欄位越多,這種拼寫錯誤發生的可能性越大。以至於可以肯定的說,100%的可能性會出現。
要特別注意sql語法
例如你在查詢的時候必須寫from
,絕對不能誤寫成form
,但是在實際開發過程中,很容易就打錯了。
這種錯誤,也只有執行的時候才會告訴你語法錯了。並且sql
越複雜,這種語法錯誤發生的可能性越大。
編輯器不會有sql的語法提示
常見的編碼用的軟體,對於sql相關的程式碼,不會有語法提示,也不會有表名提示,欄位名提示。
最終的程式碼質量如何全憑你的眼力,經驗,能力。
很顯然,既然存在該難題,那麼哪個ORM能解決該難題,就應該算得上好,如果不能解決,則不能稱之為好。
這倆概念並不是新概念,但是我估計大多數開發者並不熟悉。
所謂 code first, 相近的詞是 model fist, 意思是模型優先,指的是在設計和開發系統時,優先和重點做的工作是設計業務模型,然後根據業務模型去建立資料庫。
所謂 database first,意思是資料庫優先,指的是在設計和開發系統時,優先和重點做的工作是建立資料庫結構,然後去實現業務。
這裡我提到了幾個詞語,可能在不同的語言裡叫法不一樣,可能不同的人的叫法也不一樣,為了下述方便,我們舉例子來說。
假設我是一個對電商系統完全不懂的小白,手頭上也沒有如何設計電商系統的資料,我和我的夥伴只是模糊地知道電商系統主要業務就是處理訂單。
然後我大概會知道這個訂單,主要的資訊包括哪個使用者下單,什麼時間下單,有哪幾種商品,數量分別是多少,根據這些已有的資訊,我可以設計出來業務模型如下
public class OrderModel {
//訂單編號
Integer orderId;
//使用者編號
Integer userId;
//訂單時間
Integer createTime;
//訂單詳情(包含商品編號,商品數量)
String orderDetail;
}
很簡單,對吧,這個模型很匹配我目前對系統的認知。接下來會做各種業務邏輯,最後要做的是將訂單模型的資料儲存到資料庫。但是在儲存資料到資料庫的時候,就有一些考慮了。
我可以將上面OrderModel
業務模型建立一張對應表,裡面的4個屬性,對應資料表裡的4個欄位,這完全可以。
但是我是電商小白,不是資料庫小白啊,這樣儲存的話,肯定不利於統計訂單商品的。
所以我換一種策略,將OrderModel
的資訊進行拆分,將前三個屬性 orderId, userId, createTime 放到一個新的類裡。
然後將 orderDetail 的資訊進行再次分解,放到另一個類裡
public class OrderEntity {
Integer orderId;
Integer userId;
Integer createTime;
}
public class OrderDetailEntity {
Integer orderDetailId;
Integer orderId;
Integer goodsId;
Integer goodsCount;
}
最後,在資料庫建立兩張表order
,order_detail
,表結構分別對應類OrderEntity
,OrderDetailEntity
的結構。
至此,我們完成了從業務模型OrderModel
到資料表order
,order_detail
的過程。
這就是 code first ,注意這個過程的關鍵點,我優先考慮的是模型和業務實現,後面將業務模型資料進行分解和儲存是次要的,非優先的。
假設我是一個對電商系統非常熟悉的老鳥,之前做過很多電商系統,那麼我在做新的電商系統的時候,就完全可以先設計資料庫。
order
表放訂單主要資料,裡面有xxx幾個欄位,分別有什麼作用,有哪些狀態值
order_detail
表放訂單詳情資料,,裡面有xxx幾個欄位,分別有什麼作用
這些都可以很清楚和明確。然後根據表資訊,生成OrderEntity
,以及OrderDetailEntity
即可開始接下來的編碼工作。這種情況下OrderModel
可能有,也可能沒有。
這就是 database first ,注意這個過程的關鍵點,我優先考慮的是資料庫結構和資料表結構。
code first 模式下, 系統設計者優先考慮的是業務模型OrderModel
, 它可以描述清楚一個完整業務,包括它的所有業務細節(什麼人的訂單,什麼時候的訂單,訂單包含哪些商品,數量多少),有利於設計者對於系統的整體把控。
database first 模式下, 系統設計者優先考慮的是資料表order
,order_detail
,他們中任何一張表都不能完整的描述清楚一個完整業務,只能夠描述區域性細節,不利於設計者對於系統的整體把控。
在這裡,調皮的同學會問,在 database first 模式下, 我把order
,order_detail
的資訊一起看,不就知道完整的業務細節了嗎?
確實是這樣,但這裡有一個前提,前提是你必須明確的知道order
,order_detail
是需要一起看的,而你知道他們需要一起看的前提是你瞭解電商系統。 如果你設計的不是電商系統,而是電路系統,你還了解嗎?還知道哪些表需要一起看嗎?
至此,我們可以有以下粗淺的判斷:
對於新專案,不熟悉的業務,code first 模式更適合一些
對於老專案,熟悉的業務,database first 模式更合適一些
如果兩種模式都可以的話,優先使用 code first 模式,便於理解業務,把控專案
如果哪個ORM支援 code first , 我們可以稍稍認為它更好一些
Java語言是web開發領域處於領先地位,這一點無可置疑。它的優點很明顯,但是缺點也不是沒有。
國內應用比較廣泛的orm是Mybatis,以及衍生品Mybatis-plus等
實際上Mybatis團隊還出了另外一款產品,MyBatis Dynamic SQL,國內我見用的不多,討論都較少。英文還可以的同學,可以看下面的檔案。
另外還有 jOOQ, 實際上跟 MyBatis Dynamic SQL 非常類似,有興趣的可以去翻翻
下面,我們舉一些例子,來對比一下他們的基本操作
單就orm這一塊,國內用的最多的應該是Mybatis,說到它的使用體驗吧,那簡直是一言難盡。
你需要先定義模型,然後編寫xml
檔案用來對映資料,然後建立mapper檔案,用來執行xml
裡定於的sql。
從這個流程可以看出,中間的xml
檔案起到核心作用,裡面不光有資料型別轉換,還有最核心的sql
語句。
典型的xml
檔案內容如下
<mapper namespace="xxx.mapper.UserMapper">
<insert id="insertUser" parameterType="UserEntity">
insert into user (id,name,mobile)
values (#{id},#{name},#{mobile})
</insert>
<update id="updateUser" parameterType="UserEntity">
update user set
name = #{name},
mobile = #{mobile}
where id = #{id}
</update>
<delete id="deleteUser">
delete from user where id = #{id}
</delete>
<select id="selectUsers" resultType="UserVO">
select u.*, (select count(*) from article a where a.uid=u.id) as article_count
from user u
where u.id = #{id}
</select>
</mapper>
你在編寫這個xml
檔案的時候,這個手寫sql沒有本質區別,一定會遇到剛才說到的SQL編寫難題
。
這裡有必要提一下 Mybatis-plus,它是國內的團隊開發出來的工具,算是對Mybatis的擴充套件吧,它減少了xml
檔案內容的編寫,減少了一些開發的痛苦。比如,你可以使用如下的程式碼來完成以上相同的工作
userService.insert(user);
userService.update(user);
userService.deleteById(user);
List<UserEntity> userList = userService.selectList(queryWrapper);
完成這些工作,你不需要編寫任何xml
檔案,也不需要編寫sql
語句,如之前所述,減少了一些開發的痛苦。
但是,請你注意我的用詞,是減少了一些。
對於連表操作,巢狀查詢等涉及到多表操作的事情,它就不行了,為啥不行,因為根本就不支援啊。
遇到這種情況,你就老老實實的去寫xml
吧,然後你還會遇到剛才說到的SQL編寫難題
。
值得一提的是Mybatis3 Dynamic Sql,翻譯一下就是動態sql。還是剛才說的國內我見用的不多,討論都較少,但是評價看上去挺好。
簡單來說,可以根據不同條件拼接出sql語句。不同於上面的Mybatis,這些sql語句是程式執行時生成的,而不是提前寫好的,或者定義好的。
它的使用流程是,先在資料庫裡定義好資料表,然後建立模型檔案,讓然後通過命令列工具,將每一個表生成如下的支援檔案
public final class PersonDynamicSqlSupport {
public static final Person person = new Person();
public static final SqlColumn<Integer> id = person.id;
public static final SqlColumn<String> firstName = person.firstName;
public static final SqlColumn<LastName> lastName = person.lastName;
public static final SqlColumn<Date> birthDate = person.birthDate;
public static final SqlColumn<Boolean> employed = person.employed;
public static final SqlColumn<String> occupation = person.occupation;
public static final SqlColumn<Integer> addressId = person.addressId;
public static final class Person extends SqlTable {
public final SqlColumn<Integer> id = column("id", JDBCType.INTEGER);
public final SqlColumn<String> firstName = column("first_name", JDBCType.VARCHAR);
public final SqlColumn<LastName> lastName = column("last_name", JDBCType.VARCHAR, "examples.simple.LastNameTypeHandler");
public final SqlColumn<Date> birthDate = column("birth_date", JDBCType.DATE);
public final SqlColumn<Boolean> employed = column("employed", JDBCType.VARCHAR, "examples.simple.YesNoTypeHandler");
public final SqlColumn<String> occupation = column("occupation", JDBCType.VARCHAR);
public final SqlColumn<Integer> addressId = column("address_id", JDBCType.INTEGER);
public Person() {
super("Person");
}
}
}
可以看出,這裡的主要功能能是將表內的欄位,與java專案裡的類裡面的屬性,做了一一對映。
接下來你在開發的時候,就不用關心表名,以及欄位名了,直接使用剛才生成的類,以及類下面的那些屬性。具體如下
SelectStatementProvider selectStatement = select(id.as("A_ID"), firstName, lastName, birthDate, employed,occupation, addressId)
.from(person)
.where(id, isEqualTo(1))
.or(occupation, isNull())
.build()
.render(RenderingStrategies.MYBATIS3);
List<PersonRecord> rows = mapper.selectMany(selectStatement);
如上面的程式碼,好處有以下四點
SQL編寫難題
當然帶來了額外的事情,比如你要使用工具來生成PersonDynamicSqlSupport
類,比如你要先建表。
先建表這事兒,很明顯就屬於 database first
模式。
C# 在工業領域,遊戲領域用的多一些,在web領域少一些。
它也有自己的orm,名字叫 Entity Framework Core, 一直都是微軟公司在維護。
下面是一個典型的聯表查詢
var id = 1;
var query = database.Posts
.Join(database.Post_Metas,
post => post.ID,
meta => meta.Post_ID,
(post, meta) => new { Post = post, Meta = meta }
)
.Where(postAndMeta => postAndMeta.Post.ID == id);
這句程式碼的主要作用是,將資料庫裡的Posts表,與Post_Metas表做內聯操作,然後取出Post.ID等於1的資料
這裡出現的Post,以及Meta都是提前定義好的模型,也就是類。 Post.ID 是 Post 的一個屬性,也是提前定義好的。
整個功能的優點很多,你不再需要手寫sql,不需要關心欄位名,不需要生成額外類,也不會有語法錯誤,你只需要提前定義好模型,完全沒有SQL編寫難題
,很明顯就屬於 code first
模式。
對比java的Mybatis以及Mybatis3 Dynamic Sql來說,你可以腦補一下下面的場景
php體系內,框架也非常多,比如常見的laravel
,symfony
,這裡我們就看這兩個,比較有代表性
使用php語言開發web應用的也很多,其中比較出名的是laravel
框架,比較典型的運算元據庫的程式碼如下
$user = DB::table('users')->where('name', 'John')->first();
這裡沒有使用模型(就算使用了也差不多),程式碼裡出現的 users 就是資料庫表的名字, name 是 users 表裡的欄位名,他們是被直接寫入程式碼的
很明顯它會產生SQL編寫難題
並且,因為是先設計資料庫,肯定也屬於 database first
模式
這個框架歷史也比較悠久了,它使用了 Doctrine 找個類庫作為orm
使用它之前,也需要先定義模型,然後生成支援檔案,然後建表,但是在實際使用的時候,還是和laravel一樣,表名,欄位名都需要寫死
$repository = $this->getDoctrine()->getRepository('AppBundle:Product');
// query for a single product by its primary key (usually "id")
// 通過主鍵(通常是id)查詢一件產品
$product = $repository->find($productId);
// dynamic method names to find a single product based on a column value
// 動態方法名稱,基於欄位的值來找到一件產品
$product = $repository->findOneById($productId);
$product = $repository->findOneByName('Keyboard');
// query for multiple products matching the given name, ordered by price
// 查詢多件產品,要匹配給定的名稱和價格
$products = $repository->findBy(
array('name' => 'Keyboard'),
array('price' => 'ASC')
);
很明顯它也會產生SQL編寫難題
另外,並不是先設計表,屬於 code first
模式
在python領域,有一個非常著名的框架,叫django, 另外一個比較出名的叫flask, 前者追求大而全,後者追求小而精
django推薦的開發方法,也是先建模型,但是在查詢的時候,這建立的模型,基本上毫無用處
res=models.Author.objects.filter(name='jason').values('author_detail__phone','name')
print(res)
# 反向
res = models.AuthorDetail.objects.filter(author__name='jason') # 拿作者姓名是jason的作者詳情
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','author__name')
print(res)
# 2.查詢書籍主鍵為1的出版社名稱和書的名稱
res = models.Book.objects.filter(pk=1).values('title','publish__name')
print(res)
# 反向
res = models.Publish.objects.filter(book__id=1).values('name','book__title')
print(res)
如上連表查詢的程式碼,values('title','publish__name') 這裡面寫的全都是欄位名,寫死進去,進而產生sql語句,查詢出結果
很顯然,它也會產生SQL編寫難題
另外,並不是先設計表,屬於 code first
模式
flask本身沒有orm,一般搭配 sqlalchemy 使用
使用 sqlalchemy 的時候,一般也是先建模型,然後查詢的時候,可以直接使用模型的屬性,而無須寫死
result = session.
query(User.username,func.count(Article.id)).
join(Article,User.id==Article.uid).
group_by(User.id).
order_by(func.count(Article.id).desc()).
all()
如上 Article.id 即是 Article 模型下的 id 屬性
很顯然,它不會產生SQL編寫難題
另外,並不是先設計表,屬於 code first
模式
在go體系,orm比較多,屬於百花齊放的形態,比如國內用的多得gorm以及gorm gen,國外比較多的ent, 當然還有我自己寫的 arom
使用gorm,一般的流程是你先建立模型,然後使用類似如下的程式碼進行操作
type User struct {
Id int
Age int
}
type Order struct {
UserId int
FinishedAt *time.Time
}
query := db.Table("order").
Select("MAX(order.finished_at) as latest").
Joins("left join user user on order.user_id = user.id").
Where("user.age > ?", 18).
Group("order.user_id")
db.Model(&Order{}).
Joins("join (?) q on order.finished_at = q.latest", query).
Scan(&results)
這是一個巢狀查詢,雖然定義了模型,但是查詢的時候並沒有使用模型的屬性,而是輸入寫死
很顯然,它會產生SQL編寫難題
另外,是先設計模型,屬於 code first
模式
gorm gen 是 gorm 團隊開發的另一款產品,和mybaits下的Mybatis3 Dynamic Sql比較像
它的流程是 先建立資料表,然後使用工具生成結構體(類)和支援程式碼, 然後再使用生成的結構體
它生成的比較關鍵的程式碼如下
func newUser(db *gorm.DB) user {
_user := user{}
_user.userDo.UseDB(db)
_user.userDo.UseModel(&model.User{})
tableName := _user.userDo.TableName()
_user.ALL = field.NewAsterisk(tableName)
_user.ID = field.NewInt64(tableName, "id")
_user.Name = field.NewString(tableName, "name")
_user.Age = field.NewInt64(tableName, "age")
_user.Balance = field.NewFloat64(tableName, "balance")
_user.UpdatedAt = field.NewTime(tableName, "updated_at")
_user.CreatedAt = field.NewTime(tableName, "created_at")
_user.DeletedAt = field.NewField(tableName, "deleted_at")
_user.Address = userHasManyAddress{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Address", "model.Address"),
}
_user.fillFieldMap()
return _user
}
注意看,其中大多數程式碼的作用是啥?不意外,就是將結構體的屬性與表欄位做對映關係
_user.Name 對應 name
_user.Age 對應 age
如此,跟mybaits下的Mybatis3 Dynamic Sql的思路非常一致
典型查詢程式碼如下
u := query.User
err := u.WithContext(ctx).
Select(u.Name, u.Age.Sum().As("total")).
Group(u.Name).
Having(u.Name.Eq("group")).
Scan(&users)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
這是一個分組查詢,定義了模型,也使用了模型的屬性。
但是呢,它需要使用工具生成額外的支援程式碼,並且需要先定義資料表
很顯然,它不會產生SQL編寫難題
另外,它是先設計表,屬於 database first
模式
ent 是 facebook公司開發的Orm產品,與 gorm gen 有相通,也有不同
相同點在於,都是利用工具生成實體與資料表欄位的對映關係
不同點在於gorm gen先有表和欄位,然後生成實體
ent是沒有表和欄位,你自己手動設定,設定完了一起生成實體和建表
接下來,看一眼ent生成的對映關係
const (
// Label holds the string label denoting the user type in the database.
Label = "user"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldAge holds the string denoting the age field in the database.
FieldAge = "age"
// FieldAddress holds the string denoting the address field in the database.
FieldAddress = "address"
// Table holds the table name of the user in the database.
Table = "users"
)
有了對映關係,使用起來就比較簡單了
u, err := client.User.
Query().
Where(user.Name("realcp")).
Only(ctx)
注意,這裡沒有寫死
它需要使用工具生成額外的支援程式碼,並且需要先設定表結構
很顯然,它不會產生SQL編寫難題
另外,它屬於先設計表,屬於 database first
模式
aorm 是我自己開發的orm庫,吸取了ef core 的一些優點,比較核心的步驟如下
和大多數orm一樣,需要先建立模型,比如
type Person struct {
Id null.Int `aorm:"primary;auto_increment" json:"id"`
Name null.String `aorm:"size:100;not null;comment:名字" json:"name"`
Sex null.Bool `aorm:"index;comment:性別" json:"sex"`
Age null.Int `aorm:"index;comment:年齡" json:"age"`
Type null.Int `aorm:"index;comment:型別" json:"type"`
CreateTime null.Time `aorm:"comment:建立時間" json:"createTime"`
Money null.Float `aorm:"comment:金額" json:"money"`
Test null.Float `aorm:"type:double;comment:測試" json:"test"`
}
然後範例化它,並且儲存起來
//Instantiation the struct
var person = Person{}
//Store the struct object
aorm.Store(&person)
然後即可使用
var personItem Person
err := aorm.Db(db).Table(&person).WhereEq(&person.Id, 1).OrderBy(&person.Id, builder.Desc).GetOne(&personItem)
if err != nil {
fmt.Println(err.Error())
}
很顯然,它不會產生SQL編寫難題
另外,它屬於先設計模型,屬於 code first
模式
本文,我們提出了兩個衡量orm功能的原則,並且對比了幾大主流後端語言的orm,彙總列表如下
框架 | 語言 | SQL編寫難題 | code first | 額外建立檔案 |
---|---|---|---|---|
MyBatis 3 | java | 有難度 | 不是 | 需要 |
MyBatis-Plus | java | 有難度 | 不是 | 不需要 |
MyBatis Dynamic SQL | java | 沒有 | 不是 | 需要 |
jOOQ | java | 沒有 | 不是 | 需要 |
ef core | c# | 沒有 | 是 | 不需要 |
laravel | php | 有難度 | 不是 | 不需要 |
symfony | php | 有難度 | 不是 | 需要 |
django | python | 有難度 | 不是 | 不需要 |
sqlalchemy | python | 沒有 | 是 | 不需要 |
grom | go | 有難度 | 是 | 不需要 |
grom gen | go | 沒有 | 不是 | 需要 |
ent | go | 沒有 | 不是 | 需要 |
aorm | go | 沒有 | 是 | 不需要 |
單就從這張表來說,不考慮其他條件,在做orm技術選型時,
如果你使用java語言,請選擇 MyBatis Dynamic SQL 或者 jOOQ,因為選擇他們不會有SQL編寫難題
如果你使用c#語言,請選擇 ef core, 這已經是最棒的orm了,不會有SQL編寫難題
,支援code first
,並且不需要額外的工作
如果你使用php語言,請選擇 laravel 而不是 symfony, 反正都有SQL編寫難題
,那就挑個容易使用的
如果你使用python語言,請選擇 sqlalchemy 庫, 不會有SQL編寫難題
,支援code first
,並且不需要額外的工作
如果你使用go語言,請選擇 aorm 庫, 不會有SQL編寫難題
,支援code first
,並且不需要額外的工作
好了,文章寫兩天了,終於寫完了。如果對你有幫助,記得點贊,收藏,轉發。
如果我有說的不合適,或者不對的地方,請在下面狠狠的批評我。
MyBatis 3
MyBatis-Plus
MyBatis Dynamic SQL
jOOQ: The easiest way to write SQL in Java
Entity Framework Core 概述 - EF Core | Microsoft Learn
資料庫和Doctrine ORM - Symfony開源 - Symfony中國 (symfonychina.com)
Django(ORM查詢、多表、跨表、子查詢、聯表查詢) - 知乎 (zhihu.com)
Sqlalchemy join連表查詢_FightAlita的部落格-CSDN部落格_sqlalchemy 連表查詢
Gorm + Gen自動生成資料庫結構體_Onemorelight95的部落格-CSDN部落格_gorm 自動生成
tangpanqing/aorm: Operate Database So Easy For GoLang Developer (github.com)