我在Go中创建了一个非关系的对象映射,它非常简单。
我有几个看起来像这样的结构:
type Message struct {
Id int64
Message string
ReplyTo sql.NullInt64 `db:"reply_to"`
FromId int64 `db:"from_id"`
ToId int64 `db:"to_id"`
IsActive bool `db:"is_active"`
SentTime int64 `db:"sent_time"`
IsViewed bool `db:"is_viewed"`
Method string `db:"-"`
AppendTo int64 `db:"-"`
}
要创建新消息,我只需运行此功能:
func New() *Message {
return &Message{
IsActive: true,
SentTime: time.Now().Unix(),
Method: "new",
}
}
然后我有一个这个结构的message_crud.go文件,如下所示:
要按唯一列查找消息(例如按id),我运行此函数:
func ByUnique(column string, value interface{}) (*Message, error) {
query := fmt.Sprintf(`
SELECT *
FROM message
WHERE %s = ?
LIMIT 1;
`, column)
message := &Message{}
err := sql.DB.QueryRowx(query, value).StructScan(message)
if err != nil {
return nil, err
}
return message, nil
}
要保存消息(在数据库中插入或更新),我运行此方法:
func (this *Message) save() error {
s := ""
if this.Id == 0 {
s = "INSERT INTO message SET %s;"
} else {
s = "UPDATE message SET %s WHERE id=:id;"
}
query := fmt.Sprintf(s, sql.PlaceholderPairs(this))
nstmt, err := sql.DB.PrepareNamed(query)
if err != nil {
return err
}
res, err := nstmt.Exec(*this)
if err != nil {
return err
}
if this.Id == 0 {
lastId, err := res.LastInsertId()
if err != nil {
return err
}
this.Id = lastId
}
return nil
}
sql.PlaceholderPairs()函数如下所示:
func PlaceholderPairs(i interface{}) string {
s := ""
val := reflect.ValueOf(i).Elem()
count := val.NumField()
for i := 0; i < count; i++ {
typeField := val.Type().Field(i)
tag := typeField.Tag
fname := strings.ToLower(typeField.Name)
if fname == "id" {
continue
}
if t := tag.Get("db"); t == "-" {
continue
} else if t != "" {
s += t + "=:" + t
} else {
s += fname + "=:" + fname
}
s += ", "
}
s = s[:len(s)-2]
return s
}
但每次我创建一个新结构时,例如一个User结构,我必须复制粘贴&#34; crud部分&#34;在上面创建一个user_crud.go文件并替换单词&#34; Message&#34;使用&#34;用户&#34;和单词&#34; message&#34;与&#34;用户&#34;。我重复了很多代码而且它不是很干。有什么我可以做的,不重复这些代码我会重用的东西?我总是有一个save()方法,并且总是有一个ByUnique()函数,我可以返回一个结构并按唯一列搜索。
在PHP中,这很简单,因为PHP不是静态类型的。
这可以在Go吗?
答案 0 :(得分:0)
您可能想要使用ORM。 它们消除了许多你描述的样板代码。
请参阅this question了解&#34;什么是ORM?&#34;
以下是go https://github.com/avelino/awesome-go#orm
的ORM列表我自己从未使用过,所以我无法推荐。主要原因是ORM需要开发人员的大量控制,并引入了不可忽视的性能开销。你需要亲自看看它们是否适合你的用例和/或你是否对#34;魔法&#34;那些图书馆正在进行。
答案 1 :(得分:0)
我不建议这样做,我个人更愿意明确扫描结构并创建查询。
但如果你真的想坚持反思你可以做到:
[indexPath section]
为了您的保存:
func ByUnique(obj interface{}, column string, value interface{}) error {
// ...
return sql.DB.QueryRowx(query, value).StructScan(obj)
}
// Call with
message := &Message{}
ByUnique(message, ...)
我使用的方法会向您推荐:
type Identifiable interface {
Id() int64
}
// Implement Identifiable for message, etc.
func Save(obj Identifiable) error {
// ...
}
// Call with
Save(message)
这样做的好处是可以使用类型系统并只映射应该实际映射的内容。
答案 2 :(得分:0)
您的ByUnique
几乎已经是通用的了。只需拉出不同的部分(桌子和目的地):
func ByUnique(table string, column string, value interface{}, dest interface{}) error {
query := fmt.Sprintf(`
SELECT *
FROM %s
WHERE %s = ?
LIMIT 1;
`, table, column)
return sql.DB.QueryRowx(query, value).StructScan(dest)
}
func ByUniqueMessage(column string, value interface{}) (*Message, error) {
message := &Message{}
if err := ByUnique("message", column, value, &message); err != nil {
return nil, err
}
return message, error
}
您的save
非常相似。您只需要按照以下方式进行通用保存功能:
func Save(table string, identifier int64, source interface{}) { ... }
然后在(*Message)save
内,您只需调用常规Save()
函数。看起来非常简单。
附注:不要使用this
作为方法内对象的名称。有关详细信息,请参阅@OneOfOne的链接。并且不要对DRY着迷。它本身不是一个目标。 Go专注于简单,清晰和可靠的代码。不要创建复杂和脆弱的东西,以避免键入简单的错误处理行。这并不意味着您不应该提取重复的代码。它只是意味着在Go中通常更好地重复简单的代码而不是创建复杂的代码来避免它。
编辑:如果你想使用接口实现Save
,那没问题。只需创建一个Identifier
界面。
type Ider interface {
Id() int64
SetId(newId int64)
}
func (msg *Message) Id() int64 {
return msg.Id
}
func (msg *Message) SetId(newId int64) {
msg.Id = newId
}
func Save(table string, source Ider) error {
s := ""
if source.Id() == 0 {
s = fmt.Sprintf("INSERT INTO %s SET %%s;", table)
} else {
s = fmt.Sprintf("UPDATE %s SET %%s WHERE id=:id;", table)
}
query := fmt.Sprintf(s, sql.PlaceholderPairs(source))
nstmt, err := sql.DB.PrepareNamed(query)
if err != nil {
return err
}
res, err := nstmt.Exec(source)
if err != nil {
return err
}
if source.Id() == 0 {
lastId, err := res.LastInsertId()
if err != nil {
return err
}
source.SetId(lastId)
}
return nil
}
func (msg *Message) save() error {
return Save("message", msg)
}
可能会爆炸的一件事就是对Exec
的召唤。我不知道你正在使用什么包,如果你传递一个接口而不是实际的结构,Exec
可能无法正常工作,但它可能会起作用。也就是说,我可能只是传递标识符而不是增加这个开销。