zoukankan      html  css  js  c++  java
  • golang连接达梦数据库的一个坑

    golang连接达梦数据库的一个坑

    有一次项目中用到了达梦数据库,后端语言使用的golang,达梦官方并未适配专门的golang连接方式,正一筹莫展的时候发现达梦提供了odbc的连接,这样可以使用类似mssqlodbc连接方式连接达梦数据库。

    使用的达梦数据库版本为DM8

    达梦数据库开启odbc连接

    参考博客1参考博客2

    参照上面两个博客内容配置odbc连接

    golang代码

    一些参考文档:

    package main
    import (
    	"fmt"
    	_ "github.com/alexbrainman/odbc"  // google's odbc driver
    	"github.com/go-xorm/xorm"
    	"xorm.io/core"
    	"github.com/axgle/mahonia"
    )
    
    type Address struct {
        Addressid int64 `xorm:"addressid"`
        Address1 string `xorm:"address1"`
        Address2 string `xorm:"address2"`
        City string `xorm:"city"`
        Postalcode string `xorm:"postalcode"`
    }
    
    // 字符串解码函数,处理中文乱码
    func ConvertToString(src string, srcCode string, tagCode string) string {
        srcCoder := mahonia.NewDecoder(srcCode)
        srcResult := srcCoder.ConvertString(src)
        tagCoder := mahonia.NewDecoder(tagCode)
        _, cdata, _ := tagCoder.Translate([]byte(srcResult), true)
        result := string(cdata)
        return result
    }
    
    func main() {
    	engine, err := xorm.NewEngine("odbc", "driver={DM8 ODBC DRIVER};server=127.0.0.1:5236;database=DM;uid=SYSDBA;pwd=password;charset=utf8")
    	if err != nil {
    		fmt.Println("new engine got error:", err)
    		return
    	}
    	engine.ShowSQL(true)//控制台打印出生成的SQL语句;
    	engine.Logger().SetLevel(core.LOG_DEBUG)
    	if err := engine.Ping(); err != nil {
    		fmt.Println("ping got error:", err)
    		return
    	}
    
    	// 1) sql查询
    	results, err := engine.Query("select addressid, address1, address2, city, postalcode from person.address limit 5 offset 2")
    	if err != nil {
    		fmt.Println("查询出错:", err)
    		return
    	}
    	for i, e := range results {
    		fmt.Printf("%v	", i)
    		for k, v := range e {
    			// 达梦数据库中文默认为gbk
    			fmt.Printf("%v=%v	", k, ConvertToString(string(v), "gbk", "utf-8"))
    		}
    		fmt.Printf("
    ")
    	}
    	fmt.Println("*******************************")
    	// 2) 使用struct 映射结果
    	engine.SetMapper(core.SameMapper{})
    	var sliceOfAddress []Address
    	err = engine.Table("person.address").Limit(5, 0).Find(&sliceOfAddress)
    	if err != nil {
    		fmt.Println("查询出错:", err)
    		return
    	}
    	for i,e := range sliceOfAddress {
    		e.Address1 = ConvertToString(e.Address1, "gbk", "utf-8")
    		e.Address2 = ConvertToString(e.Address2, "gbk", "utf-8")
    		e.City = ConvertToString(e.City, "gbk", "utf-8")
    		fmt.Printf("%v=%v
    ", i, e)
    	}
    }
    

    1)解决 golang.org/x/ 下包下载不下来的问题

    https://studygolang.com/articles/19051?fr=sidebar

    https://studygolang.com/articles/24075?fr=sidebar

    2)无效的表或视图名[person.address](这个也是最坑的一点)

    原因:我们使用的是odbc的方式连接达梦数据库,实际上使用的是mssql的驱动,第一个1) SQL查询结果是OK的,但是2) struct 查询就会报错:

    [xorm] [info]  2020/06/08 16:52:40.183731 [SQL] SELECT TOP 5 "addressid", "address1", "address2", "city", "postalcode" FROM "person.address"
    查询出错: SQLPrepare: {42S02} 第1 行附近出现错误:
    无效的表或视图名[person.address]
    

    通过日志发现,xorm吧没一个字段和表名都添加上了双引号:SELECT TOP 5 "addressid", "address1", "address2", "city", "postalcode" FROM "person.address"这个sql在mssql中执行是没问题的,但是放到达梦数据库中就会报错,因为达梦不支持双引号包裹字段、命名空间、表名:

    这样就很坑爹了,我尝试着修改结构体的xorm:"addressid"去掉其中的双引号,但是问题还存在,最后想到打印出来的sql中待了双引号,说明xorm后台还是拼接的sql语句,如果找到拼接sql语句的代码然后去掉其中的双引号不是就好了么。于是跟踪代码查找拼接sql的代码:

    解决方式一)

    1、engine.Table("person.address").Limit(5, 0).Find(&sliceOfAddress)

    先找到engin模块的Find()方法:

    代码路径:src.github.com/go-xorm/xorm/engine.go

    // Find retrieve records from table, condiBeans's non-empty fields
    // are conditions. beans could be []Struct, []*Struct, map[int64]Struct
    // map[int64]*Struct
    func (engine *Engine) Find(beans interface{}, condiBeans ...interface{}) error {
    	session := engine.NewSession()
    	defer session.Close()
    	return session.Find(beans, condiBeans...)
    }
    

    发现其实调用的是session.Find() 方法

    2、src.github.com/go-xorm/session_find.go

    // Find retrieve records from table, condiBeans's non-empty fields
    // are conditions. beans could be []Struct, []*Struct, map[int64]Struct
    // map[int64]*Struct
    func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
    	if session.isAutoClose {
    		defer session.Close()
    	}
    	return session.find(rowsSlicePtr, condiBean...)
    }
    

    发现实际上调用的是session.find(rowsSlicePtr, condiBean...)

    func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
    	
    	defer session.resetStatement()
    
    	// 代码省略 。。。
    
    	var sqlStr string
    	var args []interface{}
    	var err error
        // 此处就是拼接sql的代码
    	if session.statement.RawSQL == "" {
    		// 代码省略 。。。
    	} else {
    		sqlStr = session.statement.RawSQL
    		args = session.statement.RawParams
    	}
        // 获得配置信息判断当前数据库类型
    	uri := session.engine.Dialect().URI()
    	// 判断当前是否是达梦数据库
    	if uri.DbType == "mssql" && uri.DbName == "DM" {
    		newSqlStr := strings.Replace(sqlStr, """, "", -1) // 去掉双引号
    		sqlStr = newSqlStr
    	}
    
    	// 代码省略 。。。
    	return session.noCacheFind(table, sliceValue, sqlStr, args...)
    }
    

    通过session.engine.Dialect().URI()获得配置信息,这段代码怎么来的,实际上是在xorm.NewEngine()的时候会解析配置信息,并赋值给enginedialect属性,代码位置:src.github.com/go-xorm/xorm/xorm.go

    engine := &Engine{
    		db:             db,
    		dialect:        dialect,
    		Tables:         make(map[reflect.Type]*core.Table),
    		mutex:          &sync.RWMutex{},
    		TagIdentifier:  "xorm",
    		TZLocation:     time.Local,
    		tagHandlers:    defaultTagHandlers,
    		cachers:        make(map[string]core.Cacher),
    		defaultContext: context.Background(),
    	}
    

    找到sql之后去掉双引号即可,因为做了判断只有是达梦的类型数据库的时候才修改,所以不会影响其他类型的数据库。至此问题得到了解决。

    解决方式二)

    上面的方式一是一种解决方案,其实有更简便的,因为我们在创建engine的时候已经确定了dialect类型为dialect_mssql,找到src.github.com/go-xorm/xorm/dialect_mssql.go找到方法Quote稍作修改即可:

    func (db *mssql) Quote(name string) string {
    	fmt.Println("Quote -> ", db.URI().DbName) // DM
    	if  db.URI().DbName == "DM" { // 如果是达梦数据库不添加双引号
    		return name
    	}
    	return """ + name + """
    }
    

    这样,是在拼接SQL之前修改了逻辑,二方式一是在拼接之后再去掉引号,方式二更方便一点。
    注意:
    查阅最新的文档,发现最新的xorm.io/core v1.0.1版本已经支持使用engine.Dialect().SetQuotePolicy(core.QuotePolicyNone)来设置引号策略。如果你使用的是该版本,直接设置即可。
    Dialect结构体QuotePolicy常量值
    输出结果:

    [xorm] [info]  2020/06/08 17:14:18.061667 PING DATABASE odbc
    [xorm] [info]  2020/06/08 17:14:19.315349 [SQL] select addressid, address1, address2, city, postalcode from person.address limit 5 offset 2
    0       ADDRESSID=3     ADDRESS1=青山区青翠苑1号        ADDRESS2=       CITY=武汉市青山区       POSTALCODE=430080
    1       ADDRESSID=4     ADDRESS1=武昌区武船新村115号    ADDRESS2=       CITY=武汉市武昌区       POSTALCODE=430063
    2       ADDRESSID=5     ADDRESS1=汉阳大道熊家湾15号     ADDRESS2=       CITY=武汉市汉阳区       POSTALCODE=430050
    3       ADDRESSID=6     ADDRESS1=洪山区保利花园50-1-304 ADDRESS2=       CITY=武汉市洪山区       POSTALCODE=430073
    4       ADDRESSID=7     ADDRESS1=洪山区保利花园51-1-702 ADDRESS2=       CITY=武汉市洪山区       POSTALCODE=430073
    *******************************
    [xorm] [info]  2020/06/08 17:14:19.324291 [SQL] SELECT TOP 5 addressid, address1, address2, city, postalcode FROM person.address
    0={1 洪山区369号金地太阳城56-1-202  武汉市洪山区 430073}
    1={2 洪山区369号金地太阳城57-2-302  武汉市洪山区 430073}
    2={3 青山区青翠苑1号  武汉市青山区 430080}
    3={4 武昌区武船新村115号  武汉市武昌区 430063}
    4={5 汉阳大道熊家湾15号  武汉市汉阳区 430050}
    

    参考文档

    xorm的操作指南

    xorm的pkg文档

    go语言中文文档

  • 相关阅读:
    关于管理单元初始化失败的解决方法
    如何快速在两台电脑之间传输大文件
    拿到商标受理通知书就可以打上“TM”就可以使用吗?
    山里王土蜂蜜
    我的博客今天1岁344天了,我领取了新锐博主徽章
    Winxp Stop c0000218 unknown hard error
    设置网易博客、新浪博客、博客园的windows live writer帐户支持
    邮件变成了Winmail.dat
    outlook 2007 .pst文件过大,提示:“磁盘空间已满,无法删除邮件”
    git 本地给远程仓库创建分支 三步法
  • 原文地址:https://www.cnblogs.com/bartggg/p/13066944.html
Copyright © 2011-2022 走看看