zorm 正在参加 2020 年度 OSC 中国开源项目评选,请投票支持!
zorm 在 2020 年度 OSC 中国开源项目评选 中已获得 {{ projectVoteCount }} 票,请投票支持!
投票让它出道
已投票
授权协议: Apache
开发语言: Google Go
操作系统: 跨平台
收录时间: 2020-03-09
提 交 者: 光石头

Golang 轻量级 ORM,readygo 子项目。

  • 基于原生sql语句编写,是springrain的精简和优化.
  • 自带代码生成器
  • 代码精简,总计2000行左右,注释详细,方便定制修改.
  • 支持事务传播,这是zorm诞生的主要原因
  • 支持mysql,postgresql,oracle,mssql,sqlite
  • 支持数据库读写分离
  • 更新性能zorm,gorm,xorm相当. 读取性能zorm比gorm,xorm快一倍
    go get gitee.com/chunanyong/zorm 
    

    生产使用参考 UserStructService.go

测试用例

https://gitee.com/chunanyong/readygo/blob/master/test/testzorm/BaseDao_test.go

// zorm 使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体
// 占位符统一使用?,zorm会根据数据库类型,自动替换占位符,例如postgresql数据库把?替换成$1,$2...
// 为了保持数据库兼容性,分页语句必须有order by
// zorm使用 ctx context.Context 参数实现事务传播,ctx从web层传递进来即可,例如gin的c.Request.Context()
// zorm的事务操作需要显示使用zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {})开启

数据库脚本和实体类

https://gitee.com/chunanyong/readygo/blob/master/test/testzorm/demoStruct.go

生成实体类或手动编写,建议使用代码生成器 https://gitee.com/chunanyong/readygo/tree/master/codegenerator

package test

import (
	"time"

	"gitee.com/chunanyong/zorm"
)

//建表语句

/*

DROP TABLE IF EXISTS `t_demo`;
CREATE TABLE `t_demo`  (
  `id` varchar(50)  NOT NULL COMMENT '主键',
  `userName` varchar(30)  NOT NULL COMMENT '姓名',
  `password` varchar(50)  NOT NULL COMMENT '密码',
  `createTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
  `active` int(0) NOT NULL DEFAULT 1 COMMENT '是否有效(0否,1是)',
  PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4  COMMENT = '例子' ;

*/

//demoStructTableName 表名常量,方便直接调用
const demoStructTableName = "t_demo"

// demoStruct 例子
type demoStruct struct {
	//引入默认的struct,隔离IEntityStruct的方法改动
	zorm.EntityStruct

	//Id 主键
	Id string `column:"id"`

	//UserName 姓名
	UserName string `column:"userName"`

	//Password 密码
	Password string `column:"password"`

	//CreateTime <no value>
	CreateTime time.Time `column:"createTime"`

	//Active 是否有效(0否,1是)
	Active int `column:"active"`

	//------------------数据库字段结束,自定义字段写在下面---------------//

}

//GetTableName 获取表名称
func (entity *demoStruct) GetTableName() string {
	return demoStructTableName
}

//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称.
func (entity *demoStruct) GetPKColumnName() string {
	return "id"
}

//newDemoStruct 创建一个默认对象
func newDemoStruct() demoStruct {
	demo := demoStruct{
		//如果Id=="",保存时zorm会调用zorm.FuncGenerateStringID(),默认UUID字符串,也可以自己定义实现方式,例如 zorm.FuncGenerateStringID=funcmyId
		Id:         zorm.FuncGenerateStringID(),
		UserName:   "defaultUserName",
		Password:   "defaultPassword",
		Active:     1,
		CreateTime: time.Now(),
	}
	return demo
}

测试用例即文档

// testzorm 使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体
// 占位符统一使用?,zorm会根据数据库类型,自动替换占位符,例如postgresql数据库把?替换成$1,$2...
// 为了保持数据库兼容性,分页语句必须有order by
// zorm使用 ctx context.Context 参数实现事务传播,ctx从web层传递进来即可,例如gin的c.Request.Context()
// zorm的事务操作需要显示使用zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {})开启
package testzorm

import (
	"context"
	"fmt"
	"testing"
	"time"

	"gitee.com/chunanyong/zorm"

	//00.引入数据库驱动
	_ "github.com/go-sql-driver/mysql"
)

//dbDao 代表一个数据库,如果有多个数据库,就对应声明多个DBDao
var dbDao *zorm.DBDao

// ctx默认应该有 web层传入,例如gin的c.Request.Context().这里只是模拟
var ctx = context.Background()

//01.初始化DBDao
func init() {

	//自定义zorm日志输出
	//zorm.LogCalldepth = 4 //日志调用的层级
	//zorm.FuncLogError = myFuncLogError //记录异常日志的函数
	//zorm.FuncLogPanic = myFuncLogPanic //记录panic日志,默认使用ZormErrorLog实现
	//zorm.FuncPrintSQL = myFuncPrintSQL //打印sql的函数

	//dbDaoConfig 数据库的配置
	dbDaoConfig := zorm.DataSourceConfig{
		//DSN 数据库的连接字符串
		DSN: "root:root@tcp(127.0.0.1:3306)/readygo?charset=utf8&parseTime=true",
		//DriverName 数据库驱动名称,和DBType对应,一个数据库可以有多个驱动(DriverName)
		DriverName: "mysql",
		//DBType 数据库类型(mysql,postgresql,oracle,mssql,sqlite),zorm判断方言的依据,一个数据库可以有多个驱动(DriverName)
		DBType: "mysql",
		//MaxOpenConns 数据库最大连接数 默认50
		MaxOpenConns: 50,
		//MaxIdleConns 数据库最大空闲连接数 默认50
		MaxIdleConns: 50,
		//ConnMaxLifetimeSecond 连接存活秒时间. 默认600(10分钟)后连接被销毁重建.避免数据库主动断开连接,造成死连接.MySQL默认wait_timeout 28800秒(8小时)
		ConnMaxLifetimeSecond: 600,
		//PrintSQL 打印SQL.会使用FuncPrintSQL记录SQL
		PrintSQL: true,
	}

	// 根据dbDaoConfig创建dbDao, 一个数据库只执行一次,第一个执行的数据库为 defaultDao,后续zorm.xxx方法,默认使用的就是defaultDao
	dbDao, _ = zorm.NewDBDao(&dbDaoConfig)
}

//TestInsert 02.测试保存Struct对象
func TestInsert(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//创建一个demo对象
		demo := newDemoStruct()

		//保存对象,参数是对象指针.如果主键是自增,会赋值到对象的主键属性
		_, err := zorm.Insert(ctx, &demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	//标记测试失败
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}

//TestInsertEntityMap 03.测试保存EntityMap对象,用于不方便使用struct的场景,使用Map作为载体
func TestInsertEntityMap(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//创建一个EntityMap,需要传入表名
		entityMap := zorm.NewEntityMap(demoStructTableName)
		//设置主键名称
		entityMap.PkColumnName = "id"
		//如果是自增序列,设置序列的值
		//entityMap.PkSequence = "mySequence"

		//Set 设置数据库的字段值
		//如果主键是自增或者序列,不要entityMap.Set主键的值
		entityMap.Set("id", zorm.FuncGenerateStringID())
		entityMap.Set("userName", "entityMap-userName")
		entityMap.Set("password", "entityMap-password")
		entityMap.Set("createTime", time.Now())
		entityMap.Set("active", 1)

		//执行
		_, err := zorm.InsertEntityMap(ctx, entityMap)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	//标记测试失败
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}

//TestQuery 04.测试查询一个struct对象
func TestQuery(t *testing.T) {

	//声明一个对象的指针,用于承载返回的数据
	demo := &demoStruct{}

	//构造查询用的finder
	finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
	//finder = zorm.NewSelectFinder(demoStructTableName, "id,userName") // select id,userName from t_demo
	//finder = zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo

	//finder.Append 第一个参数是语句,后面的参数是对应的值,值的顺序要正确.语句统一使用?,zorm会处理数据库的差异
	finder.Append("WHERE id=? and active in(?)", "41b2aa4f-379a-4319-8af9-08472b6e514e", []int{0, 1})

	//执行查询
	err := zorm.Query(ctx, finder, demo)

	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println(demo)
}

//TestQueryMap 05.测试查询map接收结果,用于不太适合struct的场景,比较灵活
func TestQueryMap(t *testing.T) {

	//构造查询用的finder
	finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
	//finder.Append 第一个参数是语句,后面的参数是对应的值,值的顺序要正确.语句统一使用?,zorm会处理数据库的差异
	finder.Append("WHERE id=? and active in(?)", "41b2aa4f-379a-4319-8af9-08472b6e514e", []int{0, 1})
	//执行查询
	resultMap, err := zorm.QueryMap(ctx, finder)

	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println(resultMap)
}

//TestQuerySlice 06.测试查询对象列表
func TestQuerySlice(t *testing.T) {
	//创建用于接收结果的slice
	list := make([]demoStruct, 0)

	//构造查询用的finder
	finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
	//为了保证数据库迁移,分页语句必须要有order by
	finder.Append("order by id asc")

	//创建分页对象,查询完成后,page对象可以直接给前端分页组件使用
	page := zorm.NewPage()
	page.PageNo = 1    //查询第1页,默认是1
	page.PageSize = 20 //每页20条,默认是20

	//执行查询
	err := zorm.QuerySlice(ctx, finder, &list, page)
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println("总条数:", page.TotalCount, "  列表:", list)
}

//TestQueryMapSlice 07.测试查询map列表,用于不方便使用struct的场景,一条记录是一个map对象
func TestQueryMapSlice(t *testing.T) {
	//构造查询用的finder
	finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
	//为了保证数据库迁移,分页语句必须要有order by
	finder.Append("order by id asc")

	//创建分页对象,查询完成后,page对象可以直接给前端分页组件使用
	page := zorm.NewPage()

	//执行查询
	listMap, err := zorm.QueryMapSlice(ctx, finder, page)
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println("总条数:", page.TotalCount, "  列表:", listMap)
}

//TestUpdateNotZeroValue 08.更新struct对象,只更新不为零值的字段.主键必须有值
func TestUpdateNotZeroValue(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//声明一个对象的指针,用于更新数据
		demo := &demoStruct{}
		demo.Id = "41b2aa4f-379a-4319-8af9-08472b6e514e"
		demo.UserName = "UpdateNotZeroValue"

		//更新 "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["UpdateNotZeroValue","41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := zorm.UpdateNotZeroValue(ctx, demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

//TestUpdate 09.更新struct对象,更新所有字段.主键必须有值
func TestUpdate(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {

		//声明一个对象的指针,用于更新数据
		demo := &demoStruct{}
		demo.Id = "41b2aa4f-379a-4319-8af9-08472b6e514e"
		demo.UserName = "TestUpdate"

		_, err := zorm.Update(ctx, demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
}

//TestUpdateFinder 10.通过finder更新,zorm最灵活的方式,可以编写任何更新语句,甚至手动编写insert语句
func TestUpdateFinder(t *testing.T) {
	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		finder := zorm.NewUpdateFinder(demoStructTableName) // UPDATE t_demo SET
		//finder = zorm.NewDeleteFinder(demoStructTableName)  // DELETE FROM t_demo
		//finder = zorm.NewFinder().Append("UPDATE").Append(demoStructTableName).Append("SET") // UPDATE t_demo SET
		finder.Append("userName=?,active=?", "TestUpdateFinder", 1).Append("WHERE id=?", "41b2aa4f-379a-4319-8af9-08472b6e514e")

		//更新 "sql":"UPDATE t_demo SET  userName=?,active=? WHERE id=?","args":["TestUpdateFinder",1,"41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := zorm.UpdateFinder(ctx, finder)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

//TestUpdateEntityMap 11.更新一个EntityMap,主键必须有值
func TestUpdateEntityMap(t *testing.T) {
	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//创建一个EntityMap,需要传入表名
		entityMap := zorm.NewEntityMap(demoStructTableName)
		//设置主键名称
		entityMap.PkColumnName = "id"
		//Set 设置数据库的字段值,主键必须有值
		entityMap.Set("id", "41b2aa4f-379a-4319-8af9-08472b6e514e")
		entityMap.Set("userName", "TestUpdateEntityMap")
		//更新 "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["TestUpdateEntityMap","41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := zorm.UpdateEntityMap(ctx, entityMap)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

//TestDelete 12.删除一个struct对象,主键必须有值
func TestDelete(t *testing.T) {
	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		demo := &demoStruct{}
		demo.Id = "ae9987ac-0467-4fe2-a260-516c89292684"

		//删除 "sql":"DELETE FROM t_demo WHERE id=?","args":["ae9987ac-0467-4fe2-a260-516c89292684"]
		_, err := zorm.Delete(ctx, demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

//TestOther 13.其他的一些说明.非常感谢您能看到这一行
func TestOther(t *testing.T) {

	//场景1.多个数据库.通过对应数据库的dbDao,调用BindContextDBConnection函数,把这个数据库的连接绑定到返回的ctx上,然后把ctx传递到zorm的函数即可.
	newCtx, err := dbDao.BindContextDBConnection(ctx)
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

	finder := zorm.NewSelectFinder(demoStructTableName).Append("order by id ")
	//把新产生的newCtx传递到zorm的函数
	list, _ := zorm.QueryMapSlice(newCtx, finder, nil)
	fmt.Println(list)

	//场景2.单个数据库的读写分离.设置读写分离的策略函数.
	zorm.FuncReadWriteStrategy = myReadWriteStrategy

	//场景3.如果是多个数据库,每个数据库还读写分离,按照 场景1 处理

}

//单个数据库的读写分离的策略 rwType=0 read,rwType=1 write
func myReadWriteStrategy(rwType int) *zorm.DBDao {
	//根据自己的业务场景,返回需要的读写dao,每次需要数据库的连接的时候,会调用这个函数
	return dbDao
}



性能压测

测试代码:https://github.com/alphayan/goormbenchmark

指标说明 总时间,平均每次纳秒数,平均每次分配的内存,平均每次内存分配次数

更新性能zorm,gorm,xorm相当.读取性能zorm比gorm,xorm快一倍

2000 times - Insert
      zorm:     9.05s      4524909 ns/op    2146 B/op     33 allocs/op
      gorm:     9.60s      4800617 ns/op    5407 B/op    119 allocs/op
      xorm:    12.63s      6315205 ns/op    2365 B/op     56 allocs/op

    2000 times - BulkInsert 100 row
      xorm:    23.89s     11945333 ns/op  253812 B/op   4250 allocs/op
      gorm:     Don't support bulk insert - https://github.com/jinzhu/gorm/issues/255
      zorm:     Don't support bulk insert

    2000 times - Update
      xorm:     0.39s       195846 ns/op    2529 B/op     87 allocs/op
      zorm:     0.51s       253577 ns/op    2232 B/op     32 allocs/op
      gorm:     0.73s       366905 ns/op    9157 B/op    226 allocs/op

  2000 times - Read
      zorm:     0.28s       141890 ns/op    1616 B/op     43 allocs/op
      gorm:     0.45s       223720 ns/op    5931 B/op    138 allocs/op
      xorm:     0.55s       276055 ns/op    8648 B/op    227 allocs/op

  2000 times - MultiRead limit 1000
      zorm:    13.93s      6967146 ns/op  694286 B/op  23054 allocs/op
      gorm:    26.40s     13201878 ns/op 2392826 B/op  57031 allocs/op
      xorm:    30.77s     15382967 ns/op 1637098 B/op  72088 allocs/op
展开阅读全文

代码

的 Gitee 指数为
超过 的项目

zorm 的相关博客

还没有任何博客,写博客

zorm 的相关问答

还没有任何问答,马上提问

评论 (29)

加载中
太复杂了,用sqlbuilder直接生成sql会比这个快的多,或则直接执行sql,在具体的也许场景里几乎用不到orm。相比较而言还不如用sqlbuilder或则模板构造sql。java那一套太笨重了。xorm也支持事务传播机制,我记得
04/05 16:29
回复
举报
光石头软件作者
拼接sql是个苦力活,容易出错,使用也不方便,对象映射还是刚需的.xorm的事务传播是自己传递参数,没有统一规范,不同的开发人员实现的风格也会不同,属于用户自定义实现.
04/05 17:58
回复
举报
有个库叫xormplus/xorm直接实现了spring的那一套事务传播机制,都好几年了
04/05 18:58
回复
举报
光石头软件作者
它是传递的session,没有强制性,如果不传,就不能支持了,开发人员标准不统一的
04/05 19:00
回复
举报
光石头软件作者
zorm要比xorm和gorm简单多啦,API要比他们少的多,概念性的东西也少的多
04/05 18:06
回复
举报
只用sqlbuilder和sql操作,xorm,gorm,sqlx的api都很少,用go的时候强行orm多余,最多返回结果直接和struct映射,这东西,几个库都实现了
04/05 18:57
回复
举报
光石头软件作者
还是要考虑兼容数据库分页,主键自增或者序列,使用map保存和map接收返回值,事务处理以及传播,读写分离以及多库.....这些都是要实现的呀
04/05 19:03
回复
举报
这些几年前就有很多实现了,我们现在用的我们的内部库。只是看到最近很多从java过来的套用以前java的东西,感觉是对go最大的误解。没事,我也就是看你的项目多了一句嘴而已,你继续你的项目,百花齐放挺好。另外之前看过xorm也实现了读写分离,不过小项目在这一层实现可以,大项目不建议耦合在这一层
04/05 19:07
回复
举报
光石头软件作者
还是建议先跑一遍,再考虑是不是java那套东西呀 zorm是函数一等,完全符合go的设计思想的,还是建议手动跑一下呢
04/05 19:12
回复
举报
主要是我看了你的测试数据有点感慨,几年前我们当时也在做开源库调研的时候做过测试,那时候直接执行sql的性能都是比orm快一倍的,xorm,gorm,beedb,gdb,sqlx好多库。xorm,gorm这种重orm的库自己和自己比较也是,所以就在评论里写了那第一个评论。
04/05 19:15
回复
举报
光石头软件作者
回复 @NoneObject : 是啊,现在的xorm和gorm的确很慢啊 ,zorm已经比他们好多啦,这个还是要均衡考虑的,直接原生sql不使用struct反射也不合适
04/05 19:18
回复
举报
回复 @NoneObject : 真要需要性能的时候,基本都是直接舍弃orm方式的
04/05 19:18
回复
举报
光石头软件作者
回复 @NoneObject : 嗯,zorm有map的方式,就绕过反射了,和手动拼写sql相差不大,但是api比较友好一些,如果还是嫌慢,可以直接在finder里写原生的sql,这个就是原生的性能了
04/05 19:20
回复
举报
回复 @光石头 : 使用xorm,gorm之类的是一样的有直接执行raw的方式,最后结果映射到struct,其实大道至简。前面不用orm,采用sqlbuilder方式在go中更合适些,sqlbuilder在api上其实和orm差不多,最后就是结果反射到struct,这一步几个库都差不多。目前go的数据库框架库主要耗时就是前面的orm翻译sql和最终结果反射到struct,其他的中间数据库处理过程耗时各库基本一样的。所以基本上最后一步大家都需要,而第一步可以不需要
04/05 19:24
回复
举报
回复 @光石头 : 其实各库都有map的方式,几个库的源码我都读过,包括后来出现的goframe那个库,这部分功能各库基本上是差不多的
04/05 19:26
回复
举报
光石头软件作者
回复 @NoneObject : zorm的finder就是sql的载体,也就是sqlbuilder,是拼接的原生sql,不需要翻译的.返回的时候如果性能要求,可以变成map,不反射到struct
04/05 19:26
回复
举报
回复 @光石头 : 这个功能那几个库也有。估计你没测过。其实现在go的库雷同度挺多的,不过目前确实没有一个比较有统治力的库,包括我们自己后来也不用开源的那几个自己实现了一套内部库。其实go在数据库处理这方面的开源力量挺散挺弱的,相对于其他领域的开源库
04/05 19:30
回复
举报
光石头软件作者
回复 @NoneObject : 甚至可以在finder里Append 原生insert sql语句,我也是一直使用原生sql,比较排斥gorm,xorm 那样用api再造一套sql语法
04/05 19:31
回复
举报
光石头软件作者
回复 @NoneObject : 你们內部库主要侧重哪些功能呀,大佬提点一下啊,我好改进zorm
04/05 19:33
回复
举报
我们实现的那个正常的orm功能都有,加上了sql处理部分,主要是像beetl学习了下,读写分离,事务这些都有。spring那一套事务的东西都移植了。另外缓存方面,xorm原来那个缓存有点弱,还有bug。我们几年前选型的xorm。另外针对数据库函数和存储过程调用有加强。动态sql的外调和反调等。这是基础组件库的同事开发的,不过业务组实际使用的时候,orm的功能基本都不用。
04/05 19:42
回复
举报
好奇怪,oschina安卓客户端好像有bug,每次回复都不能接着你的留言评论。很高兴和你讨论。就是有时候看信息的时候好乱⁼̴̤̆ ꇴ ⁼̴̤̆
04/05 19:44
回复
举报
光石头软件作者
嗯,都差不多,大道至简,直接撸sql吧
04/05 19:46
回复
举报
弱弱地问下如何支持分布式的事务
03/30 08:32
回复
举报
光石头软件作者
zorm实现了标准的数据库事务,目前并未原生实现分布式事务,需要业务和第三方实现配合
03/30 10:03
回复
举报
尽快完善相关中文文档吧,例子要详细,才会有人用的,加油
03/25 18:05
回复
举报
光石头软件作者
多谢支持,文档会尽快完善
03/26 11:57
回复
举报
光石头软件作者
03/27 14:52
回复
举报
不要走java orm的老路
03/25 11:38
回复
举报
光石头软件作者
不会的
03/25 13:04
回复
举报
更多评论
29 评论
34 收藏
分享
返回顶部
顶部