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模式后,数据库连接在初始化完成后立即被关闭,导致后续操作无法使用。

根本原因:

  1. init() 函数在 controllers 包初始化时调用 dbModels.InitTables(db)
  2. db 通过 models.OpenDb() 获取,但该函数返回前就执行了 defer db.Close()
  3. InitTables() 执行完毕后,数据库连接立即关闭
  4. 后续任何数据库操作都会遇到 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)
	}()
}

这个解决方案:

  1. 使用单例模式确保只有一个数据库连接
  2. 移除了导致连接过早关闭的 defer db.Close()
  3. 添加了应用退出时的连接清理
  4. 使用 sync.Once 确保初始化代码只执行一次
回到顶部