古老的榕树

用 Go 开发终端接口服务--灵活写 Dao 数据层函数

发表 2019-05-14 16:43 阅读(660)
上一章节把实体层的结构体都准备就绪了,dao 数据层主要是进行数据操作,以实体层结构体为载体返回给上层使用。数据层的函数,我们尽量提高它们的可复用性和扩展性。数据层的函数根据功能性一般可以抽象几个分类出来,读和写相关的两种类型函数,读函数再细分读取一条记录、读取多条记录、读取聚合统计单列数据;而写函数可以细分 插入、修改、删除三种类型函数。

从可复用性出发,我们可以从获取多条记录函数入手,函数参数接收结构体实例,结构体一些重要的属性如果不是非默认值,即可作为 SQL 过滤条件,参与动态条件查询。同样,聚合统计、插入、修改、删除也都以同样的方式过滤数据和执行操作。

另外数据层的函数,尽量保持函数的独立,原子性,单一性,尽量不要把业务逻辑放进来,把它们交给 service 服务层处理就好,否则会混乱。另外我们习惯每个表对应一个 go 文件,作为 dao 数据层的一项约定规则。

数据层的每个函数,不在本层实例化 DB 数据库对象,这个工作由 service 服务层来完成,dao 数据层只接收服务层传进来的,因为数据层的函数适合放可复用性高的函数,遇到细而零散的数据需求,还需要在 service 层按需获取,这样更加灵活。所以我们会发现 dao 数据层,每个函数都有一个参数 sqlxDB *sqlx.DB 实例。

下面我们列举一些具有代表性的函数来讲解,首先从 product 产品的数据操作函数开始。

*代码清单 - 产品数据层部分操作*

package dao

import (
	"errors"
	"time"

	sq "github.com/elgris/sqrl"
    "github.com/jmoiron/sqlx"

	"chapter01/src/model"
)

// SelectProductByID 根据 ID 获取单个 Product 对象
func SelectProductByID(sqlxDB *sqlx.DB, id int64) (*model.Product, error) {
	var product model.Product
	err := sqlxDB.Get(&product, "select * from product where id=? limit 1", id)
	if err != nil {
		return nil, err
	}

	return &product, nil
}

// SelectProductListByModel 根据实体(动态条件)获取多个 Product 对象集合
func SelectProductListByModel(sqlxDB *sqlx.DB, params model.Product, orderBy string, pageIndex, pageSize uint64) ([]model.Product, error) {
	sqlBuild := sq.Select("*").From("product")
	if params.BaseModel.ID > 0 {
		sqlBuild.Where("id=?", params.ID)
	}

	if params.Status > 0 {
		sqlBuild.Where("status=?", params.Status)
	}

	if params.Category > 0 {
		sqlBuild.Where("category=?", params.Category)
	}

	if params.Name != "" && params.Intro != "" {
		sqlBuild.Where(sq.Expr(
            "name like ? or intro like ?", 
            `%`+params.Name+`%`, 
            `%`+params.Intro+`%`,
        ))
	} else {
		if params.Name != "" {
			sqlBuild.Where("name LIKE ?", `%`+params.Name+`%`)
		}

		if params.Intro != "" {
			sqlBuild.Where("intro LIKE ?", `%`+params.Intro+`%`)
		}
	}

	if orderBy != "" {
		sqlBuild.OrderBy(orderBy)
	} else {
		sqlBuild.OrderBy("id desc")
	}

	if pageIndex > 0 && pageSize > 0 {
		sqlBuild.Offset(pageIndex).Limit(pageSize)
	}

	query, args, err := sqlBuild.ToSql()
	if err != nil {
		return nil, err
	}
    
	var list []model.Product
	err = sqlxDB.Select(&list, query, args...)
	if err != nil {
		return list, err
	}

	return list, nil
}

// DeleteProductByID 根据 ID 删除一个 Product 记录
func DeleteProductByID(sqlxDB *sqlx.DB, id int64) (rowsAffected int64, err error) {
	result, err := sqlxDB.Exec("delete from product where id=?", id)
	if err != nil {
		return 0, err
	}

	rowsAffected, err = result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return
}

//InsertProduct 新增一条产品记录
func InsertProduct(sqlxDB *sqlx.DB, params model.Product) (id int64, err error) {
	result, err := sqlxDB.Exec(
		`insert into product 
		(category,name,intro,price,status,created,updated) 
		values 
		(?,?,?,?,?,?,?)`,
		params.Category,
		params.Name,
		params.Intro,
		params.Price,
		params.Status,
		time.Now(),
		time.Now(),
	)
	if err != nil {
		return 0, err
	}

	id, err = result.LastInsertId()
	if err != nil {
		return 0, err
	}
	return
}

//UpdateProduct 更新一个产品信息,params.ID 是必须的
func UpdateProduct(sqlxDB *sqlx.DB, params model.Product) (rowsAffected int64, err error) {
	if params.ID <= 0 {
		return 0, errors.New("id le zero")
	}

	sqlBuild := sq.Update("product").Set("updated", time.Now())
	if params.Category > 0 {
		sqlBuild.Set("category", params.Category)
	}

	if params.Status > 0 {
		sqlBuild.Set("status", params.Status)
	}

	if params.Price > 0 {
		sqlBuild.Set("price", params.Price)
	}

	if params.Name != "" {
		sqlBuild.Set("name", params.Name)
	}

	if params.Intro != "" {
		sqlBuild.Set("intro", params.Intro)
	}

	query, args, err := sqlBuild.Where("id=?", params.ID).ToSql()
	if err != nil {
		return 0, err
	}

	result, err := sqlxDB.Exec(query, args...)
	if err != nil {
		return 0, err
	}

	rowsAffected, err = result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return
}
以上代码,除了获取一条数据 SelectProductByID 和删除一条记录 DeleteProductByID,没有使用动态条件之外,其他都使用了 sqrl 这个第三方库实现动态条件传入。这样函数可实现可复用性,比如 SelectProductListByModel 的参数 params model.Product,我们可以查询 status category 等于某值,name intro 类似某值的条件进行单个或多个条件串行查询。

然后是 product_photo 产品图片对应的 dao 数据层部分函数,和其他的很相似,结构上是一致的:
*代码清单 - 产品图片数据层部分操作*




package dao

import (
	"errors"
	"github.com/jmoiron/sqlx"
	"time"

	sq "github.com/elgris/sqrl"

	"chapter01/src/model"
)

// SelectProductPhotoByID 根据 ID 获取单个 ProductPhoto 对象
func SelectProductPhotoByID(sqlxDB *sqlx.DB, id int64) (*model.ProductPhoto, error) {
	var ProductPhoto model.ProductPhoto
	err := sqlxDB.Get(&ProductPhoto, "select * from product_photo where id=? limit 1", id)
	if err != nil {
		return nil, err
	}

	return &ProductPhoto, nil
}

// SelectProductPhotoListByModel 根据实体(动态条件)获取多个 ProductPhoto 对象集合
func SelectProductPhotoListByModel(sqlxDB *sqlx.DB, params model.ProductPhoto, orderBy string, pageIndex, pageSize uint64) ([]model.ProductPhoto, error) {
	sqlBuild := sq.Select("*").From("product_photo")
	if params.BaseModel.ID > 0 {
		sqlBuild.Where("id=?", params.ID)
	}

	if params.ProductID > 0 {
		sqlBuild.Where("product_id=?", params.ProductID)
	}

	if orderBy != "" {
		sqlBuild.OrderBy(orderBy)
	} else {
		sqlBuild.OrderBy("id desc")
	}

	if pageIndex > 0 && pageSize > 0 {
		sqlBuild.Offset(pageIndex).Limit(pageSize)
	}

	query, args, err := sqlBuild.ToSql()
	if err != nil {
		return nil, err
	}

	list := []model.ProductPhoto{}
	err = sqlxDB.Select(&list, query, args...)
	if err != nil {
		return list, err
	}

	return list, nil
}

// SelectProductPhotoListByProductID 根据实体(动态条件)获取多个 ProductPhoto 对象集合
func SelectProductPhotoListByProductID(sqlxDB *sqlx.DB, productID int64) ([]model.ViewPhotoRespArgs, error) {
	list := []model.ViewPhotoRespArgs{}
	err := sqlxDB.Select(&list, "select id,path,seq from product_photo where product_id=? order by seq asc,id desc ", productID)
	if err != nil {
		return list, err
	}

	return list, nil
}

// DeleteProductPhotoByID 根据 ID 删除一个 ProductPhoto 记录
func DeleteProductPhotoByID(sqlxDB *sqlx.DB, id int64) (rowsAffected int64, err error) {
	result, err := sqlxDB.Exec("delete from product_photo where id=?", id)
	if err != nil {
		return 0, err
	}

	rowsAffected, err = result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return
}

//InsertProductPhoto 新增一条产品记录
func InsertProductPhoto(sqlxDB *sqlx.DB, params model.ProductPhoto) (id int64, err error) {
	result, err := sqlxDB.Exec(
		`insert into product_photo (product_id,path,seq,created,updated) values (?,?,?,?,?)`,
		params.ProductID,
		params.Path,
		params.Seq,
		time.Now(),
		time.Now(),
	)
	if err != nil {
		return 0, err
	}

	id, err = result.LastInsertId()
	if err != nil {
		return 0, err
	}
	return
}

//UpdateProductPhoto 更新一个产品信息,params.ID 是必须的
func UpdateProductPhoto(sqlxDB *sqlx.DB, params model.ProductPhoto) (rowsAffected int64, err error) {
	if params.ID <= 0 {
		return 0, errors.New("id le zero")
	}

	sqlBuild := sq.Update("product_photo").Set("updated", time.Now())
	if params.ProductID > 0 {
		sqlBuild.Set("product_id", params.ProductID)
	}

	if params.Path != "" {
		sqlBuild.Set("path", params.Path)
	}

	if params.Seq > 0 {
		sqlBuild.Set("seq", params.Seq)
	}

	query, args, err := sqlBuild.Where("id=?", params.ID).ToSql()
	if err != nil {
		return 0, err
	}

	result, err := sqlxDB.Exec(query, args...)
	if err != nil {
		return 0, err
	}

	rowsAffected, err = result.RowsAffected()
	if err != nil {
		return 0, err
	}
	return
}

小结
dao 数据层函数,要保持原子性、单一性,要避免一切业务逻辑的东西,这样利于增强程序的可读性,也利于定位突发的底层问题。实际项目中,数据层函数解决了服务层所需要的绝大部分的数据需求,我们尽量降低它的复杂度,遵循原子性、单一性和可复用性。其他零散的数据查询和操作,可以交给 service 服务层来做。


《用 Go 开发终端接口服务》 目录


Donate

如果文章对您有帮助,请使用手机支付宝扫描二维码,捐赠X元,作者离不开读者的支持!