首页 > 数据库 >Gorm与数据库

Gorm与数据库

时间:2024-02-17 21:34:26浏览次数:36  
标签:null models 数据库 Gorm gin Article Id id

本篇是对上一篇博客的继续补充(当然也可以视作独立的一篇)。

友情提示,本篇博客中用到了数据库可视化工具Navicat。另外,本篇博客的所有代码都可以从这里获取。

建立大致项目结构

Gorm是一个可以操作数据库的框架。为了更方便观察对数据库的操作,我们先建立一个基础从项目结构:
只有一个路由组,路由组里只有一个首页的路由。还要配置一下go mod的相关项。

先使用go mod创建一个项目,比如项目名称就叫gormnote。

go mod init gormnote

然后获取一下要用到的包。

go get github.com/gin-gonic/gin
go get gorm.io/driver/mysql
go get gorm.io/gorm

然后就要建立项目结构了:
和之前的一样,在main.go中调用路由组。

package main

import (
	"gormnote/routers"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/**/*")
	routers.DefaultRoutersInit(r)
	r.Run()
}

// github.com/pilu/fresh

routers/defaultRouters.go中配置路由。

package routers

import (
	"gormnote/controllers/defaults"

	"github.com/gin-gonic/gin"
)

func DefaultRoutersInit(r *gin.Engine) {
	defaultRouters := r.Group("/")
	{
		defaultRouters.GET("/", defaults.DefaultController{}.Index)
	}
}

controllers/default/defaultController.go中实现路由逻辑。

package defaults //注意这里的包名不要定义成default关键字,稍微区分一下。

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type DefaultController struct {
}

func (con DefaultController) Index(ctx *gin.Context) {
	// ctx.String(http.StatusOK, "首页")
	ctx.HTML(http.StatusOK, "default/index.html", gin.H{})
}

如果只是观察数据库的变化,不需要再渲染一张网页,但为了讲究一点,我们再在templates/default中写一个简单的index.html。

{{define "default/index.html"}}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>首页</h2>
</body>
</html>
{{end}}

到这里,静态的网页基本结构就写好了,之后就要和数据库建立联系。

新建一个models/core.go
这个文件里包含了一个init函数,里面是数据库的连接方法。其中DB就是数据库。
至于那一长串dsn

  • 第一个root是用户名。
  • 第二个root也就是冒号后面那个是密码。这里简单起见我就都设置成了root
  • 括号里那部分是ip和端口。
  • /?中间的是数据库的名称,注意是数据库的名称,不是连接的名称。
  • charset=后面的是编码格式,好像一般都会用utf8mb4
  • 其他部分一般不用改动。
package models

import (
	"fmt"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB
var err error

func init() {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
	dsn := "root:root@tcp(127.0.0.1:3306)/gogin?charset=utf8mb4&parseTime=True&loc=Local"
	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})

	if err != nil {
		fmt.Printf("err: %v\n", err)
		fmt.Println("数据库连接失败!")
	} else {
		fmt.Println("数据库连接成功!")
	}
}

有了数据库以后我们要定义结构体和数据库实现映射关系。
比如我们在数据库里创建了一个user用户表,其中包含了这么几项属性:

  • id,用户id,具有自增属性。
  • username,用户名。
  • age,用户年龄。
  • email,邮箱。
  • add_time,创建时间(是一个时间戳)。

那么我们可以在models下创建user.go,在里面定义结构体User。

package models

type User struct {
	Id       int
	Username string
	Age      int
	Email    string
	AddTime  int
}

// 结构体对应的表明,Gorm给了默认情况的表名,这里是自定义表名。
// 意思是User这个结构体连接的数据表名字是user。
func (User) TableName() string {
	return "user"
}

若要查询数据库中的内容,需要修改路由中的逻辑。由于数据并不是一个,所以我们查询全部数据时用一个切片来接收数据。
models.DB.Find()会返回一个DB结构体,其中有一项err属性,不过后来我试验这里的err判断似乎并没有用(就忽略不计了)。

func (con DefaultController) Index(ctx *gin.Context) {
	// ctx.String(http.StatusOK, "首页")
	userList := []models.User{} //定义一个User类型的切片。
	if err := models.DB.Find(&userList).Error; err != nil {
		fmt.Printf("err: %v\n", err)
	}

	ctx.HTML(http.StatusOK, "default/index.html", gin.H{
		"userList": userList,
	})
}

现在数据被传到了前台,还要再渲染出来。这里可以使用range语句。在body里添加这些。

<ul>
    {{range $user := .userList}}
    <li> {{$user}} </li>
    {{end}}
</ul>

至此,基本项目结构就完成了。

数据库的操作

经典增删改查。

增加数据

先来看增加数据。
配置一个新的Add路由。

func (con DefaultController) Add(ctx *gin.Context) {
	// 增加数据
	// 实例化一个结构体并将它添加到数据库里。
	user := models.User{
		Username: "hlry",
		Age:      20,
		Email:    "[email protected]",
		AddTime:  1708060539,
	}
	if err := models.DB.Create(&user).Error; err != nil {
		fmt.Println("数据添加失败!")
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Println("数据添加成功!")
	}

	// 查询数据,验证一下确实是添加进去了。
	userList := []models.User{}
	if err := models.DB.Find(&userList).Error; err != nil {
		fmt.Printf("err: %v\n", err)
	}

	ctx.HTML(http.StatusOK, "default/index.html", gin.H{
		"userList": userList,
	})
}

这里并不需要写Id的值,因为它是可以自增,而且是主键。如果强行写了一个已经存在的id值,那么就会报错。

还要记得在路由组里配置路由。

func DefaultRoutersInit(r *gin.Engine) {
	defaultRouters := r.Group("/")
	{
		defaultRouters.GET("/", defaults.DefaultController{}.Index)
		defaultRouters.GET("/add", defaults.DefaultController{}.Add)
	}
}

删除数据

配置路由。
有两种删除数据的方法。可以实例化结构体时直接指定数据的id。也可以实例一个空的结构体,并提供要删除的数据id。

func (con DefaultController) Delete(ctx *gin.Context) {
	// 删除数据
	// user := models.User{Id: 2} //指定删除的数据的id。
	// models.DB.Delete(&user)

	user := models.User{}
	models.DB.Where("id = ?", 2).Delete(&user) //查询到id=2的数据并删除。

	userList := []models.User{}
	if err := models.DB.Find(&userList).Error; err != nil {
		fmt.Printf("err: %v\n", err)
	}

	ctx.HTML(http.StatusOK, "default/index.html", gin.H{
		"userList": userList,
	})
}

查询数据

前面两种操作其实一直在用查询数据,只不过是没有任何限制条件地查询所有数据,这里介绍一些条件查询。
配置路由(不配置也可以)。

func (con DefaultController) Query(ctx *gin.Context) {
	userList := []models.User{}
	// 查询id>2的数据。
	// models.DB.Where("id > ?", 2).Find(&userList)

	// 查询id=3,4,6的数据,?是占位符,用切片打包要查询的数据的id。
	models.DB.Where("id in ?", []int{3, 4, 6}).Find(&userList)

	ctx.HTML(http.StatusOK, "default/index.html", gin.H{
		"userList": userList,
	})
}

修改数据

修改数据的方法就是,先找到要修改的数据,再用结构体赋值的方法对其修改。

func (con DefaultController) Update(ctx *gin.Context) {
	user := models.User{}                 //先实例化一个空结构体。
	models.DB.Where("id = 4").Find(&user) //找到要修改的数据。
	//修改数据。
	user.Username = "kzh"
	user.Age = 19
	user.Email = "[email protected]"
	models.DB.Save(&user)

	userList := []models.User{}
	if err := models.DB.Find(&userList).Error; err != nil {
		fmt.Printf("err: %v\n", err)
	}

	ctx.HTML(http.StatusOK, "default/index.html", gin.H{
		"userList": userList,
	})
}

关联查询

Belongs To

现在在数据库里新增一些数据。
article,文章:

  • id,文章id。
  • title,文章标题。
  • categary_id,文章分类对应的类别id。

article_categary,文章分类:

  • id,类别的id。
  • categary,类别。

假如我们现在要查询每篇文章及其对应的id。因为每篇文章只能有一个分类,而一个分类可以包含多篇文章,所以可以说文章属于分类的一个实例。

那么我们需要这样定义两个结构体。

package models

type Article struct {
	Id              int
	Title           string
	CategaryId      int
	ArticleCategary ArticleCategary `gorm:"foreignKey:CategaryId;references:Id"`
}

func (Article) TableName() string {
	return "article"
}
package models

type ArticleCategary struct {
	Id       int
	Categary string
}

func (ArticleCategary) TableName() string {
	return "article_categary"
}

gorm:"foreignKey:CategaryId;references:Id"这段代码是重写外键和引用,Gorm有默认值,但我们已经建立的数据库的键与默认值不太匹配,所以要自己重新(另外,我觉得还是自己声明比较好,虽然默认值也就是和表名有关,但毕竟要约束表名,而且被封装起来看不到。自己重新一眼就能看出到底是哪个和哪个相连)。意思就是,现在要查询文章及其分类,那么就用Article.CategaryIdArticleCategary.Id进行连接。也就是说,在数据库中,对于article表里的每一篇文章,我们都要用文章的分类的id即categary_idarticle_categary表里进行匹配,匹配的就是和categary_id相同的id。说得再简单一点就是,拿到一篇文章以后,不急着将它返回,先拿着它的categary_id值去article_categary表里的id属性里找一样的值,找到以后把文章和找到的这个id对应的分类绑定在一起返回。

使用预加载查询到带分类的文章。要注意的是,Preload()括号里的内容要与结构体里的属性名一致,而不是与类型一致。

func (con DefaultController) Query_bt(ctx *gin.Context) {

	articleList := []models.Article{}
	models.DB.Preload("ArticleCategary").Find(&articleList)

	ctx.HTML(http.StatusOK, "default/article.html", gin.H{
		"results": articleList,
	})
}

Has One

恕我无能,笔者搞了半天也没搞明白到底Belongs ToHas One到底有什么区别,除了描述的方向相反以外,其他的方面真看不出来猫腻儿,感觉是同一回事。

Has Many

前面是每篇文章都属于一个分类。假如现在要查询每个分类及分类下的每篇文章,那么这就是一对多。
我们需要在ArticleCategary里再加上一个属性。

type ArticleCategary struct {
	Id       int
	Categary string
	Article  []Article `gorm:"foreignKey:CategaryId;references:Id"`
}

这个意思就是用一个名为Article的结构体切片去存储每一个Article,先不说后面那一串。到这里,可能会有些疑问,ArticleArticleCategary互相嵌套,这样不会出问题吗?实践验证,最后的结果是这两个互相嵌套的属性最后会以空值结束。就比如,我查到了一篇文章,那么它的结果大概是这样的:

"Id":1,
"Title":"新闻111",
"CategaryId":1,
"ArticleCategary":{
    "Id":1,
    "Categary":"类别1",
    "Article":null
}

可以看到,虽然整个Article里最后又嵌套的一个Article,但是这个Article会直接被设置为空值。
同理,ArticleCategary也大致一样。

"Id":1,
"Categary":"类别1",
"Article":[
    {
        "Id":1,
        "Title":"新闻111",
        "CategaryId":1,
        "ArticleCategary":{
            "Id":0,
            "Categary":"",
            "Article":null
        }
    },
    {
        "Id":2,
        "Title":"title222",
        "CategaryId":1,
        "ArticleCategary":{
            "Id":0,
            "Categary":"",
            "Article":null
        }
    }
]

然后再说gorm:"foreignKey:CategaryId;references:Id"这一串东西。这和前面的Belongs To很像,也是重新外键和引用。这里的意思就是,在数据库里查询article_categary时,我们要用ArticleCategary.Id也就是的article_categary表里的idarticle表里进行匹配,把和Article.CategaryId也就是article表里categary_id值一样的文章拿出来存放到ArticleCategary.Article切片中。再说简单点就是用文章分类的id去找这个类别下的所有文章。

Many To Many

这部分稍微有些复杂。
假设现在每篇文章又多了一个标签Tags属性,其类型是Tags类型的切片,也就是说,一篇文章可以有多个标签,那么我们又需要定义一下Tags类型。同时,在定义Tags类型时,也要考虑到一个标签可以给多个文章使用,也就是一个标签可以属于多个文章,那么在Tags结构体里还要加上一个Article属性,其类型是Article类型的切片。

package models

type Tags struct {
	Id      int
	Tag     string
	Article []Article `gorm:"many2many:article_tags;foreignKey:Id;joinForeignKey:TagID;References:Id;joinReferences:ArticleId"`
}

func (Tags) TableName() string {
	return "tags"
}

我知道你看到了那一大串密密麻麻的字母,但先别管。我们还要再修改一下Article的定义。

type Article struct {
	Id              int
	Title           string
	CategaryId      int
	ArticleCategary ArticleCategary `gorm:"foreignKey:CategaryId;references:Id"`
	Tags            []Tags          `gorm:"many2many:article_tags;foreignKey:Id;joinForeignKey:ArticleID;References:Id;joinReferences:TagId"`
}

那么现在,数据库里有了新的tags表,如果我们想查询每一篇文章并附带着它们的标签,只有这些还不够。因为是多对多关系,不可能在数据库的articletags这两个表里实现,还需要再引入第三张表,比如就叫article_tags表。这个表有两个属性,一个是article_id,也就是文章id,另一个是tags_id也就是标签id。把所有的关联起来的文章标签的id存放在这张表里。这张表只有这两项属性,不需要拥有自己的id,因为它只是起到一个连接的作用,我们不会特定的要查询某个文章标签的关联组合。

把这张表也定义成结构体。

package models

type ArticleTags struct {
	ArticleId int
	TagId     int
}

func (ArticleTags) TableName() string {
	return "article_tags"
}

接下来,我们的查询思路就是,对于article表里的每一篇文章,我们用它的id在article_tags表里找到和所有这篇文章id一样的article_id。找到以后用那一行数据的tags_id属性再去tags表里找和这个tags_id一样的id。找到对应的id以后再把整行数据作为结果放到结构体ArticleTags切片里。最后再把带有Tags切片的Article作为查询结果返回。
思路有了,那么问题就是该怎么让几个结构体或者说数据表关联起来。这就需要解释一下那一大串字母了。
gorm:"many2many:article_tags;foreignKey:Id;joinForeignKey:ArticleID;References:Id;joinReferences:TagId"
这里也是像之前一样重写了外键和引用。

  • many2many:article_tags:意思是连接名为article_tags的数据表,我们要拿哪个数据表做中间连接就用哪个表的名称。
  • foreignKey:Id:我们这里是要查询article,所有的重写都是定义在Article结构体里的。这段代码意思就是要用Article里的Id去连接ArticleTags结构体或者说article_tags表。
  • joinForeignKey:ArticleID:将ArticleTags连接起来的结构体是ArticleTags。这段代码的意思就是要用前面拿到的Article.Id去找匹配的ArticleTags.ArticleId
    先跳过References:Id先看最后一部分。
  • joinReferences:TagId:在找到和Article.Id去找匹配的ArticleTags.ArticleId以后,要根据这个ArticleTags.ArticleId找到对应的TagsId
  • References:Id:我们这里是希望找到Article及其所有的Tags,那么这段代码的意思就是用前面的TagsIdTags里找到匹配的Tags.Id
    最后,因为Tags.Id就是Tags结构体或者说tags表的主键,所以可以找到对应的标签,并将整个数据存入Article.[]Tags里。

总结一下就是:

  • many2many就是连接两个多对多关系的表(简单起见,以下就叫A和B,并且要查询A及其所带的B属性)。
  • foreignKey就是A表里要拿去在连接表里找一样的值的属性。
  • joinForeignKey就是要去和A表里拿过来的那条属性匹配的属性。
  • joinReferences就是找到连接表里那条相匹配的数据以后,需要用它的值去B表里查询的属性。
  • References:Id就是要被查询的值在B表里查询的那个属性。

整个流程就是:

  • A -> A.foreignKey
  • A.foreignKey -> A_B.joinForeignKey
  • A_B.joinForeignKey -> A_B.joinReferences
  • A_B.joinReferences -> B.References
  • B.References -> B
    这里起始的A和结束的B分别代指A表和B表里的一条或多条数据。

解释完上面这些,就可以直接查询了。查询Tags及其所拥有的Article也是同样的方法。

func (con DefaultController) Query_mtm(ctx *gin.Context) {
	articleList := []models.Article{}
	models.DB.Preload("Tags").Find(&articleList)
	// ctx.HTML(http.StatusOK, "default/article.html", gin.H{
	// 	"results": articleList,
	// })
	ctx.JSON(http.StatusOK, gin.H{
		"articleList": articleList,
	})

	tagsList := []models.Tags{}
	models.DB.Preload("Article").Find(&tagsList)
	// ctx.HTML(http.StatusOK, "default/article.html", gin.H{
	// 	"results": tagsList,
	// })
	ctx.JSON(http.StatusOK, gin.H{
		"tagsList": tagsList,
	})
}

得到的结构大概长这样:

{
    "articleList":[
        {
            "Id":1,
            "Title":"新闻111",
            "CategaryId":1,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":1,
                    "Tag":"tag111",
                    "Article":null
                },
                {
                    "Id":2,
                    "Tag":"tag222",
                    "Article":null
                },
                {
                    "Id":5,
                    "Tag":"tag555",
                    "Article":null
                }
            ]
        },
        {
            "Id":2,
            "Title":"title222",
            "CategaryId":1,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":1,
                    "Tag":"tag111",
                    "Article":null
                },
                {
                    "Id":3,
                    "Tag":"tag333",
                    "Article":null
                }
            ]
        },
        {
            "Id":3,
            "Title":"title333",
            "CategaryId":2,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":5,
                    "Tag":"tag555",
                    "Article":null
                },
                {
                    "Id":6,
                    "Tag":"tag666",
                    "Article":null
                }
            ]
        },
        {
            "Id":4,
            "Title":"title444",
            "CategaryId":3,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":2,
                    "Tag":"tag222",
                    "Article":null
                },
                {
                    "Id":3,
                    "Tag":"tag333",
                    "Article":null
                },
                {
                    "Id":4,
                    "Tag":"tag444",
                    "Article":null
                }
            ]
        },
        {
            "Id":5,
            "Title":"title555",
            "CategaryId":4,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":1,
                    "Tag":"tag111",
                    "Article":null
                },
                {
                    "Id":2,
                    "Tag":"tag222",
                    "Article":null
                },
                {
                    "Id":5,
                    "Tag":"tag555",
                    "Article":null
                },
                {
                    "Id":6,
                    "Tag":"tag666",
                    "Article":null
                }
            ]
        },
        {
            "Id":6,
            "Title":"title666",
            "CategaryId":3,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":1,
                    "Tag":"tag111",
                    "Article":null
                },
                {
                    "Id":2,
                    "Tag":"tag222",
                    "Article":null
                },
                {
                    "Id":6,
                    "Tag":"tag666",
                    "Article":null
                }
            ]
        },
        {
            "Id":7,
            "Title":"title777",
            "CategaryId":4,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":1,
                    "Tag":"tag111",
                    "Article":null
                },
                {
                    "Id":3,
                    "Tag":"tag333",
                    "Article":null
                }
            ]
        },
        {
            "Id":8,
            "Title":"title888",
            "CategaryId":2,
            "ArticleCategary":{
                "Id":0,
                "Categary":"",
                "Article":null
            },
            "Tags":[
                {
                    "Id":1,
                    "Tag":"tag111",
                    "Article":null
                },
                {
                    "Id":2,
                    "Tag":"tag222",
                    "Article":null
                }
            ]
        }
    ]
}

Gorm对数据库的基本操作大概就是这样。以后如果还有什么再补充吧。

标签:null,models,数据库,Gorm,gin,Article,Id,id
From: https://www.cnblogs.com/luviichann/p/18018450

相关文章

  • Navicat Premium 16:打破数据库界限,实现高效管理mac/win版
    NavicatPremium16是一款功能强大的数据库管理工具,旨在帮助用户更轻松地连接、管理和保护各种数据库。该软件支持多种数据库系统,如MySQL、Oracle、SQLServer、PostgreSQL等,并提供了直观的图形界面,使用户能够轻松地完成各种数据库操作。→→↓↓载NavicatPremium16mac/win版......
  • 《安富莱嵌入式周报》第332期:铷时钟控制板,航天战斗机C++代码标准,免费开源芯片设计,在线
    周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104 视频版https://www.bilibili.com/video/BV1tU421d7ZK/目录:1、Rubidium铷时钟控制板2、开源小设计,简易万用表连通性测试仪3、免费开源芯片设计软件Electric4、在线电路仿......
  • Asp.Net Core访问阿里云MongoDB云数据库
    Asp.NetCore访问阿里云MongoDB云数据库选择.NetCore技术栈开发跨平台软件解决方案,投入少,产出快,有助于企业内部降本增效。MongoDB的实体类增加字段不用迁移数据库,适合需求经常变化的应用场景。如果是企业内部小型应用,拉一个MongoDB容器即可,如果要进一步考虑多节点冗余,高可用,异地......
  • (学习日记)四、数据库MySQL
    1.初识网站默认编写的网站是静态的动态需要用到web框架的功能fromflaskimportFlask,render_templateapp=Flask(__name__)@app.route("/index")defindex():users={"Wu":"1","Liu":"2","Deng":"3"}#此处的数......
  • MSSQL Server 备份数据库脚本
    备份数据库脚本,实现如下脚本1.定时备份2. 平日以N开头,周一以W开头,每月1号以M开头,每年1月1日以Y开头, (保留最近7天,4周,12月和10年的备份)3.每周一还原备份到test库,并将密码改为123 declare@bakfilevarchar(100),@dbvarchar(100),@prevarchar(......
  • MySQL 主从数据库同步是如何实现的?
    回顾我们之前讲MySQL相关的几节课程,你会发现 主从同步有多重要:解决数据可靠性的问题需要用到主从同步;解决MySQL服务高可用要用到主从同步;应对高并发的时候,还是要用到主从同步。我们在运维MySQL集群时,遇到的很多常见的问题,比如说:为什么从节点故障会影响到主节点?为......
  • 数据库概论笔记
    数据库概论笔记第一章绪论数据库的四个基本概念数据库发展阶段数据模型ER图常用数据模型层次模型网状模型关系模型数据库系统结构数据库系统组成......
  • 数据库归档工具 pt-archiver 的使用
    简介pt-archiver属于大名鼎鼎的percona工具集的一员,是归档MySQL大表数据的最佳轻量级工具之一。安装官网下载地址,选择PerconaToolkit版本和操作系统平台,具体如下图解压缩,所有工具都在bin目录下使用注意:pt-archiver操作的表必须有主键/root/test_archiver/pt-3......
  • 【数据库】postgressql设置数据库执行超时时间
    在这篇文章中,我们将深入探讨PostgreSQL数据库中的一个关键设置:SETstatement_timeout。这个设置对于管理数据库性能和优化查询执行时间非常重要。让我们一起来了解它的工作原理以及如何有效地使用它。什么是statement_timeout?statement_timeout是一个PostgreSQL服务器参数,用于设......
  • 创建企业级地理数据库oracle
    创建oracle地理数据库sde之前写过一篇在postgres数据库中创建sde的教程,由于工作需求,现需要在oracle数据库中创建sde并连接使用,现把主要步骤记录下来,以备后续查看方便。有一说一,开源的postgres数据库创建sde不要太方便,关键是人家还有自己的PostGIS插件以支持空间数据表达,闭源的or......