学习
实践
活动
工具
TVP
写文章
专栏首页c++与qt学习从零实现ORM框架GeoORM-database/sql基础-01

从零实现ORM框架GeoORM-database/sql基础-01

从零实现ORM框架GeoORM-database/sql基础-01


本系列参考: 7天用Go从零实现ORM框架GeeORM

本系列源码: https://gitee.com/DaHuYuXiXi/geo-orm


ORM 框架需要干什么

对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。

那对象和数据库是如何映射的呢?

数据库

面向对象的编程语言

表(table)

类(class/struct)

记录(record, row)

对象 (object)

字段(field, column)

对象属性(attribute)

举一个具体的例子,来理解 ORM。

CREATE TABLE `User` (`Name` text, `Age` integer);
INSERT INTO `User` (`Name`, `Age`) VALUES ("Tom", 18);
SELECT * FROM `User`;

第一条 SQL 语句,在数据库中创建了表 User,并且定义了 2 个字段 Name 和 Age;第二条 SQL 语句往表中添加了一条记录;最后一条语句返回表中的所有记录。

假如我们使用了 ORM 框架,可以这么写:

type User struct {
    Name string
    Age  int
}

orm.CreateTable(&User{})
orm.Save(&User{"Tom", 18})
var users []User
orm.Find(&users)

ORM 框架相当于对象和数据库中间的一个桥梁,借助 ORM 可以避免写繁琐的 SQL 语言,仅仅通过操作具体的对象,就能够完成对关系型数据库的操作。

那如何实现一个 ORM 框架呢?

  • CreateTable 方法需要从参数 &User{} 得到对应的结构体的名称 User 作为表名,成员变量 Name, Age 作为列名,同时还需要知道成员变量对应的类型。
  • Save 方法则需要知道每个成员变量的值。
  • Find 方法仅从传入的空切片 &[]User,得到对应的结构体名也就是表名 User,并从数据库中取到所有的记录,将其转换成 User 对象,添加到切片中。

如果这些方法只接受 User 类型的参数,那是很容易实现的。但是 ORM 框架是通用的,也就是说可以将任意合法的对象转换成数据库中的表和记录。例如:

type Account struct {
    Username string
    Password string
}

orm.CreateTable(&Account{})

这就面临了一个很重要的问题:如何根据任意类型的指针,得到其对应的结构体的信息。这涉及到了 Go 语言的反射机制(reflect),通过反射,可以获取到对象对应的结构体名称,成员变量、方法等信息,例如:

typ := reflect.Indirect(reflect.ValueOf(&Account{})).Type()
fmt.Println(typ.Name()) // Account

for i := 0; i < typ.NumField(); i++ {
    field := typ.Field(i)
    fmt.Println(field.Name) // Username Password
}
  • reflect.ValueOf() 获取指针对应的反射值。
  • reflect.Indirect() 获取指针指向的对象的反射值。
  • (reflect.Type).Name() 返回类名(字符串)。
  • (reflect.Type).Field(i) 获取第 i 个成员变量。

除了对象和表结构/记录的映射以外,设计 ORM 框架还需要关注什么问题呢?

1)MySQL,PostgreSQL,SQLite 等数据库的 SQL 语句是有区别的,ORM 框架如何在开发者不感知的情况下适配多种数据库?

2)如果对象的字段发生改变,数据库表结构能够自动更新,即是否支持数据库自动迁移(migrate)?

3)数据库支持的功能很多,例如事务(transaction),ORM 框架能实现哪些?

4)…


关于 GeoORM

数据库的特性非常多,简单的增删查改使用 ORM 替代 SQL 语句是没有问题的,但是也有很多特性难以用 ORM 替代,比如复杂的多表关联查询,ORM 也可能支持,但是基于性能的考虑,开发者自己写 SQL 语句很可能更高效。

因此,设计实现一个 ORM 框架,就需要给功能特性排优先级了。

Go 语言中使用比较广泛 ORM 框架是 gormxorm。除了基础的功能,比如表的操作,记录的增删查改,gorm 还实现了关联关系(一对一、一对多等),回调插件等;xorm 实现了读写分离(支持配置多个数据库),数据同步,导入导出等。

gorm 正在彻底重构 v1 版本,短期内看不到发布 v2 的可能。相比于 gorm-v1,xorm 在设计上更清晰。GeoORM的设计主要参考了 xorm,一些细节上的实现参考了 gorm。GeoORM的目的主要是了解 ORM 框架设计的原理,具体实现上鲁棒性做得不够,一些复杂的特性,例如 gorm 的关联关系,xorm 的读写分离没有实现。目前支持的特性有:

gorm 正在彻底重构 v1 版本,短期内看不到发布 v2 的可能。相比于 gorm-v1,xorm 在设计上更清晰。GeeORM 的设计主要参考了 xorm,一些细节上的实现参考了 gorm。GeeORM 的目的主要是了解 ORM 框架设计的原理,具体实现上鲁棒性做得不够,一些复杂的特性,例如 gorm 的关联关系,xorm 的读写分离没有实现。目前支持的特性有:

  • 表的创建、删除、迁移。
  • 记录的增删查改,查询条件的链式操作。
  • 单一主键的设置(primary key)。
  • 钩子(在创建/更新/删除/查找之前或之后)
  • 事务(transaction)。

初识 SQLite

SQLite中文文档

SQLite基本语法和Mysql等关系型数据库大体一致,无需耗费太多时间即可掌握

SQLite 是一款轻量级的,遵守 ACID 事务原则的关系型数据库。SQLite 可以直接嵌入到代码中,不需要像 MySQL、PostgreSQL 需要启动独立的服务才能使用。SQLite 将数据存储在单一的磁盘文件中,使用起来非常方便。也非常适合初学者用来学习关系型数据的使用。GeoORM的所有的开发和测试均基于 SQLite。

目前,几乎所有版本的 Linux 操作系统都附带 SQLite。所以,只要使用下面的命令来检查您的机器上是否已经安装了 SQLite。

在 Ubuntu 上,安装 SQLite 只需要一行命令,无需配置即可使用。 apt-get install sqlite3

接下来,连接数据库(geo.db),如若 geo.db 不存在,则会新建。如果连接成功,就进入到了 SQLite 的命令行模式,执行 .help 可以看到所有的帮助命令。

# sqlite3 geo.db

SQLite version 3.7.17 2013-05-20 00:56:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"

使用 SQL 语句新建一张表 User,包含两个字段,字符串 Name 和 整型 Age。

CREATE TABLE User(Name text, Age integer);

插入两条数据

INSERT INTO User(Name, Age) VALUES ("Tom", 18), ("Jack", 25);

执行简单的查询操作,在执行之前使用 .head on 打开显示列名的开关,.mode column让每一列左对齐显示,这样查询结果看上去更直观。

.head on
.mode column
# 查找 `Age > 20` 的记录
SELECT * FROM User WHERE Age > 20;
# 统计记录个数
SELECT COUNT(*) FROM User;

使用 .table 查看当前数据库中所有的表(table),执行 .schema < table > 查看建表的 SQL 语句。

SqlLite的基本CURD用法和Mysql等关系型数据库一致,这里不多介绍了,详细内容可以参考官方文档


database/sql 标准库

Go 语言提供了标准库 database/sql 用于和数据库的交互,接下来我们写一个 Demo,看一看这个库的用法。

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/mattn/go-sqlite3"
)

func main() {
	db, _ := sql.Open("sqlite3", "geo.db")

	defer func() { _ = db.Close() }()

	_, _ = db.Exec("DROP TABLE IF EXISTS User;")

	_, _ = db.Exec("CREATE TABLE User(Name text);")

	result, err := db.Exec("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam")

	if err == nil {
		affected, _ := result.RowsAffected()
		fmt.Println(affected)
	}

	row := db.QueryRow("SELECT Name FROM User LIMIT 1")

	var name string

	if err := row.Scan(&name); err == nil {
		fmt.Println(name)
	}
}

go-sqlite3 依赖于 gcc,如果这份代码在 Windows 上运行的话,需要安装 mingw 或其他包含有 gcc 编译器的工具包。

执行 go run .,输出如下。

> go run .
2020/03/07 20:28:37 2
2020/03/07 20:28:37 Tom
  • 使用 sql.Open() 连接数据库,第一个参数是驱动名称,import 语句 _ “github.com/mattn/go-sqlite3” 包导入时会注册 sqlite3 的驱动,第二个参数是数据库的名称,对于 SQLite 来说,也就是文件名,不存在会新建。返回一个 sql.DB 实例的指针。
  • Exec() 用于执行 SQL 语句,如果是查询语句,不会返回相关的记录。所以查询语句通常使用 Query() 和 QueryRow(),前者可以返回多条记录,后者只返回一条记录。
  • Exec()、Query()、QueryRow() 接受1或多个入参,第一个入参是 SQL 语句,后面的入参是 SQL 语句中的占位符 ? 对应的值,占位符一般用来防 SQL 注入。
  • QueryRow() 的返回值类型是 *sql.Row,row.Scan() 接受1或多个指针作为参数,可以获取对应列(column)的值,在这个示例中,只有 Name 一列,因此传入字符串指针 &name 即可获取到查询的结果。

掌握了基础的 SQL 语句和 Go 标准库 database/sql 的使用,可以开始实现 ORM 框架的雏形了。


实现一个简单的 log 库

开发一个框架/库并不容易,详细的日志能够帮助我们快速地定位问题。因此,在写核心代码之前,我们先用几十行代码实现一个简单的 log 库。

为什么不直接使用原生的 log 库呢?log 标准库没有日志分级,不打印文件和行号,这就意味着我们很难快速知道是哪个地方发生了错误。

这个简易的 log 库具备以下特性:

  • 支持日志分级(trace,info,debug,warn,error)。
  • 不同层级日志显示时使用不同的颜色区分。
  • 显示打印日志代码对应的文件名和行号。

  • 初始化项目结构如下:
  • log.go

编写自己的日志库设计到对Log标准库的相关操作,建议大家先熟悉一下标准库的操作:

GoLang的Log标准库介绍

print输出的字体颜色设置

package log

import (
	"io/ioutil"
	"log"
	"os"
	"sync"
)

var (
	//参数: 日志输出到控制台, 日志输出的统一前缀设置(包括颜色设置) , 日志输出的额外选项: 输出日期,文件名和行号
	//print( "\033[字背景颜色;字体颜色m字符串\033[0m" )
	errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile)
	warnLog  = log.New(os.Stdout, "\033[33m[warn ]\033[0m ", log.LstdFlags|log.Lshortfile)
	infoLog  = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile)
	debugLog = log.New(os.Stdout, "\033[36m[debug]\033[0m ", log.LstdFlags|log.Lshortfile)
	traceLog = log.New(os.Stdout, "\033[30m[trace]\033[0m ", log.LstdFlags|log.Lshortfile)

	//存放所有日志记录器的数组
	loggers = []*log.Logger{errorLog, warnLog, infoLog, debugLog, traceLog}
	mu      sync.Mutex
)

//日志输出方法
var (
	Error  = errorLog.Println
	Errorf = errorLog.Printf
	Warn   = warnLog.Println
	Warnf  = warnLog.Printf
	Info   = infoLog.Println
	Infof  = infoLog.Printf
	Debug  = debugLog.Println
	Debugf = debugLog.Printf
	Trace  = traceLog.Println
	Tracef = traceLog.Printf
)

//日志级别
const (
	TraceLevel = iota
	DebugLevel
	InfoLevel
	WarnLevel
	ErrorLevel
	Disabled
)

//SetLevel 设置日志级别
func SetLevel(level int) {
	mu.Lock()
	defer mu.Unlock()
    //ioutil.Discard,即不打印该日志
	if ErrorLevel < level {
		errorLog.SetOutput(ioutil.Discard)
	}

	if WarnLevel < level {
		warnLog.SetOutput(ioutil.Discard)
	}

	if InfoLevel < level {
		infoLog.SetOutput(ioutil.Discard)
	}

	if DebugLevel < level {
		debugLog.SetOutput(ioutil.Discard)
	}

	if TraceLevel < level {
		traceLog.SetOutput(ioutil.Discard)
	}
}

  • log_test.go
package log

import (
	"testing"
)

func TestPrintColor(t *testing.T) {
	SetLevel(InfoLevel)
	logAllLevel()
}

func logAllLevel() {
	errorLog.Println("error日志输出测试")
	warnLog.Println("warn日志输出测试")
	infoLog.Println("info日志输出测试")
	debugLog.Println("debug日志输出测试")
	traceLog.Println("trace日志输出测试")
}

上面是极客兔兔给出的日志库Demo,下面给出一个我自己写的日志库demo:

package myLog

import (
	"errors"
	"io"
	"log"
	"os"
	"sync"
)

type any interface{}

//日志级别
const (
	TraceLevel = iota
	DebugLevel
	InfoLevel
	WarnLevel
	ErrorLevel
)

var mu sync.Mutex

type Log struct {
	//日志级别名称
	Log string
	//日志级别
	level int
	//日志输出前缀
	prefix string
	//是否开启了当前日志级别的输出
	logAble bool
	//标准库
	logger *log.Logger
	//日志输出到哪里
	out io.Writer
}

func (l *Log) log(v any) {
	if l.logAble {
		l.logger.Println(v)
	}
}

func (l *Log) logf(str string, args interface{}) {
	if l.logAble {
		l.logger.Printf(str, args)
	}
}

// 初始化相关log
var (
	traceLog = &Log{Log: "trace", prefix: "\033[30m[trace]\033[0m ", out: os.Stdout, logAble: false, level: 0}
	debugLog = &Log{Log: "debug", prefix: "\033[36m[debug]\033[0m ", out: os.Stdout, logAble: false, level: 1}
	infoLog  = &Log{Log: "info", prefix: "\033[34m[info ]\033[0m ", out: os.Stdout, logAble: true, level: 2}
	warnLog  = &Log{Log: "warn", prefix: "\033[33m[warn ]\033[0m ", out: os.Stdout, logAble: true, level: 3}
	errorLog = &Log{Log: "error", prefix: "\033[31m[error]\033[0m ", out: os.Stdout, logAble: true, level: 4}
)

func init() {
	traceLog.logger = log.New(traceLog.out, traceLog.prefix, log.LstdFlags|log.Lshortfile)
	debugLog.logger = log.New(debugLog.out, debugLog.prefix, log.LstdFlags|log.Lshortfile)
	infoLog.logger = log.New(infoLog.out, infoLog.prefix, log.LstdFlags|log.Lshortfile)
	warnLog.logger = log.New(warnLog.out, warnLog.prefix, log.LstdFlags|log.Lshortfile)
	errorLog.logger = log.New(errorLog.out, errorLog.prefix, log.LstdFlags|log.Lshortfile)
}

var logs = []*Log{traceLog, debugLog, infoLog, warnLog, errorLog}

type HandleLog func(l *Log)

//logsOp 对日志数组中每个日志log进行处理
func logsOp(logHandle HandleLog) {
	for _, log := range logs {
		logHandle(log)
	}
}

//SetLogLevel 设置日志级别
func SetLogLevel(level int) {
	mu.Lock()
	defer mu.Unlock()

	clearLevel()

	if level < 0 {
		for _, log := range logs {
			log.logAble = false
		}
	} else if level >= len(logs) {
		clearLevel()
	} else {
		for i := 0; i < level; i++ {
			logs[i].logAble = false
		}
	}
}

//clearLevel 让所有日志级别都可以输出日志
func clearLevel() {
	logsOp(func(l *Log) {
		l.logAble = true
	})
}

//SetGlobalLogOutPut 设置全局日志输出
func SetGlobalLogOutPut(out io.Writer) {
	logsOp(func(l *Log) {
		l.logger.SetOutput(out)
	})
}

func SetGlobalLogMulOutPut(out ...io.Writer) {
	logsOp(func(l *Log) {
		l.logger.SetOutput(io.MultiWriter(out...))
	})
}

//SetLogOutPut 设置某个级别的日志输出
func SetLogOutPut(logLevel int, out io.Writer) {
	if logLevel < 0 || logLevel >= len(logs) {
		panic(errors.New("logLevel is wrong"))
	}
	logs[logLevel].logger.SetOutput(out)
}

func SetLogMulOutPut(logLevel int, out ...io.Writer) {
	if logLevel < 0 || logLevel >= len(logs) {
		panic(errors.New("logLevel is wrong"))
	}
	logs[logLevel].logger.SetOutput(io.MultiWriter(out...))
}

func Info(v any) {
	infoLog.log(v)
}

func Infof(str string, args interface{}) {
	infoLog.logf(str, args)
}

func Trace(v any) {
	traceLog.log(v)
}

func Tracef(str string, args interface{}) {
	traceLog.logf(str, args)
}

func Debug(v any) {
	debugLog.log(v)
}

func Debugf(str string, args interface{}) {
	debugLog.logf(str, args)
}

func Warn(v any) {
	warnLog.log(v)
}

func Warnf(str string, args interface{}) {
	warnLog.logf(str, args)
}

func Error(v any) {
	errorLog.log(v)
}

func Errorf(str string, args interface{}) {
	errorLog.logf(str, args)
}
  • 测试
package myLog

import (
	"fmt"
	"os"
	"testing"
)

func TestPrintColor(t *testing.T) {
	SetLogLevel(TraceLevel)
	logAllLevel()
	SetLogLevel(DebugLevel)
	logAllLevel()
	SetLogLevel(InfoLevel)
	logAllLevel()
	SetLogLevel(WarnLevel)
	logAllLevel()
	SetLogLevel(ErrorLevel)
	logAllLevel()
}

func TestLogOutput(t *testing.T) {
	file, _ := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
	SetLogMulOutPut(ErrorLevel, os.Stdout, file)
	SetLogLevel(TraceLevel)
	logAllLevel()
}

func logAllLevel() {
	Error("error日志输出测试")
	Warn("warn日志输出测试")
	Info("info日志输出测试")
	Debug("debug日志输出测试")
	Trace("trace日志输出测试")
	fmt.Println("----------------------------")
}

核心结构 Session

我们在根目录下新建一个文件夹 session,用于实现与数据库的交互。今天我们只实现直接调用 SQL 语句进行原生交互的部分,这部分代码实现在 session/raw.go 中。

  • session/raw.go
package session

import (
	"database/sql"
	"strings"
)

type Session struct {
	db      *sql.DB
	sql     strings.Builder
	sqlVars []interface{}
}

func New(db *sql.DB) *Session {
	return &Session{db: db}
}

func (s *Session) Clear() {
	s.sql.Reset()
	s.sqlVars = nil
}

func (s *Session) DB() *sql.DB {
	return s.db
}

func (s *Session) Raw(sql string, values ...interface{}) *Session {
	s.sql.WriteString(sql)
	s.sql.WriteString(" ")
	s.sqlVars = append(s.sqlVars, values...)
	return s
}
  • Session 结构体目前只包含三个成员变量,第一个是 db *sql.DB,即使用 sql.Open()方法连接数据库成功之后返回的指针。
  • 第二个和第三个成员变量用来拼接 SQL 语句和 SQL 语句中占位符的对应值。用户调用 Raw() 方法即可改变这两个变量的值。

接下来呢,封装 Exec()、Query() 和 QueryRow() 三个原生方法。

// Exec raw sql with sqlVars
func (s *Session) Exec() (result sql.Result, err error) {
	defer s.Clear()
	myLog.Infof(s.sql.String(), s.sqlVars)
	if result, err = s.DB().Exec(s.sql.String(), s.sqlVars...); err != nil {
		myLog.Error(err)
	}
	return
}

// QueryRow gets a record from db
func (s *Session) QueryRow() *sql.Row {
	defer s.Clear()
	myLog.Infof(s.sql.String(), s.sqlVars)
	return s.DB().QueryRow(s.sql.String(), s.sqlVars...)
}

// QueryRows gets a list of records from db
func (s *Session) QueryRows() (rows *sql.Rows, err error) {
	defer s.Clear()
	myLog.Infof(s.sql.String(), s.sqlVars)
	if rows, err = s.DB().Query(s.sql.String(), s.sqlVars...); err != nil {
		myLog.Error(err)
	}
	return
}
  • 封装有 2 个目的,一是统一打印日志(包括 执行的SQL 语句和错误日志)。
  • 二是执行完成后,清空 (s *Session).sql 和 (s *Session).sqlVars 两个变量。这样Session 可以复用,开启一次会话,可以执行多次 SQL。

核心结构 Engine

Session 负责与数据库的交互,那交互前的准备工作(比如连接/测试数据库),交互后的收尾工作(关闭连接)等就交给 Engine 来负责了。Engine 是 GeeORM 与用户交互的入口。代码位于根目录的 geoorm.go。

  • engine.go
package GeoORM

import (
	"GeoORM/mylog"
	"GeoORM/session"
	"database/sql"
)

type Engine struct {
	db *sql.DB
}

func NewEngine(driver, source string) (e *Engine, err error) {
	db, err := sql.Open(driver, source)
	if err != nil {
		myLog.Error(err)
		return
	}
	// Send a ping to make sure the database connection is alive.
	if err = db.Ping(); err != nil {
		myLog.Error(err)
		return
	}
	e = &Engine{db: db}
	myLog.Info("Connect database success")
	return
}

func (engine *Engine) Close() {
	if err := engine.db.Close(); err != nil {
		myLog.Error("Failed to close database")
	}
	myLog.Info("Close database success")
}

func (engine *Engine) NewSession() *session.Session {
	return session.New(engine.db)
}

Engine 的逻辑非常简单,最重要的方法是 NewEngine,NewEngine 主要做了两件事。

  • 连接数据库,返回 *sql.DB。
  • 调用 db.Ping(),检查数据库是否能够正常连接。

另外呢,提供了 Engine 提供了 NewSession() 方法,这样可以通过 Engine 实例创建会话,进而与数据库进行交互了。到这一步,整个 GeoORM 的框架雏形已经出来了。


测试

package cmd_test

import (
	"GeoORM"
	"fmt"
	#########导入对应的驱动实现别忘记了##########
	_ "github.com/go-sql-driver/mysql"
	"testing"
)

func TestCommonOp(t *testing.T) {

	engine, _ := GeoORM.NewEngine("mysql", "root:xxx@tcp(xxx9:3306)/test")

	defer engine.Close()

	s := engine.NewSession()

	_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()

	_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()

	_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()

	result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec()

	count, _ := result.RowsAffected()

	fmt.Printf("Exec success, %d affected\n", count)
}

执行测试,将会看到如下的输出:

日志中出现了一行报错信息,table User already exists,因为我们在 main 函数中执行了两次创建表 User 的语句。可以看到,每一行日志均标明了报错的文件和行号,而且不同层级日志的颜色是不同的。

注意: 我们实现的日志框架的error输出仅仅只是调用标准库log的println方法进行输出,并没有调用painc等会抛出异常的日志输出


本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:https://blog.csdn.net/m0_53157173?spm=1011.2124.3001.5343复制
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 从零实现Web框架Geo教程-Http基础-01

    要开发一个Web应用,我们首先会考虑框架的选型,对于Javaer来说,通常Spring mvc是Web开发的首选框架;

    大忽悠爱学习
  • 01 . Go之从零实现Web框架

    大部分时候,我们需要实现一个 Web 应用,第一反应是应该使用哪个框架。不同的框架设计理念和提供的功能有很大的差别。比如 Python 语言的 django和f...

    常见_iginkgo
  • 从零实现ORM框架GeoORM-记录新增和查询-03

    本系列源码: https://gitee.com/DaHuYuXiXi/geo-orm

    大忽悠爱学习
  • 从零实现ORM框架GeoORM-对象表结构映射-02

    本系列源码: https://gitee.com/DaHuYuXiXi/geo-orm

    大忽悠爱学习
  • 01 . Go之从零实现Web框架(框架雏形, 上下文Context,路由)

    大部分时候,我们需要实现一个 Web 应用,第一反应是应该使用哪个框架。不同的框架设计理念和提供的功能有很大的差别。比如 Python 语言的 django和f...

    常见_iginkgo
  • python 操作MySQL数据库

    输出:连接成功: <pymysql.connections.Connection object at 0x00000205AC8E96D0>

    Michael阿明
  • MyBatis学习总结(一)——ORM概要与MyBatis快速入门

    程序员应该将核心关注点放在业务上,而不应该将时间过多的浪费在CRUD中,多数的ORM框架都把增加、修改与删除做得非常不错了,然后数据库中查询无疑是使用频次最高、...

    张果
  • MyBatis学习总结(一)——ORM概要与MyBatis快速起步

    程序员应该将核心关注点放在业务上,而不应该将时间过多的浪费在CRUD中,多数的ORM框架都把增加、修改与删除做得非常不错了,然后数据库中查询无疑是使用频次最高、...

    张果
  • Spring-01 Spring基础

    2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。

    张小驰出没
  • 使用ORM框架,必须迁就数据库的设计吗?

    我在CSDN发表了一个帖子,发布一款强大的ORM工具--PDF.NET集成开发工具 ,有个朋友caozhy提出了非常尖锐的问题,我对他的问题做了回答,现在觉得他...

    用户1177503
  • JDBC、ORM、JPA、Spring Data JPA,傻傻分不清楚?给你个选择SpringDataJPA的理由!

    本章节主要对Spring Data JPA的整体情况以及与其相关的一些概念进行一个简单的介绍。

    架构悟道
  • Golang 语言 Web 框架 beego v2 之读操作

    beego v2.x 和 beego v1.x 在 ORM 上的区别是,beego v2.x 的 ORM 对象被设计为无状态的,它是线程安全的,建议大家在使用时...

    frank.
  • 如何优雅地操作数据库?ORM了解一下

    对象关系映射(Object Relational Mapping,简称ORM),是一种程序技术,实现面向对象编程语言中的内存对象与关系型数据库中的业务实体之间的...

    前端LeBron
  • 2017最全的Java学习方向

    方向不对努力白费,Java技术的学习并不是一蹴而就的,正确的学习方向能让你事半功倍,如果你想在自己的Java学习之初就了解学Java又好又快的方法,那么这篇文章...

    企鹅号小编
  • .NET(C#)有哪些主流的ORM框架,SqlSugar,Dapper,EF还是...

    前言 在以前的一篇文章中,为大家分享了《什么是ORM?为什么用ORM?浅析ORM的使用及利弊》。那么,在目前的.NET(C#)的世界里,有哪些主流的ORM,Sq...

    Rector
  • Mybatis系列全解(二):Mybatis简介与环境搭建

    Mybatis系列全解,我们预计准备10+篇文章,让我们了解到 Mybatis 的基本全貌,真正从入门到上手,从上手到精通,本文为首篇,我们开始。

    潘潘和他的朋友们
  • spring原理案例-基本项目搭建 02 spring jar包详解 spring jar包的用途

    在基础IOC功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持。

    noteless
  • 从零开始串联Python前后端技术

    运维开发流程概述 是我们参与到其中的一个入口,我们需要了解运维开发的一些环节,还有运维开发的一些技术基础。我们通过一个实例来做演示,基本的需求就是从数据库中查...

    jeanron100
  • APIJSON-零代码接口和文档JSON 协议 与 ORM 库

    最近项目上用到 APIJSON,查阅官方文档,虽然通用文档和APIAuTo提供了很多例子,但是不是很直观,因此把在项目上用到的和自己总结的整理出来,方便后期回顾...

    青年码农

扫码关注腾讯云开发者

领取腾讯云代金券