Golang在MVC重构中遇到的数据库关闭问题
Golang在MVC重构中遇到的数据库关闭问题
我的后端原本运行正常,但在添加路由并将代码重构为MVC模式的过程中遇到了问题。
在添加了 createJobsTable 方法后,出现了 database is closed 错误。
我想知道为什么会发生这种情况以及如何解决这个问题。
请参考以下Go文件片段:
// 文件 main.go
package main
import (
"fmt"
"net/http"
"github.com/lpvm/newest_sapo_jobs/controllers"
)
func main() {
uc := controllers.NewUserController()
fmt.Println("usercontroller: ", uc)
jc := controllers.NewJobController()
http.Handle("/favicon.ico", http.NotFoundHandler())
http.HandleFunc("/", jc.ServeIndex)
http.ListenAndServe(":8079", nil)
}
// 文件 controllers.go
package controllers
import (
"html/template"
"net/http"
"sync"
"github.com/lpvm/newest_sapo_jobs/models"
)
type UserController struct{}
type JobController struct{}
func NewUserController() *UserController {
return &UserController{}
}
var tpl *template.Template
func NewJobController() *JobController {
return &JobController{}
}
func (j JobController) ServeIndex(w http.ResponseWriter, req *http.Request) {
tpl.ExecuteTemplate(w, "index.gohtml", nil)
}
var db = models.OpenDb()
var dbModels = models.NewDbModel()
func init() {
tpl = template.Must(template.ParseGlob("templates/*.gohtml"))
dbModels.InitTables(db)
}
// 文件 models.go
package models
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
const dbName = "jobs.db"
func checkError(e error) {
if e != nil {
log.Fatal(e)
}
}
type DbModel struct{}
func NewDbModel() *DbModel {
return &DbModel{}
}
func OpenDb() *sql.DB {
db, err := sql.Open("sqlite3", dbName)
checkError(err)
defer db.Close()
return db
}
func (dbm DbModel) InitTables(db *sql.DB) {
dbm.createJobsTable(db)
}
func (dbm DbModel) createJobsTable(db *sql.DB) {
stmt, err := db.Prepare(`CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY NOT NULL,
date TEXT NOT NULL);`)
checkError(err)
_, err = stmt.Exec()
checkError(err)
}
更多关于Golang在MVC重构中遇到的数据库关闭问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
更新你在数据库中的代码,如果 defer.close 返回的数据库处于关闭状态。 *sql.DB 是数据库连接池,不要关闭它!
func OpenDb() *sql.DB {
db, err := sql.Open("sqlite3", dbName)
checkError(err)
// defer db.Close()
return db
}
更多关于Golang在MVC重构中遇到的数据库关闭问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
问题出在 OpenDb() 函数中的 defer db.Close() 语句。在重构为MVC模式后,数据库连接在初始化完成后立即被关闭,导致后续操作无法使用。
根本原因:
init()函数在controllers包初始化时调用dbModels.InitTables(db)db通过models.OpenDb()获取,但该函数返回前就执行了defer db.Close()- 当
InitTables()执行完毕后,数据库连接立即关闭 - 后续任何数据库操作都会遇到
database is closed错误
解决方案:
修改 models.go 文件,移除 defer db.Close() 并改为在应用退出时关闭连接:
package models
import (
"database/sql"
"log"
"sync"
_ "github.com/mattn/go-sqlite3"
)
const dbName = "jobs.db"
var (
db *sql.DB
dbOnce sync.Once
)
func checkError(e error) {
if e != nil {
log.Fatal(e)
}
}
type DbModel struct{}
func NewDbModel() *DbModel {
return &DbModel{}
}
// 获取数据库连接(单例模式)
func GetDB() *sql.DB {
dbOnce.Do(func() {
var err error
db, err = sql.Open("sqlite3", dbName)
checkError(err)
// 测试连接
err = db.Ping()
checkError(err)
})
return db
}
// 关闭数据库连接
func CloseDB() {
if db != nil {
db.Close()
}
}
func (dbm DbModel) InitTables() {
db := GetDB()
dbm.createJobsTable(db)
}
func (dbm DbModel) createJobsTable(db *sql.DB) {
stmt, err := db.Prepare(`CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY NOT NULL,
date TEXT NOT NULL);`)
checkError(err)
defer stmt.Close()
_, err = stmt.Exec()
checkError(err)
}
同时修改 controllers.go:
package controllers
import (
"html/template"
"net/http"
"sync"
"github.com/lpvm/newest_sapo_jobs/models"
)
type UserController struct{}
type JobController struct{}
func NewUserController() *UserController {
return &UserController{}
}
var (
tpl *template.Template
tplOnce sync.Once
db = models.GetDB()
dbModels = models.NewDbModel()
)
func NewJobController() *JobController {
// 初始化表
dbModels.InitTables()
return &JobController{}
}
func (j JobController) ServeIndex(w http.ResponseWriter, req *http.Request) {
tplOnce.Do(func() {
tpl = template.Must(template.ParseGlob("templates/*.gohtml"))
})
tpl.ExecuteTemplate(w, "index.gohtml", nil)
}
最后修改 main.go 以确保程序退出时关闭数据库:
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/lpvm/newest_sapo_jobs/controllers"
"github.com/lpvm/newest_sapo_jobs/models"
)
func main() {
uc := controllers.NewUserController()
fmt.Println("usercontroller: ", uc)
jc := controllers.NewJobController()
// 设置信号监听,确保程序退出时关闭数据库
setupCloseHandler()
http.Handle("/favicon.ico", http.NotFoundHandler())
http.HandleFunc("/", jc.ServeIndex)
fmt.Println("Server starting on :8079")
http.ListenAndServe(":8079", nil)
}
func setupCloseHandler() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
fmt.Println("\nShutting down server...")
models.CloseDB()
os.Exit(0)
}()
}
这个解决方案:
- 使用单例模式确保只有一个数据库连接
- 移除了导致连接过早关闭的
defer db.Close() - 添加了应用退出时的连接清理
- 使用
sync.Once确保初始化代码只执行一次

