前言
最近一段时间,我应用 golang
开发了一个新的 ORM
库。
为了让这个库更好用,我比拟钻研了各语言的支流 ORM
库,发现有一些语言的 ORM
库的确很好用,而有另外一些语言的库那不是个别的难用。
而后我总结了他们呢的一些共性和差别点,于是造成了本文的次要内容。
本文会先阐明什么是 SQL 编写难题,以及探讨一下 code first
和 database first
的优缺点。
而后根据这两个问题的论断去扫视目前支流后端语言 java
, c#
, php
, python
, go
各自的 orm 库,比照钻研下他们的优缺点。最初给出总结和参考文档。
如果你须要做技术选型,或者做技术钻研,或者相似于我做框架开发,或者单纯地理解各语言的差别,或者就是想吹个牛,倡议保留或珍藏。如果本文所波及到的内容有任何不正确,欢送批评指正。
舒适提醒,本文会有一些戏谑或者调侃成分,并非对某些语言或者语言的使用者有任何歧视意见。
如果对你造成了某些挫伤,请多包涵。
什么是 SQL 编写难题
如果你是做 web 开发,那么必然须要保留数据到数据库,这个时候你必须相熟应用 sql 语句来读写数据库。
sql 自身不难,命令也就那几个,关键字也不算多,然而为什么编写 sql 会成为难题呢?
比方上面的 sql
select * from user
insert user (name,mobile) values ('tang','18600000000')
它有什么难题?简略的单表操作嘛,一点难题没有,凡是学过点 sql
的程序员都能写进去,并且保障正确。我预计比例能超过 90%
然而,如果你须要写上面的 sql 呢?
SELECT
article.*,
person.name as person_name
FROM article
LEFT JOIN person ON person.id=article.person_id
WHERE article.type = 0
AND article.age IN (18,20)
这个也不简单,就是你在做查问列表的时候,会常常用到的联表查问。你是否还有勇气说,写进去的 sql
相对正确。我预计比例不超过 70%
再略微简单点,如果是上面的 sql?
SELECT
o.*,
d.department_name,
(SELECT Sum(so.goods_fee) AS task_detail_target_completed_tem
FROM sale_order so
WHERE so.merchant_id = '356469725829664768'
AND so.create_date BETWEEN (20230127) AND (20230212)
AND so.delete_state = 2
AND so.department_id = o.department_id
) AS task_detail_target_completed
FROM task_detail o
LEFT JOIN department d ON d.department_id=o.department_id
WHERE o.merchant_id = '356469725829664768'
AND o.task_id = '356469725972271104768'
这是我我的项目里实在的 sql 语句,目标是统计出所有部门在某时间段内各自的业绩。逻辑上也不太简单,但你是否还有勇气说,写进去的 sql
相对正确。我预计比例不超过 40%
如下面的 sql 所示,SQL 编写难题在于以下几方面。
要保障字段正确
应该有的字段不能少,不应该有的字段不能多。
比方你把 mobile
误打成mobike
,这属于拼写错误,然而这个拼写错误只有在理论运行的时候才会通知你字段名错了。
并且我的项目越大,表越多,字段越多,这种拼写错误产生的可能性越大。以至于能够必定的说,100% 的可能性会呈现。
要特地留神 sql 语法
例如你在查问的时候必须写from
,相对不能误写成form
,然而在理论开发过程中,很容易就打错了。
这种谬误,也只有运行的时候才会通知你语法错了。并且 sql
越简单,这种语法错误产生的可能性越大。
编辑器不会有 sql 的语法提醒
常见的编码用的软件,对于 sql 相干的代码,不会有语法提醒,也不会有表名提醒,字段名提醒。
最终的代码品质如何全凭你的目力, 教训, 能力。
很显然,既然存在该难题,那么哪个 ORM 能解决该难题,就应该算得上好,如果不能解决,则不能称之为好。
什么是 code first 和 database first
这俩概念并不是新概念,然而我预计大多数开发者并不相熟。
所谓 code first, 相近的词是 model fist, 意思是模型优先,指的是在设计和开发零碎时,优先和重点做的工作是设计业务模型,而后依据业务模型去创立数据库。
所谓 database first,意思是数据库优先,指的是在设计和开发零碎时,优先和重点做的工作是创立数据库构造,而后去实现业务。
这里我提到了几个词语,可能在不同的语言里叫法不一样,可能不同的人的叫法也不一样,为了下述不便,咱们举例子来说。
code first 例子
假如我是一个对电商零碎齐全不懂的小白,手头上也没有如何设计电商零碎的材料,我和我的搭档只是模糊地晓得电商零碎次要业务就是解决订单。
而后我大略会晓得这个订单,次要的信息包含哪个用户下单,什么工夫下单,有哪几种商品,数量别离是多少,依据这些已有的信息,我能够设计进去业务模型如下
public class OrderModel {
// 订单编号
Integer orderId;
// 用户编号
Integer userId;
// 订单工夫
Integer createTime;
// 订单详情(蕴含商品编号,商品数量)
String orderDetail;
}
很简略,对吧,这个模型很匹配我目前对系统的认知。接下来会做各种业务逻辑,最初要做的是将订单模型的数据保留到数据库。然而在保留数据到数据库的时候,就有一些思考了。
我能够将下面 OrderModel
业务模型建设一张对应表,外面的 4 个属性,对应数据表里的 4 个字段,这齐全能够。
然而我是电商小白,不是数据库小白啊,这样存储的话,必定不利于统计订单商品的。
所以我换一种策略,将 OrderModel
的信息进行拆分,将前三个属性 orderId, userId, createTime 放到一个新的类里。
而后将 orderDetail 的信息进行再次合成,放到另一个类里
public class OrderEntity {
Integer orderId;
Integer userId;
Integer createTime;
}
public class OrderDetailEntity {
Integer orderDetailId;
Integer orderId;
Integer goodsId;
Integer goodsCount;
}
最初,在数据库建设两张表 order
,order_detail
,表构造别离对应类OrderEntity
,OrderDetailEntity
的构造。
至此,咱们实现了从业务模型 OrderModel
到数据表 order
,order_detail
的过程。
这就是 code first,留神这个过程的关键点,我优先思考的是模型和业务实现,前面将业务模型数据进行合成和保留是主要的,非优先的。
database first 例子
假如我是一个对电商零碎十分相熟的老鸟,之前做过很多电商零碎,那么我在做新的电商零碎的时候,就齐全能够先设计数据库。
order
表放订单次要数据,外面有 xxx 几个字段,别离有什么作用,有哪些状态值
order_detail
表放订单详情数据,,外面有 xxx 几个字段,别离有什么作用
这些都能够很分明和明确。而后依据表信息,生成 OrderEntity
, 以及OrderDetailEntity
即可开始接下来的编码工作。这种状况下 OrderModel
可能有,也可能没有。
这就是 database first,留神这个过程的关键点,我优先思考的是数据库构造和数据表构造。
两种形式比照
code first 模式下,零碎设计者优先思考的是业务模型OrderModel
,它能够形容分明一个残缺业务,包含它的所有业务细节(什么人的订单,什么时候的订单,订单蕴含哪些商品,数量多少),有利于设计者对于零碎的整体把控。
database first 模式下,零碎设计者优先思考的是数据表order
,order_detail
,他们中任何一张表都不能残缺的形容分明一个残缺业务,只可能形容部分细节,不利于设计者对于零碎的整体把控。
在这里,淘气的同学会问,在 database first 模式下,我把 order
,order_detail
的信息一起看,不就晓得残缺的业务细节了吗?
的确是这样,但这里有一个前提,前提是你必须明确的晓得 order
,order_detail
是须要一起看的,而你晓得他们须要一起看的前提是你理解电商零碎。如果你设计的不是电商零碎,而是电路系统,你还理解吗?还晓得哪些表须要一起看吗?
至此,咱们能够有以下浅显的判断:
对于新我的项目,不相熟的业务,code first 模式更适宜一些
对于老我的项目,相熟的业务,database first 模式更适合一些
如果两种模式都能够的话,优先应用 code first 模式,便于了解业务,把控我的项目
如果哪个 ORM 反对 code first , 咱们能够稍稍认为它更好一些
Java 体系的 orm
Java 语言是 web 开发畛域处于领先地位,这一点半信半疑。它的长处很显著,然而毛病也不是没有。
国内利用比拟宽泛的 orm 是 Mybatis,以及衍生品 Mybatis-plus 等
实际上 Mybatis 团队还出了另外一款产品,MyBatis Dynamic SQL,国内我见用的不多,探讨都较少。英文还能够的同学,能够看上面的文档。
另外还有 jOOQ,实际上跟 MyBatis Dynamic SQL 十分相似,有趣味的能够去翻翻
上面,咱们举一些例子,来比照一下他们的基本操作
Java 体系的 Mybatis
单就 orm 这一块,国内用的最多的应该是 Mybatis,说到它的应用体验吧,那几乎是一言难尽。
你须要先定义模型, 而后编写 xml
文件用来映射数据, 而后创立 mapper 文件,用来执行 xml
里定于的 sql。
从这个流程能够看出,两头的 xml
文件起到核心作用,外面不光有数据类型转换,还有最外围的 sql
语句。
典型的 xml
文件内容如下
<mapper namespace="xxx.mapper.UserMapper">
<insert id="insertUser" parameterType="UserEntity">
insert into user (id,name,mobile)
values (#{id},#{name},#{mobile})
</insert>
<update id="updateUser" parameterType="UserEntity">
update user set
name = #{name},
mobile = #{mobile}
where id = #{id}
</update>
<delete id="deleteUser">
delete from user where id = #{id}
</delete>
<select id="selectUsers" resultType="UserVO">
select u.*, (select count(*) from article a where a.uid=u.id) as article_count
from user u
where u.id = #{id}
</select>
</mapper>
你在编写这个 xml
文件的时候,这个手写 sql 没有本质区别,肯定会遇到方才说到的SQL 编写难题
。
Java 体系的 Mybatis-plus
这里有必要提一下 Mybatis-plus,它是国内的团队开发进去的工具,算是对 Mybatis 的扩大吧,它缩小了 xml
文件内容的编写,缩小了一些开发的苦楚。比方,你能够应用如下的代码来实现以上雷同的工作
userService.insert(user);
userService.update(user);
userService.deleteById(user);
List<UserEntity> userList = userService.selectList(queryWrapper);
实现这些工作,你不须要编写任何 xml
文件,也不须要编写 sql
语句,如之前所述,缩小了一些开发的苦楚。
然而,请你留神我的用词,是缩小了一些。
对于连表操作,嵌套查问等波及到多表操作的事件,它就不行了,为啥不行,因为基本就不反对啊。
遇到这种状况,你就老老实实的去写 xml
吧,而后你还会遇到方才说到的SQL 编写难题
。
Java 体系的 Mybatis3 Dynamic Sql
值得一提的是 Mybatis3 Dynamic Sql,翻译一下就是动静 sql。还是方才说的国内我见用的不多,探讨都较少,然而评估看上去挺好。
简略来说,能够依据不同条件拼接出 sql 语句。不同于下面的 Mybatis,这些 sql 语句是程序运行时生成的,而不是提前写好的,或者定义好的。
它的应用流程是,先在数据库里定义好数据表,而后创立模型文件,让而后通过命令行工具,将每一个表生成如下的反对文件
public final class PersonDynamicSqlSupport {public static final Person person = new Person();
public static final SqlColumn<Integer> id = person.id;
public static final SqlColumn<String> firstName = person.firstName;
public static final SqlColumn<LastName> lastName = person.lastName;
public static final SqlColumn<Date> birthDate = person.birthDate;
public static final SqlColumn<Boolean> employed = person.employed;
public static final SqlColumn<String> occupation = person.occupation;
public static final SqlColumn<Integer> addressId = person.addressId;
public static final class Person extends SqlTable {public final SqlColumn<Integer> id = column("id", JDBCType.INTEGER);
public final SqlColumn<String> firstName = column("first_name", JDBCType.VARCHAR);
public final SqlColumn<LastName> lastName = column("last_name", JDBCType.VARCHAR, "examples.simple.LastNameTypeHandler");
public final SqlColumn<Date> birthDate = column("birth_date", JDBCType.DATE);
public final SqlColumn<Boolean> employed = column("employed", JDBCType.VARCHAR, "examples.simple.YesNoTypeHandler");
public final SqlColumn<String> occupation = column("occupation", JDBCType.VARCHAR);
public final SqlColumn<Integer> addressId = column("address_id", JDBCType.INTEGER);
public Person() {super("Person");
}
}
}
能够看出,这里的次要性能能是将表内的字段,与 java 我的项目里的类外面的属性,做了一一映射。
接下来你在开发的时候,就不必关怀表名,以及字段名了,间接应用方才生成的类,以及类上面的那些属性。具体如下
SelectStatementProvider selectStatement = select(id.as("A_ID"), firstName, lastName, birthDate, employed,occupation, addressId)
.from(person)
.where(id, isEqualTo(1))
.or(occupation, isNull())
.build()
.render(RenderingStrategies.MYBATIS3);
List<PersonRecord> rows = mapper.selectMany(selectStatement);
如下面的代码,益处有以下四点
- 你不再须要手写 sql
- 也不必在意字段名了,因为应用的都是类,或者属性,编写代码的时候编辑器会有提醒,编译的时候如果有谬误也会提醒,理论运行的时候就不会有问题了。
- 联表查问,嵌套查问啥的,也都反对
- 完满避开了
SQL 编写难题
当然带来了额定的事件,比方你要应用工具来生成 PersonDynamicSqlSupport
类,比方你要先建表。
先建表这事儿,很显著就属于 database first
模式。
C# 体系的 orm
C# 在工业畛域,游戏畛域用的多一些,在 web 畛域少一些。
它也有本人的 orm,名字叫 Entity Framework Core, 始终都是微软公司在保护。
上面是一个典型的联表查问
var id = 1;
var query = database.Posts
.Join(database.Post_Metas,
post => post.ID,
meta => meta.Post_ID,
(post, meta) => new {Post = post, Meta = meta}
)
.Where(postAndMeta => postAndMeta.Post.ID == id);
这句代码的次要作用是,将数据库里的 Posts 表,与 Post_Metas 表做内联操作,而后取出 Post.ID 等于 1 的数据
这里呈现的 Post,以及 Meta 都是提前定义好的模型,也就是类。Post.ID 是 Post 的一个属性,也是提前定义好的。
整个性能的长处很多,你不再须要手写 sql,不须要关怀字段名,不须要生成额定类,也不会有语法错误,你只须要提前定义好模型,齐全没有SQL 编写难题
,很显著就属于 code first
模式。
比照 java 的 Mybatis 以及 Mybatis3 Dynamic Sql 来说,你能够脑补一下上面的场景
PHP 体系的 orm
php 体系内,框架也十分多,比方常见的laravel
,symfony
,这里咱们就看这两个,比拟有代表性
PHP 体系的 laravel
应用 php 语言开发 web 利用的也很多,其中比拟闻名的是 laravel
框架,比拟典型的操作数据库的代码如下
$user = DB::table('users')->where('name', 'John')->first();
这里没有应用模型(就算应用了也差不多),代码里呈现的 users 就是数据库表的名字,name 是 users 表里的字段名,他们是被间接写入代码的
很显著它会产生SQL 编写难题
并且,因为是先设计数据库,必定也属于 database first
模式
PHP 体系的 symfony
这个框架历史也比拟悠久了,它应用了 Doctrine 找个类库作为 orm
应用它之前,也须要先定义模型,而后生成反对文件,而后建表,然而在理论应用的时候,还是和 laravel 一样,表名,字段名都须要硬编码
$repository = $this->getDoctrine()->getRepository('AppBundle:Product');
// query for a single product by its primary key (usually "id")
// 通过主键(通常是 id)查问一件产品
$product = $repository->find($productId);
// dynamic method names to find a single product based on a column value
// 动静办法名称,基于字段的值来找到一件产品
$product = $repository->findOneById($productId);
$product = $repository->findOneByName('Keyboard');
// query for multiple products matching the given name, ordered by price
// 查问多件产品,要匹配给定的名称和价格
$products = $repository->findBy(array('name' => 'Keyboard'),
array('price' => 'ASC')
);
很显著它也会产生SQL 编写难题
另外,并不是先设计表,属于 code first
模式
python 体系的 orm
在 python 畛域,有一个十分驰名的框架,叫 django, 另外一个比拟闻名的叫 flask, 前者谋求大而全,后者谋求小而精
python 体系的 django
django 举荐的开发方法,也是先建模型,然而在查问的时候,这建设的模型,基本上毫无用处
res=models.Author.objects.filter(name='jason').values('author_detail__phone','name')
print(res)
# 反向
res = models.AuthorDetail.objects.filter(author__name='jason') # 拿作者姓名是 jason 的作者详情
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','author__name')
print(res)
# 2. 查问书籍主键为 1 的出版社名称和书的名称
res = models.Book.objects.filter(pk=1).values('title','publish__name')
print(res)
# 反向
res = models.Publish.objects.filter(book__id=1).values('name','book__title')
print(res)
如上连表查问的代码,values(‘title’,’publish__name’) 这外面写的全都是字段名,硬编码进去,进而产生 sql 语句,查问出后果
很显然,它也会产生SQL 编写难题
另外,并不是先设计表,属于 code first
模式
python 体系的 flask
flask 自身没有 orm,个别搭配 sqlalchemy 应用
应用 sqlalchemy 的时候,个别也是先建模型,而后查问的时候,能够间接应用模型的属性,而无须硬编码
result = session.
query(User.username,func.count(Article.id)).
join(Article,User.id==Article.uid).
group_by(User.id).
order_by(func.count(Article.id).desc()).
all()
如上 Article.id 即是 Article 模型下的 id 属性
很显然,它不会产生SQL 编写难题
另外,并不是先设计表,属于 code first
模式
go 体系的 orm
在 go 体系,orm 比拟多,属于百花齐放的状态,比方国内用的多得 gorm 以及 gorm gen,国外比拟多的 ent, 当然还有我本人写的 arom
go 体系下的 gorm
应用 gorm,个别的流程是你先建设模型,而后应用相似如下的代码进行操作
type User struct {
Id int
Age int
}
type Order struct {
UserId int
FinishedAt *time.Time
}
query := db.Table("order").
Select("MAX(order.finished_at) as latest").
Joins("left join user user on order.user_id = user.id").
Where("user.age > ?", 18).
Group("order.user_id")
db.Model(&Order{}).
Joins("join (?) q on order.finished_at = q.latest", query).
Scan(&results)
这是一个嵌套查问,尽管定义了模型,然而查问的时候并没有应用模型的属性,而是输出硬编码
很显然,它会产生SQL 编写难题
另外,是先设计模型,属于 code first
模式
go 体系下的 gorm gen
gorm gen 是 gorm 团队开发的另一款产品,和 mybaits 下的 Mybatis3 Dynamic Sql 比拟像
它的流程是 先创立数据表,而后应用工具生成构造体 (类) 和反对代码, 而后再应用生成的构造体
它生成的比拟要害的代码如下
func newUser(db *gorm.DB) user {_user := user{}
_user.userDo.UseDB(db)
_user.userDo.UseModel(&model.User{})
tableName := _user.userDo.TableName()
_user.ALL = field.NewAsterisk(tableName)
_user.ID = field.NewInt64(tableName, "id")
_user.Name = field.NewString(tableName, "name")
_user.Age = field.NewInt64(tableName, "age")
_user.Balance = field.NewFloat64(tableName, "balance")
_user.UpdatedAt = field.NewTime(tableName, "updated_at")
_user.CreatedAt = field.NewTime(tableName, "created_at")
_user.DeletedAt = field.NewField(tableName, "deleted_at")
_user.Address = userHasManyAddress{db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Address", "model.Address"),
}
_user.fillFieldMap()
return _user
}
留神看,其中大多数代码的作用是啥?不意外,就是将构造体的属性与表字段做映射关系
_user.Name 对应 name
_user.Age 对应 age
如此,跟 mybaits 下的 Mybatis3 Dynamic Sql 的思路十分统一
典型查问代码如下
u := query.User
err := u.WithContext(ctx).
Select(u.Name, u.Age.Sum().As("total")).
Group(u.Name).
Having(u.Name.Eq("group")).
Scan(&users)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
这是一个分组查问,定义了模型,也应用了模型的属性。
然而呢,它须要应用工具生成额定的反对代码,并且须要先定义数据表
很显然,它不会产生SQL 编写难题
另外,它是先设计表,属于 database first
模式
go 体系下的 ent
ent 是 facebook 公司开发的 Orm 产品,与 gorm gen 有相通,也有不同
相同点在于,都是利用工具生成实体与数据表字段的映射关系
不同点在于 gorm gen 先有表和字段,而后生成实体
ent 是没有表和字段,你本人手动配置,配置完了一起生成实体和建表
接下来,看一眼 ent 生成的映射关系
const (
// Label holds the string label denoting the user type in the database.
Label = "user"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldAge holds the string denoting the age field in the database.
FieldAge = "age"
// FieldAddress holds the string denoting the address field in the database.
FieldAddress = "address"
// Table holds the table name of the user in the database.
Table = "users"
)
有了映射关系,应用起来就比较简单了
u, err := client.User.
Query().
Where(user.Name("realcp")).
Only(ctx)
留神,这里没有硬编码
它须要应用工具生成额定的反对代码,并且须要先配置表构造
很显然,它不会产生SQL 编写难题
另外,它属于先设计表,属于 database first
模式
go 体系下的 aorm
aorm 是我本人开发的 orm 库,汲取了 ef core 的一些长处,比拟外围的步骤如下
和大多数 orm 一样,须要先建设模型,比方
type Person struct {
Id null.Int `aorm:"primary;auto_increment" json:"id"`
Name null.String `aorm:"size:100;not null;comment: 名字" json:"name"`
Sex null.Bool `aorm:"index;comment: 性别" json:"sex"`
Age null.Int `aorm:"index;comment: 年龄" json:"age"`
Type null.Int `aorm:"index;comment: 类型" json:"type"`
CreateTime null.Time `aorm:"comment: 创立工夫" json:"createTime"`
Money null.Float `aorm:"comment: 金额" json:"money"`
Test null.Float `aorm:"type:double;comment: 测试" json:"test"`
}
而后实例化它,并且保存起来
//Instantiation the struct
var person = Person{}
//Store the struct object
aorm.Store(&person)
而后即可应用
var personItem Person
err := aorm.Db(db).Table(&person).WhereEq(&person.Id, 1).OrderBy(&person.Id, builder.Desc).GetOne(&personItem)
if err != nil {fmt.Println(err.Error())
}
很显然,它不会产生SQL 编写难题
另外,它属于先设计模型,属于 code first
模式
总结
本文,咱们提出了两个掂量 orm 性能的准则,并且比照了几大支流后端语言的 orm,汇总列表如下
框架 | 语言 | SQL 编写难题 | code first | 额定创立文件 |
---|---|---|---|---|
MyBatis 3 | java | 有难度 | 不是 | 须要 |
MyBatis-Plus | java | 有难度 | 不是 | 不须要 |
MyBatis Dynamic SQL | java | 没有 | 不是 | 须要 |
jOOQ | java | 没有 | 不是 | 须要 |
ef core | c# | 没有 | 是 | 不须要 |
laravel | php | 有难度 | 不是 | 不须要 |
symfony | php | 有难度 | 不是 | 须要 |
django | python | 有难度 | 不是 | 不须要 |
sqlalchemy | python | 没有 | 是 | 不须要 |
grom | go | 有难度 | 是 | 不须要 |
grom gen | go | 没有 | 不是 | 须要 |
ent | go | 没有 | 不是 | 须要 |
aorm | go | 没有 | 是 | 不须要 |
单就从这张表来说,不思考其余条件,在做 orm 技术选型时,
如果你应用 java 语言,请抉择 MyBatis Dynamic SQL 或者 jOOQ,因为抉择他们不会有SQL 编写难题
如果你应用 c# 语言,请抉择 ef core, 这曾经是最棒的 orm 了,不会有SQL 编写难题
,反对code first
,并且不须要额定的工作
如果你应用 php 语言,请抉择 laravel 而不是 symfony,反正都有SQL 编写难题
,那就挑个容易应用的
如果你应用 python 语言,请抉择 sqlalchemy 库,不会有SQL 编写难题
,反对code first
,并且不须要额定的工作
如果你应用 go 语言,请抉择 aorm 库,不会有SQL 编写难题
,反对code first
,并且不须要额定的工作
好了,文章写两天了,终于写完了。如果对你有帮忙,记得点赞,珍藏,转发。
如果我有说的不适合,或者不对的中央,请在上面狠狠的批评我。
参考文档
MyBatis 3
MyBatis-Plus
MyBatis Dynamic SQL
jOOQ: The easiest way to write SQL in Java
Entity Framework Core 概述 – EF Core | Microsoft Learn
数据库和 Doctrine ORM – Symfony 开源 – Symfony 中国 (symfonychina.com)
Django(ORM 查问、多表、跨表、子查问、联表查问)– 知乎 (zhihu.com)
Sqlalchemy join 连表查问_FightAlita 的博客 -CSDN 博客_sqlalchemy 连表查问
Gorm + Gen 主动生成数据库构造体_Onemorelight95 的博客 -CSDN 博客_gorm 主动生成
tangpanqing/aorm: Operate Database So Easy For GoLang Developer (github.com)