为了让这个库更好用,我比较研究了各措辞的主流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体系的ormJava措辞是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体系的djangodjango推举的开拓方法,也是先建模型,但是在查询的时候,这建立的模型,基本上毫无用途
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体系的flaskflask本身没有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 gengorm 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.Usererr := 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体系下的entent 是 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体系下的aormaorm 是我自己开拓的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,并且不须要额外的事情