关于orm:开源-Net-ORM-访问-Firebird-数据库

前言Firebird 是一个跨平台的关系数据库系统,目前可能运行在 Windows、linux 和各种 Unix 操作系统上,提供了大部分 SQL-99 规范的性能。它既能作为多用户环境下的数据库服务器运行,也提供嵌入式数据库的实现。 Firebird 脱胎于 Borland 公司的开源版数据库 Interbase6.0,是一个齐全非商业化的产品,用 C 和 C++ 开发。因为与 interbase 的血缘关系,大部分 interbase 的开发工具能够间接利用到 Firebird 开发中。Firebird 应用 Mozilla Public License v.1.1 许可证发行。 FreeSql 反对 Firebird 嵌入式数据库,与 Sqlite 一样属于本地数据库,并且可靠性强于 sqlite,数据库文件不过 10兆 大小。 1、装置环境数据库环境:Firebird (嵌入式版本) 下载地址:https://firebirdsql.org/en/se....NET版本:.net6.0 下载地址:https://dotnet.microsoft.com/...开发机器 :windows 10 2、创立我的项目咱们以 console 类型我的项目试验 插入、删除、更新、查问 等性能,创立控制台我的项目,应用命令: dotnet new console dotnet add package FreeSql.Provider.Firebird dotnet add package FreeSql.Repository 3、创立实体模型using System;using FreeSql.DataAnnotations;[Table(Name = "USER_FIREBIRD")]public class User{ [Column(IsIdentity = true)] public long Id { get; set; } public string UserName { get; set; } public string PassWord { get; set; } public DateTime CreateTime { get; set; }}4、初始化 ORMstatic IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Firebird, @"database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456;max pool size=3") .UseMonitorCommand(cmd => Trace.WriteLine($"线程:{cmd.CommandText}\r\n")) .UseAutoSyncStructure(true) //主动创立、迁徙实体表构造 .UseNameConvert(NameConvertType.ToUpper) .Build();5、插入数据var repo = fsql.GetRepository<User>();var user = new User { UserName = "gaussdb1", PassWord = "123" };repo.Insert(user); ...

July 7, 2022 · 1 min · jiezi

关于orm:EBean-ORM-框架介绍3实体草稿功能

前文《EBean ORM 框架介绍-2.字段加密、更新日志和历史记录》 介绍了不少个性注解,本文将介绍实体草稿性能 草稿性能次要用于一些须要进行流程审批或反复批改后的重要内容公布,先打草稿确认没有问题后再行公布的场景。 此实现计划较以往齐全自主实现最大的区别在于草稿与正式版数据结构互相独立,程序逻辑却为一体 一、设置草稿@Draftable@Builder@Data@Entity@ToString(callSuper = true)@AllArgsConstructor@NoArgsConstructor@Table(name = "customer")public class Customer extends BaseModel { public static final CustomerFinder find = new CustomerFinder(); private String name; @DraftOnly Timestamp whenPublish; @DraftDirty Boolean draft; @DraftReset String author; @OneToMany(cascade = {CascadeType.ALL}) List<Group> groupList;}@DraftableElement@Entity@Table(name = "c_group")public class Group extends BaseModel { private String name;}1. 草稿注解在主实体上设置@Draftable注解,标记为实体领有草稿性能;在其子实体头上@DraftableElement注解,标记随主实体同步保护草稿状态。 @DraftableElement反对@OneToMany和@ManyToMany关系设置 2. @DraftOnly此注解示意仅在草稿中存在,正式版数据结构中没有此字段。可用于流程审批记录,草稿批改记录等场景 3. DraftDirty此注解能够放在一个布尔属性上,仅在草稿中存在。当草稿被保留时,其主动设置为true;当草稿被公布时,其主动设置为false。此注解可用于数据是否被公布 4. DraftReset此注解属性值在草稿公布之后,草稿中的值将主动设置为NULL,正式版本的值不变 二、草稿实现1. 新建@Testpublic void create() { List<Group> list = new ArrayList<>(); list.add(Group.builder().name("abc").build()); list.add(Group.builder().name("def").build()); Customer customer = Customer.builder() .name("abc") .author("hypier") .groupList(list) .whenPublish(new Timestamp(System.currentTimeMillis())) .build(); customer.save();}主实体和子实体同时保留时,将为两个数据表创立草稿数据库表 ...

June 24, 2021 · 1 min · jiezi

关于orm:对象识别之重写对象equals和hashCode方法

对数据库而言,其辨认一条记录唯一性的形式是依据主键值,如果手上有两条记录,它们领有同样的主键值,则它们在数据库中代表同一个字段的记录。   对Java而言,要辨认两个对象是否为同一个对象有两种形式      (1)内存地址辨认(“= =”号辨认);      (2)依据equals()、hashCode()中的定义 (默认Object类中定义的equals(Object o)办法也是按内存地址来比拟的)            源码如下:                 public boolean equals(Object obj){                               return  (this==obj);                       } 先探讨第一种Java的辨认形式在Hibernate中该留神的中央,在Hibernate中,如果是在同一个session中依据雷同查问所失去的雷同记录,则它们会领有雷同的Java辨认 //对象辨认 public void testObjectIndentifySession(){ Configuration cfg=null; SessionFactory sf=null; Session session=null; Transaction ts=null; try { sf=HibernateUtil.getSessionFactory(); //sessionFactory的单态模式 session=sf.getCurrentSession();//保障每个读写线程有惟一的session实例 ts=session.beginTransaction(); User u1=session.get(User.class, 2); User u2=session.get(User.class, 2); System.out.println(u1==u2); ts.commit(); } catch (HibernateException e) { // TODO Auto-generated catch block e.printStackTrace(); if(ts!=null){ ts.rollback(); } }finally{ //session.close(); //sf.close(); } }后果: ...

June 14, 2021 · 2 min · jiezi

关于orm:SpringBoot-DB-系列Jooq-初体验

【SpringBoot DB 系列】Jooq 初体验java 环境中,说到数据库的操作,咱们通常会想到的是 mybatis 或者 hibernate,明天给大家介绍一个国内可能用得不太多的操作形式 JOOQ,一款基于 Java 拜访关系型数据库的工具包,轻量,简略,并且足够灵便的 ORM 框架 本文将各位小伙伴演示一下 jooq 集成 springboot 的姿态 <!-- more --> I. 我的项目搭建咱们这里借助 h2dabase 来搭建演示我的项目,因而有趣味的小伙伴在文末能够间接获取我的项目地址启动即可体验,不须要额定的装置和配置 mysql 了 本文采纳SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发 1. pom 依赖上面给出外围的依赖配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jooq</artifactId></dependency><dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId></dependency>2. 配置接下来设置一下数据库相干的配置信息,在资源目录resources下,新建配置文件application.properties #Database Configurationspring.datasource.url=jdbc:h2:~/h2-jooq-dbspring.datasource.username=testspring.datasource.password=spring.datasource.driverClassName=org.h2.Driver3. 数据库初始化jooq 有一个特点,是须要咱们本人来生成表构造对象,所以咱们先初始化一下 h2dabase 的数据结构,详情能够参考博文 [【DB 系列 h2databse 集成示例 demo】]() 表构造定义文件schema-h2.sql, 请留神表构造与 mysql 的表创立姿态不太一样哦 DROP TABLE IF EXISTS poet;CREATE TABLE poet ( `id` int NOT NULL, `name` varchar(20) NOT NULL default '', CONSTRAINT pk_t_poet PRIMARY KEY (ID));数据初始化data-h2.sql ...

September 16, 2020 · 2 min · jiezi

关于orm:NET-CORE-数据库事务进化总结

事务是数据库系统中的重要概念,本文解说作者从业 CRUD 十余载的事务多种应用形式总结。 以下所有内容都是针对单机事务而言,不波及分布式事务相干的货色!对于事务原理的解说不针对具体的某个数据库实现,所以某些中央可能和你的实践经验不符。意识事务为什么须要数据库事务? 转账是生存中常见的操作,比方从A账户转账100元到B账号。站在用户角度而言,这是一个逻辑上的繁多操作,然而在数据库系统中,至多会分成两个步骤来实现: 1.将A账户的金额缩小100元 2.将B账户的金额减少100元。 在这个过程中可能会呈现以下问题: 1.转账操作的第一步执行胜利,A账户上的钱缩小了100元,然而第二步执行失败或者未执行便产生零碎解体,导致B账户并没有相应减少100元。 2.转账操作刚实现就产生零碎解体,零碎重启复原时失落了解体前的转账记录。 3.同时又另一个用户转账给B账户,因为同时对B账户进行操作,导致B账户金额出现异常。 为了便于解决这些问题,须要引入数据库事务的概念。 以上内容援用自:https://www.cnblogs.com/takum...意识 ADO.NETADO.NET是.NET框架中的重要组件,次要用于实现C#应用程序拜访数据库。 ADO.NET的组成: System.Data.Common → 各种数据拜访类的基类和接口System.Data.SqlClient → 对Sql Server进行操作的数据拜访类 a) SqlConnection → 数据库连接器b) SqlCommand → 数据库命名对象d) SqlDataReader → 数据读取器f) SqlParameter → 为存储过程定义参数g) SqlTransaction → 数据库事物 事务1:ADO.NET最原始的事务应用形式,毛病: 代码又臭又长逻辑难管制,一不小心就忘了提交或回滚,随即而来的是数据库锁得不到开释、或者连接池不够用跨办法传递 Tran 对象太麻烦举荐:★☆☆☆☆ SqlConnection conn = new SqlConnection(connString);SqlCommand cmd = new SqlCommand();cmd.Connection = conn;try{ conn.Open(); cmd.Transaction = conn.BeginTransaction();//开启事务 int result = 0; foreach (string sql in sqlList) { cmd.CommandText = sql; result += cmd.ExecuteNonQuery(); } cmd.Transaction.Commit();//提交事务 return result;}catch (Exception ex){ //写入日志... if (cmd.Transaction != null) cmd.Transaction.Rollback();//回滚事务 throw new Exception("调用事务更新办法时出现异常:" + ex.Message);}finally{ if (cmd.Transaction != null) cmd.Transaction = null;//革除事务 conn.Close();}事务2:SqlHelper原始 ADO.NET 事务代码又臭又长,是时候封装一个 SqlHelper 来操作 ADO.NET 了。比方: ...

August 24, 2020 · 3 min · jiezi

go语言orm之gorose全新版本20低调发布

号称go语言版本的laravel's eloquent, 发布了久违了2.0版本, 新版本做了很大的改进和升级, 下面我们一起来看一下新版本的特性. 架构调整gorose 2.0版本做了彻底的重构, 拥有全新的架构. 架构如图: gorose 2.0 采用模块化架构, 通过interface的api通信,严格的上层依赖下层.每一个模块都可以拆卸, 甚至可以自定义为自己喜欢的样子. 主模块 engin gorose 初始化配置模块, 可以全局保存并复用session 真正操作数据库底层模块, 所有的操作, 最终都会走到这里来获取或修改数据orm 对象关系映射模块, 所有的orm操作, 都在这里完成builder 构建终极执行的sql模块, 可以构建任何数据库的sql, 但要符合database/sql包的接口子模块 driver 数据库驱动模块, 被engin和builder依赖, 根据驱动来搞事情binder 结果集绑定模块, 所有的返回结果集都在这里其他语言入手姿势php: 使用过laravel的orm就可以快速上手使用python: 使用过orator orm的用户,可以快速上手ruby: 使用过rails的orm就可以快速上手支持驱动mysql : https://github.com/go-sql-dri... sqlite3 : https://github.com/mattn/go-s... postgres : https://github.com/lib/pq oracle : https://github.com/mattn/go-oci8 mssql : https://github.com/denisenkom... clickhouse : https://github.com/kshvakov/c... 特色连接池链式调用支持传入struct,map或字符串表名读写分离集群支持海量数据自动分块处理一键开启事务,自动回滚和提交模块化架构,自由扩展官方文档最新版2.x文档 api预览db.Table().Fields().Where().GroupBy().Having().OrderBy.Limit().Select()db.Table().Data().Insert()db.Table().Data().Where().Update()db.Table().Where().Delete()最佳实践sql DROP TABLE IF EXISTS "users";CREATE TABLE "users" ( "uid" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "age" integer NOT NULL);INSERT INTO "users" VALUES (1, 'gorose', 18);INSERT INTO "users" VALUES (2, 'goroom', 18);INSERT INTO "users" VALUES (3, 'fizzday', 18);实战代码 ...

June 26, 2019 · 2 min · jiezi

Gorm-源码分析二-简单query分析

简单使用上一篇文章我们已经知道了不使用orm如何调用mysql数据库,这篇文章我们要查看的是Gorm的源码,从最简单的一个查询语句作为切入点。当然Gorm的功能很多支持where条件支持外键group等等功能,这些功能大体的流程都是差不多先从简单的看起。下面先看如何使用 package mainimport ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/panlei/gorm")var db *gorm.DBfunc main() { InitMysql() var u User db.Where("Id = ?", 2).First(&u)}func InitMysql() { var err error db, err = gorm.Open("mysql", "root:***@******@tcp(**.***.***.***:****)/databasename?charset=utf8&loc=Asia%2FShanghai&parseTime=True") fmt.Println(err)}type User struct { Id int `gorm:"primary_key;column:Id" json:"id"` UserName string `json:"userName" gorm:"column:UserName"` Password string `json:"password" gorm:"column:Password"`}func (User) TableName() string { return "user"}首先注册对象 添加tag标注主键 设置数据库column名 数据库名可以和字段名不一样设置table名字,如果类名和数据库名一样则不需要设置初始化数据库连接 创建GormDB对象 使用Open方法返回DB使用最简单的where函数和First来获取 翻译过来的sql语句就是select * from user where Id = 2 limit 1源码分析1. DB、search、callback对象DB对象包含所有处理mysql的方法,主要的还是search和callbacks search对象存放了所有的查询条件Callback 对象存放了sql的调用链 存放了一系列的callback函数 ...

June 16, 2019 · 4 min · jiezi

我就是不看好jpa

知乎看到问题《SpringBoot开发使用Mybatis还是Spring Data JPA??》,顺手一答,讨论激烈。我实在搞不懂spring data jpa为啥选了hibernate作为它的实现,是“Gavin King”的裙带关系么?DAO层搞来搞去,从jdbc到hibernate,从toplink到jdo,到现在MyBatis胜出,是有原因的。 目前,一些狗屁培训公司,还有一些网络课程,包括一些洋课程,为了做项目简单,很多会使用jpa。但到了公司发现根本就不是这样,这就是理论和现实的区别。 顶着被骂的风险,我整理发出来。这可不是争论php好还是java好性质了。 忠告:精力有限,一定要先学MyBatis啊。jpa那一套,表面简单而已。以下是原始回答如果你经历过多个公司的产品迭代,尤其是复杂项目,你就会发现Spring Data JPA这种东西是多么的不讨好。说实话,Mybatis的功能有时候都嫌多,有些纯粹是画蛇添足。 jpa虽然是规范,但和hibernate这种ORM是长得比较像的(不说一些特殊场景的用法)。 Spring Data JPA 是个玩具,只适合一些简单的映射关系。也不得不提一下被人吹捧的querydsl,感觉有些自作聪明的讨巧。实在不想为了一个简单的DAO层,要学一些费力不讨好的东西。 List<Person> persons = queryFactory.selectFrom(person) .where(person.children.size().eq( JPAExpressions.select(parent.children.size().max()) .from(parent))) .fetch();看看上面的查询语句,完全不如普通SQL表达的清晰。要是紧急排查个问题,妈蛋... jpa虽然有很多好处,比如和底层的SQL无关。但我觉得Spring Data JPA有以下坏处: 1、 屏蔽了SQL的优雅,发明了一种自己的查询方式。这种查询方式并不能够覆盖所有的SQL场景。 2、 增加了代码的复杂度,需要花更多的时间来理解DAO 3、DAO操作变的特别的分散,分散到多个java文件中,或者注解中(虽然也支持XML)。如果进行一些扫描,或者优化,重构成本大 4、不支持复杂的SQL,DBA流程不好切入 Mybatis虽然也有一些问题,但你更像是在写确切的SQL。它比Spring Data JPA更加轻量级。 你只要在其他地方调试好了SQL,只需要写到配置文件里,起个名字就可以用了,少了很多烧脑的转换过程。 Mybatis完全能够应对工业级的复杂SQL,甚至存储过程(不推荐)。个人认为Mybatis依然是搞复杂了,它其中还加了一些类似if else的编程语言特性。 你的公司如果有DBA,是不允许你乱用SQL的。用Mybatis更能切入到公司的流程上。 所以我认为:玩具项目或者快速开发,使用Spring Boot JPA。反之,Mybatis是首选。 一些有用的评论你说的if else是指的SQL拼接吧,这可是MyBatis的重要功能之一,学起来一点儿也不复杂好吗? 最近也在研究持久层,可以充分利用这个jpa这个玩具,两者结合是不错的选择,jpa基本的单表操作,mybatis做复杂查询,开发效率高,降低sql维护成本,也为优化留下空间,当然这需要对spring-data-jpa做一些扩展 查询直接sql,其他的还是orm方便 mybatis主要是原生sql,对于其他没学习过jpa的开发人员而言降低了学习维护门槛,而且说真的jpa写了个锅你去追其实还是挺头疼的... mybatis-plus整合之后基本curd不用纠结了,很多对对象操作然后直接save就好。复杂场景、联表就直接用原生sql就好,至于性能问题可以用sqlAdvice优化下。 jdbc template+代码生成器,更简单更高效 jpa这玩意,写一个简单的数据库操作,就比如说单表的操作,很好用。如果是多表,那就算了吧 spring boot 推荐jpa,知道为什么吗 native=true 想用原生查询也没人拦着你啊 你好像不是很懂hibernate和jpa… 不知道你是否清楚 jpa,hibernate,spring data jpa,还有querydsl 之间的关系。 总有一天你会知道数据库优先和程序优先的区别 jpa还有一个好处,那就是帅锅啊 END来,越年轻越狂妄的家伙们,来喷我啊。 ...

June 3, 2019 · 1 min · jiezi

ORM 开发环境之利器:MVC 中间件 FreeSql.AdminLTE

前言这是一篇纯技术干货的分享文章,FreeSql 已经基本完成 .NETCore 最方便的 ORM 使命,我们正在筹备生态的建立,比如 ABP 中如何使用 FreeSql 的实现,需要各种各样的扩展包,好多好多工作量。有没有大神愿意无偿参与做这件事情,好吧。。应该没有人!!大约是在三天前,因为使用 FreeSql 的某项目需要做一个简单的后台功能,以便录入或管理数据。在实施的过程中好怀念当初 dotnetGen 生成器的味道,用它产生 curd 基本功能几乎是秒做;然后今天发表的 FreeSql.AdminLTE 主角,已经实现了相关功能,它是怎么干这个事情的,且看下面内容;功能介绍它是 FreeSql 衍生出来的 .NETCore MVC 中间件扩展包,基于 AdminLTE 前端框架动态产生实体的增删查改界面;输入:实体1、实体2、实体3输出:后台管理的功能只需要传入实体,就可以形成 curd 的管理功能,是不是有些骚啊~~~先发一张运行后的图片尝个鲜:这是根据实体产生 curd 界面的 mvc 中间件,开发时预览数据好方便啊。看完预览图不由得再感叹一次 FreeSql 的易用性,那句口号:做 .NETCore 最方便的 ORM! 没有说错。。。作者多次提及:“我们是日式简约风格,没那么复杂的用法”,也验证了这一点。。添加/修改中件间产生的界面包括添加、修改数据的功能,普通实体的根据属性的类型与 Html5 UI 一一映射;比较特殊的映射规则:c# 类型Html5布尔复选框枚举下拉选择日期日期控件ManyToOne 导航属性下拉选择ManyToMany 导航属性多选器等等。。。什么情况会产生【上传文件】控件?有兴趣的可以了解源码,目前没有开放在外部配置。查询/过滤中件间为每个实体提供了分页列表查询,每页为20条数据;除此外,还提供了过滤条件的支持,规则是根据导航属性(ManyToOne、ManyToMany)。比如【文章实体】,内含有【分类id】+【分类对象】,则【文章】列表页会出现按【分类】筛选的UI,详见上面的 demo 示意图,或者下载对应的 demo 版本运行;删除中件间为每个实体提供了批量删除的功能;测试 demo我们习惯用 sqlite 做测试库,测试完毕直接删除目录,不留垃圾数据,所以下面的 demo 不需要修改任何地方,运行时自动建库、建表;提供 .net core 2.1、2.2 两种环境的测试 demo 下载:Demo for dotnet 2.1.zip、Demo for dotnet 2.2.zip第一步:dotnet restore第二步:dotnet run思考一番惊喜过后,你应该会考虑实用性,这样做有什么价值,可用于什么样的场景?这个扩展包简单的输入,产生巨量的功能反馈。目前来说它是死板的,对外提供的扩展性几乎为零,这样也就限定了它的应用场景。不合适的场景1、它不可替代我们自身开发的后台管理系统;2、它不适合摆放在公网正式环境,存在数据安全问题;3、欢迎补充。。。;谈谈定位目前的定位是这样的,在开发环境中使用,查阅预览实体数据,同时也比较方便的管理测试数据。一段拥有无比力量的小段代码,也是中间件界面的功能开启://可以配置子目录访问,如:/testadmin/app.UseFreeAdminLTE("/", typeof(Entities.Song), typeof(Entities.Tag));其他本次测试的实体有 versionRow 字段(乐观锁),当不修改内容时,点按钮后不会执行SQL。如何判定?可以回到列表,看 versionRow 的值没变化,如果执行了SQL,它的值会增加。不执行 SQL 有啥单独可说的?这就牵连到 FreeSql.DbContext 了,是它过滤了执行操作,有兴趣可移步了解;乐观锁FreeSql (乐观锁)说明:更新整个实体数据时,在并发情况下极容易造成旧数据将新的记录更新。行级锁的原理,是利用实体某字段,如:long version,更新前先查询数据,此时 version 为 1,更新时产生的 SQL 会附加 where version = 1,当修改失败时(即 Affrows == 0)抛出异常。每个实体只支持一个行级锁属性,在属性前标记特性:[Column(IsVersion = true)] 即可。适用 SetSource 更新,每次更新 version 的值都会增加 1收官FreeSql.AdminLTE 目前已经定版了,差不多已经把 dotnetGen 支持的功能都迁移了过来,完成了它应有的职责定位。下一个扩展包也非常有意思,欢迎持续关注我们,做 .NETCore 最方便的 ORM !(QQ群:4336577)github:https://github.com/2881099/FreeSqlhttps://github.com/2881099/FreeSql.AdminLTE ...

April 18, 2019 · 1 min · jiezi

Sequelize学习经验(ORM框架)

什么是ORM?简单的讲就是对SQL查询语句的封装,让我们可以用OOP的方式操作数据库,优雅的生成安全、可维护的SQL代码。直观上,是一种Model和SQL的映射关系。 sequelize.query(原始查询) 默认情况下,函数将返回两个参数 - 一个结果数组,以及一个包含元数据(受影响的行等)的对象。 请注意,由于这是一个原始查询,所以元数据(属性名称等)是具体的方言。 某些方言返回元数据 “within” 结果对象(作为数组上的属性)。 但是,将永远返回两个参数,但对于MSSQL和MySQL,它将是对同一对象的两个引用。 const result1 = await mysql.query(‘SELECT id, img_url, url from carousel where status = 1’); //返回值是两个相同元素组成的数组 “result1”: [ [ { “id”: 1, “url”: “/ClassDetail?id=4” }, { “id”: 4, “url”: “/ClassDetail?id=2” } ], [ { “id”: 1, “url”: “/ClassDetail?id=4” }, { “id”: 4, “url”: “/ClassDetail?id=2” } ] ]解决办法有两种传递一个查询类型来告诉后续如何格式化结果 const result= await mysql.query(‘SELECT id, img_url, url from carousel where status = 1’, { replacements: {}, type: mysql.QueryTypes.SELECT });传递模型const Carousel = require(’../../models/Carousel’);const result2 = await mysql.query(‘SELECT id, img_url, url from carousel where status = 1’,{ model: Carousel });替换查询中的替换可以通过两种不同的方式完成:使用命名参数(以:开头),或者由?表示的未命名参数。 替换在options对象中传递。?如果传递一个数组, ? 将按照它们在数组中出现的顺序被替换const banner2 = await mysql.query(‘SELECT id, img_url, url from carousel where id = ?’, { replacements: [1,4,5,6], type: mysql.QueryTypes.SELECT,});// 返回的结果只有数组第一个元素下标对应的数据:const banner3 = await mysql.query(‘SELECT id, img_url, url from carousel where id in (:[1,4,5,6])’, { replacements: {[1,4,5,6]}, type: mysql.QueryTypes.SELECT,});// 返回结果为符合数组的数据// 拓展:模糊查询sequelize.query(‘SELECT * FROM users WHERE name LIKE :search_name ‘, { replacements: { search_name: ‘ben%’ }, type: sequelize.QueryTypes.SELECT }).then(projects => { console.log(projects)})参考文档:Sequelize 中文文档 v4 - Raw queries - 原始查询 ...

April 11, 2019 · 1 min · jiezi

odoo 模型与ORM

型号属性在/模型添加activity.py文件class ActivityEvent(models.Model): _name = ‘activity.event’ _inherit = ’event.event’ _rec_name = ’test_field’ # 字段 test_field = fields.Char(string=“字段名称”) # 多对多的关联表 employee_ids = fields.Many2many(‘files.employee’, ‘activity_event_files_employee_rel’, string=‘与会员工’) event_type_id = fields.Many2one( ‘activity.type’, string=‘Category’, readonly=False, states={‘done’: [(‘readonly’, True)]}, oldname=‘type’)model属性详解::类型_name唯一标识,类非继承父类时必须指定。_rec_name:数据显示名称,如设置则返回其指定的字段值,不设置默认显示字段为name的字段值,如无名字段则显示“模块名,ID”;详见BaseModel.name_get方法。_log_access:是否自动增加日志字段(create_uid,create_date,write_uid,write_date)默认为真。_auto:是否创建数据库对象默认为真,详见BaseModel._auto_init方法。_table:数据库对象名称缺。省时数据库对象名称与_name指定值相同(.替换为下划线)_sequence。:数据库id字段的序列。默认自动创建序列。_order:数据显示排序。所指定值为模型字段,按指定字段和方式排序结果集。例:_order =“create_date desc”:根据创建时间降序排列。可指定多个字段。不指定desc默认升序排列;不指定_order默认id升序排列。_constraints:自定义约束条件模型创建/编辑数据时触发,约束未通过弹出错误提示,拒绝创建/编辑。格式:: _constraints = [(method, ’error message’, [field1, …]), …]method检查方法。返回True | False error message:不符合检查条件时(方法返回False)弹出的错误信息[field1, …]:字段名列表,这些字段的值会出现在错误消息中。_sql_constraints:数据库约束。例:_sql_constraints = [ (’number_uniq’, ‘unique(number, code)’, ’error message’) ]会在数据库添加约束:CONSTRAINT number_uniq UNIQUE(number, code)_inherit。:单一继承值为所继承父类_name标识如子类不定义_name属性,则在父类中增加该子类下的字段或方法,不创建新对象;如子类定义_name属性,则创建新对象,新对象拥有父类所有的字段或方法,父类不受影响。格式:_inherit = ‘父类 _name’_inherits:多重继承子类通过关联字段与父类关联,子类不拥有父类的字段或方法,但是可以直接操作父类的字段或方法。格式:_inherits = {‘父类 _name’: ‘关联字段’}字段属性基础类型Char:字符型,使用尺寸参数定义字符串长度。Text:文本型,无长度限制。Boolean:布尔型(真,假)Interger:整型Float:浮点型,使用数字参数定义整数部分和小数部分位数如。digits=(10,6)Datetime:日期时间型Date:日期型Binary:二进制型selection:下拉框字段。 例:state = fields.Selection([(‘draft’, ‘Draft’),(‘confirm’, ‘Confirmed’),(‘cancel’, ‘Cancelled’)], string=‘Status’)Html:可设置字体格式,样式,可添加图片.关系类型1. One2many:一对多关系。定义:otm = fields.One2many(“关联对象 _name”, “关联字段”,string=“字段显示名”,…)例:analytic_line_ids = fields.One2many(‘account.analytic.line’, ‘move_id’, string=‘Analytic lines’)“2. Many2one定义:mto = fields.Many2one(“关联对象 _name”, string=“字段显示名”,…)可选参数:ondelete,可选值为’级联’和’空’,缺省为空表示一个端删除时多端是否级联删除。3. Many2many定义:mtm = fields.Many2many(“关联对象 _name”, “关联表/中间表”,“关联字段1”,“关联字段2”,string=“字段显示名”,…)其中,关联字段,关联表/中间表可不填,中间表缺省为:表1_表2_rel例:partner_id= fields.Many2many(“res.partner”, string=“字段显示名”,…)“复杂类型参数readonly: 是否只读,缺省值False。required: 必填填,缺省值Falsse。string: 字段显示名,任意字符串。default: 字段默认值domain: 域条件,缺省值[]。在关系型字段中,domain用于过滤关联表中数据。help: 字段描述,鼠标滑过时提示。store: 是否存储于数据库。结合计算和相关使用。例:sale_order = fields.One2many(“sale.order”, “contract_id”,string=“销售订单”, domain=[(‘state’,’=’,‘sale’)])compute:字段值由函数计算,该字段可不储存于数据库。例:amount = fields.Float(string=“金额总计”, compute=‘_compute_amount’,store=True)_compute_amount为计算函数。related:字段值引用关联表中某字段。以下代码表示:company_id引用hr.payroll.advice中company_idadvice_id = fields.Many2one(‘hr.payroll.advice’, string=‘Bank Advice’)company_id = fields.Many2one(‘res.company’, related=‘advice_id.company_id’, string=‘Company’, store=True) ...

March 13, 2019 · 1 min · jiezi

几个数据持久化框架Hibernate、JPA、Mybatis、JOOQ和JDBC Template的比较

因为项目需要选择数据持久化框架,看了一下主要几个流行的和不流行的框架,对于复杂业务系统,最终的结论是,JOOQ是总体上最好的,可惜不是完全免费,最终选择JDBC Template。Hibernate和Mybatis是使用最多的两个主流框架,而JOOQ、Ebean等小众框架则知道的人不多,但也有很多独特的优点;而JPA则是一组Java持久层Api的规范,Spring Data JPA是JPA Repository的实现,本来和Hibernate、Mybatis、JOOQ之类的框架不在同一个层次上,但引入Spring Data JPA之类框架之后,我们会直接使用JPA的API查询更新数据库,就像我们使用Mybatis一样,所以这里也把JPA和其他框架放在一起进行比较。同样,JDBC和其他框架也在同一层次,位于所有持久框架的底层,但我们有时候也会直接在项目中使用JDBC,而Spring JDBC Template部分消除了使用JDBC的繁琐细节,降低了使用成本,使得我们更加愿意在项目中直接使用JDBC。一、SQL封装和性能在使用Hibernate的时候,我们查询的是POJO实体类,而不再是数据库的表,例如hql语句 select count(*) from User,里面的User是一个Java类,而不是数据库表User。这符合ORM最初的理想,ORM认为Java程序员使用OO的思维方式,和关系数据库的思维方式差距巨大,为了填补对象和关系思维方式的鸿沟,必须做一个对象到关系的映射,然后在Java的对象世界中,程序员可以使用纯的对象的思维方式,查询POJO对象,查询条件是对象属性,不再需要有任何表、字段等关系的概念,这样java程序员就更容易做持久层的操作。JPA可以视为Hibernate的儿子,也继承了这个思路,把SQL彻底封装起来,让Java程序员看不到关系的概念,用纯的面向对象思想,重新创造一个新的查询语言代替sql,比如hql,还有JPQL等。支持JPA的框架,例如Ebean都属于这种类型的框架。但封装SQL,使用另一种纯的面向对象查询语言代替sql,真的能够让程序员更容易实现持久层操作吗?MyBatis的流行证明了事实并非如此,至少在大多数情况下,使用hql并不比使用sql简单。首先,从很多角度上看,hql/JPQL等语言更加复杂和难以理解;其次就是性能上明显降低,速度更慢,内存占用巨大,而且还不好优化。最为恼火的是,当关系的概念被替换为对象的概念之后,查询语言的灵活性变得很差,表达能力也比sql弱很多。写查询语句的时候受到各种各样的限制,一个典型的例子就是多表关联查询。不管是hibernate还是jpa,表之间的连接查询,被映射为实体类之间的关联关系,这样,如果两个实体类之间没有(实现)关联关系,你就不能把两个实体(或者表)join起来查询。这是很恼火的事情,因为我们很多时候并不需要显式定义两个实体类之间的关联关系就可以实现业务逻辑,如果使用hql,只是为了join我们就必须在两个实体类之间添加代码,而且还不能逆向工程,如果表里面没有定义外键约束的话,逆向工程会把我们添加的关联代码抹掉。MyBatis则是另外一种类型的持久化框架,它没有封装SQL也没有创建一种新的面相对象的查询语言,而是直接使用SQL作为查询语言,只是把结果填入POJO对象而已。使用sql并不比hql和JPQL困难,查询速度快,可以灵活使用任意复杂的查询只要数据库支持。从SQL封装角度上看,MyBatis比Hibernate和JPA成功,SQL本不该被封装和隐藏,让Java程序员使用SQL既不麻烦也更容易学习和上手,这应该是MyBatis流行起来的重要原因。轻量级持久层框架JOOQ也和MyBatis一样,直接使用SQL作为查询语言,比起MyBatis,JOOQ虽然知名度要低得多,但JOOQ不但和MyBatis一样可以利用SQL的灵活性和高效率,通过逆向工程,JOOQ还可以用Java代码来编写SQL语句,利用IDE的代码自动补全功能,自动提示表名和字段名,减少程序员记忆负担,还可以在元数据发生变化时发生编译错误,提示程序员修改相应的SQL语句。Ebean作为一种基于JPA的框架,它也使用JPQL语言进行查询,多数情况下会让人很恼火。但据说Ebean不排斥SQL,可以直接用SQL查询,也可以用类似JOOQ的DSL方式在代码中构造SQL语句(还是JPQL语句?),但没用过Ebean,所以具体细节不清楚。JDBC Template就不用说了,它根本没做ORM,当然是纯SQL查询。利用Spring框架,可以把JDBC Template和JPA结合起来使用,在JPA不好查询的地方,或者效率低不好优化的地方使用JDBC,缓解了Hibernate/JPA封装SQL造成的麻烦,但我仍没看到任何封装SQL的必要性,除了给程序员带来一大堆麻烦和学习负担之外,没有太明显的好处。二、DSL和变化适应性为了实现复杂的业务逻辑,不论是用SQL还是hql或者JPQL,我们都不得不写很多简单的或者复杂的查询语句,ORM无法减少这部分工作,最多是用另一种面向对象风格的语言去表达查询需求,如前所述,用面向对象风格的语言不见得比SQL更容易。通常业务系统中会有很多表,每个表都有很多字段,即便是编写最简单的查询语句也不是一件容易的事情,需要记住数据库中有哪些表,有哪些字段,记住有哪些函数等。写查询语句很多时候成为一件头疼的事情。QueryDSL、JOOQ、Ebean甚至MyBatis和JPA都设计一些特性,帮助开发人员编写查询语句,有人称之为“DSL风格数据库编程”。最早实现这类功能的可能是QueryDSL,把数据库的表结构逆向工程为java的类,然后可以让java程序员能够用java的语法构造出一个复杂的查询语句,利用IDE的代码自动补全功能,可以自动提示表名、字段名、查询语句的关键字等,很成功的简化了查询语句的编写,免除了程序员记忆各种名字、函数和关键字的负担。QueryDSL有很多版本,但用得多的是QueryDSL JPA,可以帮助开发人员编写JPQL语句,如前所述,JPQL语句有很多局限不如SQL灵活高效。后来的JOOQ和Ebean,基本上继承了QueryDSL的思路,Ebean基本上还是JPA风格的ORM框架,虽然也支持SQL,但不清楚其DSL特性是否支持SQL语句编写,在官网上看到的例子都是用于构造JPQL语句。这里面最成功的应该是JOOQ,和QueryDSL不同,JOOQ的DSL编程是帮助开发人员编写SQL语句,抛弃累赘的ORM概念,JOOQ这个功能非常轻小,非常容易学习和使用,同时性能也非常好,不像QueryDSL和Ebean,需要了解复杂的JPA概念和各种奇异的限制,JOOQ编写的就是普通的SQL语句,只是把查询结果填充到实体类中(严格说JOOQ没有实体类,只是自动生成的Record对象),JOOQ甚至不一定要把结果转换为实体类,可以让开发人员按照字段取得结果的值,相对于JDBC,JOOQ会把结果值转换为合适的Java类型,用起来比JDBC更简单。传统主流的框架对DSL风格支持得很少,Hibernate里面基本上没有看到有这方面的特性。MyBatis提供了"SQL语句构建器"来帮助开发人员构造SQL语句,但和QueryDSL/JOOQ/Ebean差很多,不能提示表名和字段名,语法也显得累赘不像SQL。JPA给人的印象是复杂难懂,它的MetaModel Api继承了特点,MetaModel API+Criteria API,再配合Hibernate JPA 2 Metamodel Generator,让人有点QueryDSL JPA的感觉,只是绕了一个大大的弯,叠加了好几层技术,最后勉强实现了QueryDSL JPA的简单易懂的功能。很多人不推荐JPA+QueryDSL的用法,而是推荐JPA MetaModel API+Criteria API+Hibernate JPA 2 Metamodel Generator的用法,让人很难理解,也许是因为这个方案是纯的标准的JPA方案。数据库DSL编程的另一个主要卖点是变化适应性强,数据库表结构在开发过程中通常会频繁发生变化,传统的非DSL编程,字段名只是一个字符串,如果字段名或者类型改变之后,查询语句没有相应修改,编译不会出错,也容易被开发人员忽略,是bug的一个主要来源。DSL编程里面,字段被逆向工程为一个java类的属性,数据库结构改变之后,作为java代码一部分的查询语句会发生编译错误,提示开发人员进行修改,可以减少大量bug,减轻测试的负担,提高软件的可靠性和质量。三、跨数据库移植Hibernate和JPA使用hql和JPQL这类数据库无关的中间语言描述查询,可以在不同数据库中无缝移植,移植到一个SQL有巨大差别的数据库通常不需要修改代码或者只需要修改很少的代码。Ebean如果不使用原生SQL,而是使用JPA的方式开发,也能在不同数据库中平滑的移植。MyBatis和JOOQ直接使用SQL,跨数据库移植时都难免要修改SQL语句。这方面MyBatis比较差,只有一个动态SQL提供的特性,对于不同的数据库编写不同的sql语句。JOOQ虽然无法像Hibernate和JPA那样无缝移植,但比MyBatis好很多。JOOQ的DSL很大一部分是通用的,例如分页查询中,Mysql的limit/offset关键字是很方便的描述方式,但Oracle和SQLServer的SQL不支持,如果我们用JOOQ的DSL的limit和offset方法构造SQL语句,不修改移植到不支持limit/offset的Oracle和SQLServer上,我们会发现这些语句还能正常使用,因为JOOQ会把limit/offset转换成等价的目标数据库的SQL语句。JOOQ根据目标数据库转换SQL语句的特性,使得在不同数据库之间移植的时候,只需要修改很少的代码,明显优于MyBatis。JDBC Template应该最差,只能尽量使用标准sql语句来减少移植工作量。四、安全性一般来说,拼接查询语句都会有安全隐患,容易被sql注入攻击。不论是jdbc,还是hql/JPQL,只要使用拼接的查询语句都是不安全的。对于JDBC来说,使用参数化的sql语句代替拼接,可以解决问题。而JPA则应该使用Criteria API解决这个问题。对于JOOQ之类的DSL风格框架,最终会被render为参数化的sql,天生免疫sql注入攻击。Ebean也支持DSL方式编程,也同样免疫sql注入攻击。这是因为DSL风格编程参数化查询比拼接字符串查询更简单,没人会拼接字符串。而jdbc/hql/JPQL拼接字符串有时候比参数化查询更简单,特别是jdbc,很多人会偷懒使用不安全的方式。五、JOOQ的失败之处可能大部分人会不同意,虽然Hibernate、JPA仍然大行其道,是最主流的持久化框架,但其实这种封装SQL的纯正ORM已经过时,效益低于使用它们的代价,应该淘汰了。MyBatis虽然有很多优点,但它的优点JOOQ基本上都有,而且多数还更好。MyBatis最大的缺点是难以避免写xml文件,xml文件编写困难,容易出错,还不容易查找错误。相对于JOOQ,MyBatis在多数情况下没有任何优势。Ebean同时具有很多不同框架的优点,但它是基于JPA的,难免有JPA的各种限制,这是致命的缺点。JOOQ这个极端轻量级的框架技术上是最完美的,突然有一天几个Web系统同时崩了,最后发现是JOOQ试用期过期了,这是JOOQ的失败之处,它不是完全免费的,只是对MySql之类的开源数据库免费。最终,我决定选择JDBC Template。

March 12, 2019 · 1 min · jiezi

.NETCore 下支持分表分库、读写分离的通用 Repository

首先声明这篇文章不是标题党,我说的这个类库是 FreeSql.Repository,它作为扩展库现实了通用仓储层功能,接口规范参数 abp vnext,定义和实现基础的仓储层(CURD)。安装dotnet add package FreeSql.Repository可用于:.net framework 4.6+、.net core 2.1+定义var fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10”) .UseLogger(loggerFactory.CreateLogger<IFreeSql>()) .UseAutoSyncStructure(true) //自动迁移实体的结构到数据库 .Build();过滤与验证假设我们有User(用户)、Topic(主题)两个实体,在某领域类中定义了两个仓储:var userRepository = fsql.GetGuidRepository<User>();var topicRepository = fsql.GetGuidRepository<Topic>();开发过程中,我总会担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1);var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据;在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;有朋友说这个功能像 abp 的租户,但这是更小单位的过滤+验证,确保数据安全。有朋友说这个功能省事,但我觉得是省心。分表与分库GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装类。var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString(“YYYYMM”)}");上面我们得到一个日志仓储实例按年月分表,使用它 CURD 最终会操作 Log_201903 表。注意:虽然 FreeSql 支持 CodeFirst 迁移,但不提供迁移分表,开发环境中仍然可以迁移 Log 表。读写分离FreeSql 支持数据库读写分离,本功能是客户端的读写分离行为,数据库服务器该怎么配置仍然那样配置,不受本功能影响,为了方便描术后面讲到的【读写分离】都是指客户端的功能支持。各种数据库的读写方案不一,数据库端开启读写分离功能后,读写分离的实现大致分为以下几种:1、nginx代理,配置繁琐且容易出错;2、中件间,如MySql可以使用MyCat,但是其他数据库怎么办?3、在client端支持;FreeSql 实现了第3种方案,支持一个【主库】多个【从库】,【从库】的查询策略为随机方式。若某【从库】发生故障,将切换到其他可用【从库】,若已全部不可用则使用【主库】查询。出现故障【从库】被隔离起来间隔性的检查可用状态,以待恢复。以 mysql 为例:var connstr = “Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;” + “Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10”;IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.MySql, connstr) .UseSlave(“connectionString1”, “connectionString2”) //使用从数据库,支持多个 .Build();select.Where(a => a.Id == 1).ToOne(); //读【从库】(默认)select.Master().WhereId(a => a.Id == 1).ToOne(); //强制读【主库】其他特性[x] 支持 CodeFirst 迁移;[x] 支持 DbFirst 从数据库导入实体类,支持三种模板生成器;[x] 采用 ExpressionTree 高性能读取数据;[x] 支持深入的类型映射,比如pgsql的数组类型;[x] 支持丰富的表达式函数;[x] 支持导航属性查询,和延时加载;[x] 支持同步/异步数据库操作方法,丰富多彩的链式查询方法;[x] 支持读写分离、分表分库;[x] 支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite;结束语这个点我还没吃晚饭,对今天更新的 v0.1.11 作两个小时的测试。觉得好请献上宝贵一星,谢谢支持!github: https://github.com/2881099/FreeSql ...

March 6, 2019 · 1 min · jiezi

FreeSql 如何实现 Sqlite 跨库查询

FreeSql 是 .NetFramework 4.6+、.NetCore 下的 ORM 功能库,提供了丰富的功能,支持五种流行数据库 MySql/SqlServer/PostgreSQL/Oracle/Sqlite。正常的数据库都支持跨库,然而 Sqlite 默认不支持,或者说支持起来较为麻烦,FreeSql 最关心的是通用、易用性,本文介绍 FreeSql 如何实现 Sqlite 跨库操作。故事发生在 CodeFirst 自由开发FreeSql 支持并推荐使用 CodeFirst 方式开发项目,这种开发方式非常自由,如同 FreeSql 的命名一般。如下定义两个实体(文章、评论):class Topic { public Guid Id { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime CreateTime { get; set; }}[Table(Name = “xxxtb.Comment”)]class Comment { public Guid Id { get; set; } public Guid TopicId { get; set; } public Topic Topic { get; set; } public string Nickname { get; set; } public string Content { get; set; } public DateTime CreateTime { get; set; }}我们希望将 Topic 的数据存到主库,Comment 的数据存到别外一个库(xxxtb)。比如使用 mysql,不指定数据库情况下,将操作当前数据库下的表。其他 ado.net 或 ORM 将面临的问题(默认情况下):1、驱动只打开了一个库,或者需要手工调用驱动的方法对其他库进行附加;2、项目中可能需要定义多个 orm,实现对多个数据库的存储和查询;3、无法使用跨库联表查询;解决用户使用问题使用习惯是 FreeSql 主要攻克的难题,其他数据库都行,【吐槽】就你丫的 Sqlite 奇葩,同连接下默认做不到跨库操作(或者说使用不方便)。好在如今的 .NET 库大多数都已经开源,于是翻阅 System.Data.SQLite.Core 源码做了总结:SQLiteConnection 连接对象在 Open 后,执行如下命令可附加多个数据库;attach database [xxxtb.db] as [xxxtb];于是,第一步:实现一个扩展方法,使用 OpenAndAttach 代替 Open:public static void OpenAndAttach(this DbConnection that, string[] attach) { that.Open(); if (attach?.Any() == true) { var sb = new StringBuilder(); foreach(var att in attach) sb.Append($“attach database [{att}] as [{att.Split(’.’).First()}];\r\n”); var cmd = that.CreateCommand(); cmd.CommandText = sb.ToString(); cmd.ExecuteNonQuery(); }}//异步方法的实现省略…第二步:增加 ConnectionString 参数 AttachsData Source=|DataDirectory|\document.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10第三步:SqliteConnectionPool 实施1、解析 Attachs;var att = Regex.Split(_connectionString, @“Attachs\s*=\s*”, RegexOptions.IgnoreCase);if (att.Length == 2) { //此条件说明存在,找到 Attachs 配置的值,并且它支持以逗号分割 var idx = att[1].IndexOf(’;’); Attaches = (idx == -1 ? att[1] : att[1].Substring(0, idx)).Split(’,’);}2、将原有 Open 方法替换成 OpenAndAttach;大功告成编码与实施过程完成,接下来测试结果,仍然使用上方给出的两个实体类型。static IFreeSql sqlite = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|\document.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10”) .UseAutoSyncStructure(true) .UseLazyLoading(true) .Build();//秀一波 FreeSql.Repository 扩展包//安装方法:dotnet add package FreeSql.Repositoryvar topicRepository = sqlite.GetGuidRepository<Topic>();var commentRepository = sqlite.GetGuidRepository<Comment>();//添加测试文章Guid topicId = FreeUtil.NewMongodbId();topicRepository.Insert(new Topic { Id = FreeUtil.NewMongodbId(), Title = “文章标题1”, Content = “文章内容1”, CreateTime = DateTime.Now});//添加10条测试评论var comments = Enumerable.Range(0, 10).Select(a => new Comment { Id = FreeUtil.NewMongodbId(), TopicId = topicId, Nickname = $“昵称{a}”, Content = $“评论内容{a}”, CreateTime = DateTime.Now});var affrows = commentRepository.Insert(comments);var find = commentRepository.Select.Where(a => a.Topic.Title == “文章标题1”).ToList();//SELECT a.“Id”, a.“TopicId”, a.“Nickname”, a.“Content”, a.“CreateTime” //FROM “xxxtb”.“Comment” a, “Topic” a__Topic //WHERE (a__Topic.“Title” = ‘文章标题1’)//find 查询出了10条数据然后我们使用 navicat 附加两个数据库查看:其他说明1、FreeUtil.NewMongodbId 是生成有序的 Guid 值,在 FreeSql 中实现,其实可以使用 Guid.NewGuid,这里我承认有秀的嫌疑;2、文章中的代码没有过多的依赖,在 vs2017+.netcore2.2 测试一次运行通过,sqlite 的优势免安装服务;3、FreeSql 支持 CodeFirst,即建好实体运行程序,表就会创建;4、FreeSql.Repository 是扩展包,实现通用 CURD 仓储层功能,文档:https://github.com/2881099/FreeSql/wiki/Repository这个功能其实在 FreeSql 早期就已经实现了,但是一直没能有时间将过程经验整理成文章,今天把文章写完写全,希望能给大家带来一点“惊吓”。FreeSql还有更多细节优化并不是路人一眼能看透,如果觉得写得不错麻烦点个赞,这也是我继续写下去的源动力。一遍看不明白的话,建议多看几遍。谢谢观看! ...

March 6, 2019 · 2 min · jiezi

ThinkGo:一个轻量级的 Go 语言 MVC 框架

ThinkGo 是一个轻量级的 Go 语言 MVC 框架,目前支持路由、中间件、控制器、请求、响应、Session、视图、日志、缓存、ORM等 web 框架应该具备的基本功能,ThinkGo致力于让代码简洁且富于表达力,帮助开发者快速构建一个 Web 应用。特性简洁的路由,支持参数注入强大的路由中间件,支持前置/后置中间件Session支持,支持cookie、redis及自定义存储强大的日志服务,支持多通道存储,遵循RFC 5424规范。缓存,支持memory、redis及自定义缓存驱动简洁的ORM,能使用原生 SQL、流畅的查询构造器安装go get github.com/thinkoner/thinkgo快速开始package mainimport ( “github.com/thinkoner/thinkgo” “fmt” “github.com/thinkoner/thinkgo/router” “github.com/thinkoner/thinkgo/context”)func main() { app := thinkgo.BootStrap() app.RegisterRoute(func(route *router.Route) { route.Get("/", func(req *context.Request) *context.Response { return thinkgo.Text(“Hello ThinkGo !”) }) route.Get("/ping", func(req *context.Request) *context.Response { return thinkgo.Json(map[string]string{ “message”: “pong”, }) }) // Dependency injection route.Get("/user/{name}", func(req *context.Request, name string) *context.Response { return thinkgo.Text(fmt.Sprintf(“Hello %s !”, name)) }) }) // listen and serve on 0.0.0.0:9011 app.Run()}协议ThinkGo 采用 Apache 2.0 开源协议发布。项目地址GitHub: https://github.com/thinkoner/...Gitee: https://gitee.com/thinkgo/thi…联系作者https://github.com/thinkoner/… ...

February 12, 2019 · 1 min · jiezi

ORM: SQLAlchemy 初识

ORM: Object Relational Mapper.目前Python有很多ORM工具可以将数据库映像为Python的Objects对象。其中比较知名的有Django的ORM,SQLAlchemy, PostgreSQL等。SQLAlchemy有更多的人维护,功能也比较齐全。所以一般是我们的首选项。对于SQLAlchemy的使用者来说,只要你一开始连接上数据库,不管是Sqlite,MySQL还是什么,后面的处理方式完全一样。这种便利性也是它受欢迎的原因。抛弃了传统的自己编织SQL语句、制作模型、连接数据库方式,SQLAlchemy直接把这些东西全包在黑盒里面,让我们完全不需要去管。连SQL-Injection注入这种东西也被它帮忙防范了。这样一来,可以说在连接数据库方面,帮我们节省了最少一半以上的代码。甚至连数据查询,SQLAlchemy也代替了SQL语句,而使用了专门的类似MongoDB的Object.query.filter_by(name=‘Jason’).all()这种方法。安装:# 安装sqlalchemy$ pip install sqlalchemy安装Drivers:# Sqlite# 不需要,Python自带# MySQL$ pip install pymysql# Postgresql$ pip install psycopg2SQLAlchemy自身不带数据库driver,需要我们自己安装,并在连接时候指定。而这些driver,实际上就是我们曾经手动连接数据库所用的包。而SQLAlchemy只是代替我们使用这些同样的包。连接数据库创建一个sqlite的ORM引擎:from sqlalchemy import create_engine# 连接格式为:sqlite://<Hostname>/<path>engine = create_engine(‘sqlite:///foo.db’, echo=True)创建一个MySQL的ORM引擎:from sqlalchemy import create_engine# 连接格式为:dialect+driver://username:password@host:port/databaseengine = create_engine(‘mysql+pymysql://root:password123@localhost/db_test_01’, echo=True)数据库的位置(三斜杠为相对路径,四斜杠为绝对路径):# 使用绝对路径的数据库文件(////),如/tmp/mydatabase.dbengine = create_engine(‘sqlite:////tmp/mydatabase.db’)# 使用当前「执行位置」数据库文件(///或///./)engine = create_engine(‘sqlite:///mydatabase.db’)# 使用当前「执行位置」父级目录(///../)的数据库文件engine = create_engine(‘sqlite:///../mydatabase.db’)# 使用当前「脚本位置」的数据库文件import oscwd = os.path.split(os.path.realpath(file))[0]engine = create_engine(‘sqlite:///{}/mydatabase.db’.format(cwd))Create Tables 创建表注意:不同于SQL语句,SQLAlchemy中的表名是完全区分大小写的!创建一个Schema表(指单纯表,不包含ORM对象):from sqlalchemy import create_engine, MetaDatafrom sqlalchemy import Table, Columnfrom sqlalchemy import Integer, String, ForeignKeyengine = create_engine(‘mysql+pymysql://root:password123@localhost/db_test_01’, echo=True)metadata = MetaData(engine)# 创建一个表user_table = Table( ’tb_user’, metadata, Column(‘id’, Integer, primary_key=True), Column(’name’, String(50)), Column(‘fullname’, String(100)))# 让改动生效metadata.create_all()创建一个ORM对象(包括表):# 导入表格创建引擎from sqlalchemy import create_engine# 导入列格式from sqlalchemy import Column, Integer, String, ForeignKey# 导入创建ORM模型相关from sqlalchemy.ext.declarative import declarative_baseBase = declarative_base()class User(Base): tablename = ’tb_Person’ id = Column(‘id’, Integer, primary_key=True) username = Column(‘username’, String, unique=True)engine = create_engine(‘sqlite:///test.sqlite’, echo=True)Base.metadata.create_all(bind=engine)用普通表Table和ORM对象创建的表有什么不同?他们在数据库中创建的,是完全相同的表!唯一区别是,Table创建的不包含ORM对象,也就是不提供让你直接操作Python对象的功能。这么做的好处是,有很多只是关联作用的表,没有必要生成ORM对象。删除数据库中的表# engine = …# Base = …# 逐个ORM对象删除对应的表,如User类User.table.drop(engine)# 删除全部表Base.metadata.drop_all(engine)设计或调试过程中,我们经常要频繁改动表格,所以有必要在创建表格前把测试数据库中的表都清除掉,再创建新的定义。Insertion 插入数据将数据添加到数据库:# …# 导入session相关(用于添加数据)from sqlalchemy.orm import sessionmaker, relationshipuser = User()user.id = 1user.username = ‘Jason’Session = sessionmaker(bind=engine)session = Session()session.add(user)session.commit()session.close()注意:这里的session和网站上的session概念有点不一样。这里是用来commit提交数据库变动的工具。批量添加数据(向add_all()传入列表):session.add_all( [user1, user2, user3] )添加每条数据的时候自动flush():session = sessionmaker(bind=engine, autoflush=True)autoflush是在每次session.add()自动执行session.flush(),即在插入数据库之前就在内存中生成所有对象的动态数据(如主键ID等)。一般默认是选false,因为会影响效率。最好是需要的时候,才手动执行session.flush()具体缘由,看下一节“数据生效”。Take effect 数据生效SQLAlchemy中的create_all()和session.commit()都是直接让python文件中定义的对象在数据库中生效的语句。在此之前,无论怎么定义,数据都是在内存中,而没有在数据库中的。注意区分:create_all只是让创建表格结构生效,无关insert的数据条目session.commit()只是让添加的数据生效,而不负责任何表格结构。这两个的顺序,当然是先创建表格,再插入数据。只是,如果我们知道了这个原理,在编码中才能比较运用自由。比如,连create_engine()创建引擎,我们都可以在后面定义,而没必要非得写在文件头,即所有的ORM定义之前。create_engine只要定义在所有ORM类和Schema表之后即可。此后,我们再开始进行数据插入工作,也就利用到了session。session过程中呢,我们也会遇到互相引用主键外键ID的情况。但是注意,这时候因为还没有使用最终的session.commit()真正提交数据到数据库中,这些ID是没有值的。解决办法就是利用内置的方法session.flush(),将session中已添加的所有对象填充好数据,但是这时候还没有提交到数据库,只是我们内部可以正常访问各种ID了。更新/删除数据更新:# Get a row of datame = session.query(User).filter_by(username=‘Jason’).first()# Method 1:me.age += 1session.commit()# Method 2:session.query().filter( User.username == ‘Jason’).update( {“age”: (User.age +1)})session.commit()# Method 3:setattr(user, ‘age’, user.age+1)session.commit()Get Primary Key Value 获取主键值#sqlalchemy can’t get primary key , #sqlalchemy 如何获得主键的值这个问题花了我很多时间探索查询,不得其解,才明白原来是很显然的事。参考思否:SQLAlchemy中返回新插入数据的id?虽然在没有用session或engine插入数据之前,我们可以直接浏览从ORM创建的对象中的属性值。但是这个时候无论如何都获取不到primar_key主键列的值。因为这时候主键还没有插入数据库,作为动态的值,在数据库没生效之前也就为None。为什么需要获取value of primary_key?考虑如下这些场景:子表中的foreign key外键需要引用主表的id??那么该怎么获取主键ID呢?再参考Stackoverflow:sqlalchemy flush() and get inserted id?再参考:sqlalchemy获取插入的id再参考:Sqlalchemy;将主键设置为预先存在的数据库表(不使用sqlite)如果要想在插入数据之前就获取主键等动态列的值,那么有这几种方法:直接利用SQLAlchemy建立类直接的内部关联,而不直接使用ID主表插入数据,另session生效后,再用query获取相应对象,来得到它的ID。(*) 主表先用session.add(..),再session.flush(),然后就可以获取ID,最后再session.commit()不使用primary key主键,自己手动创建ID,这样来随便获取。推荐做法如下:即每次新创建对象后,立刻session.add(..),然后立刻session.flush(),全部都添加好的文末,再session.commit().Query 查询注意:query是通过session进行的,也就是必须在session.commit()之后才能进行查询,否则会报错。这里将的query查询,指的都是在插入到数据库生效之后。理解这个很重要,因为在对象未插入到数据库之前,很多主键、外键等内容都是不存在的,也就无法查询到。参考:pythonsheets - Object Relational basic query查询数据:session.commit()# …users = session.query(User).all()# 返回的是多个User类的对象:>>> [ <User 1>, <User 2>, …. ]for u in users: print(u.id, u.username)常用查询方法:# 获取某ORM中数据 .query(ORM类名)>>> session.query( User ).all() # All rows of data>>> session.query( User ).first() # First row of data as an object# 查询结果排序 .order_by(类名.列名)>>> session.query(User).order_by( User.birth ).all()# 筛选结果 .filter( True/False 表达式 )>>> session.query(User).filter( User.name != ‘Jason’ ).all()>>> session.query(User).filter( User.name.like(’%ed%’) ).all() # Fuzzy search>>> session.query(User).filter( User.id in [1, 2, 3] ).all() # IN>>> session.query(User).filter( ~ User.id in [4, 5, 6] ).all() # NOT IN>>> session.query(User).filter( User.school == ‘MIT’, User.age < 24 ).first() # AND>>> session.query(User).filter( _or(User.school == ‘MIT’, User.age < 24) ).first() # OR ...

January 24, 2019 · 2 min · jiezi

beego orm 多对多查询

首先纠正一下beego的文档rel_table设置自动生成的 m2m 关系表的名称rel_through如果要在 m2m 关系中使用自定义的 m2m 关系表通过这个设置其名称,格式为 pkg.path.ModelNameeg: app.models.PostTagRelPostTagRel 表需要有到 Post 和 Tag 的关系实际上rel_through的格式并不是pkg.path.ModelName,正确的姿势是:// 不要照抄哈,这里只是eg应该怎么使用learnBeego/myApp/models.RoleUser在这里卡了一天,后来还是在google的帮助下找到了问题,bd能搜索出关于go的东西实在太少了举个例子需求:用户、角色。我们要实现 一个用户可以有多个角色,一个角色可以有多个用户数据表设计用户表 useridusernamepasswrodcreated_atupdated_at1testa1234562018-01-01 12:36:472018-01-01 12:36:472testb6543212018-01-01 12:36:472018-01-01 12:36:47角色表 roleidnamecreated_atupdated_at1测试角色A2018-01-01 12:36:472018-01-01 12:36:472测试角色B2018-01-01 12:36:472018-01-01 12:36:47角色用户关系表 role_useriduser_idrole_idcreated_atupdated_at1112018-01-01 12:36:472018-01-01 12:36:472122018-01-01 12:36:472018-01-01 12:36:473212018-01-01 12:36:472018-01-01 12:36:47模型用户模型注意! 使用多对多时,想要获取关系字段是需要手动完成的,orm不会为你自动完成这些查询操作,不要以为设置完rel_through就完事了!type User struct { Id int64 Username string orm:"size(128);unique" valid:"Required" password string orm:"size(128);" json:"-" valid:"Required" CreatedAt time.Time orm:"auto_now_add;type(datetime)" UpdatedAt time.Time orm:"auto_now;type(datetime)" Roles []*Role orm:"rel(m2m);rel_through(learnBeego/myApp/models.RoleUser)"}func init() { orm.RegisterModel(new(User))}func (m *User) TableName() string { return “user”}// 通过用户ID获取用户信息及用户所属的角色func GetUserById(id int64) (v *User, err error) { o := orm.NewOrm() v = &User{Id: id} if err = o.QueryTable(new(User)).Filter(“Id”,id).RelatedSel().One(v); err == nil { // 获取关系字段,o.LoadRelated(v, “Roles”) 这是关键 // 查找该用户所属的角色 if _, err = o.LoadRelated(v, “Roles”);err!=nil{ return nil, err } return v, nil } return nil, err}角色模型type Role struct { Id int64 Name string CreatedAt time.Time orm:"auto_now_add;type(datetime)" UpdatedAt time.Time orm:"auto_now;type(datetime)" Users []*User orm:"reverse(many)"}func init() { orm.RegisterModel(new(Role))}func (m *Role) TableName() string { return “role”}用户角色关系模型type RoleUser struct { Id int64 User *User orm:"rel(fk)" Role *Role orm:"rel(fk)" CreatedAt time.Time orm:"type(datetime)" UpdatedAt time.Time orm:"type(datetime)"}func init() { orm.RegisterModel(new(RoleUser))}func (m *RoleUser) TableName() string { return “role_user”}最后在控制器中…func (c *UserController) GetOne() { idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseInt(idStr, 0, 64) v, err := models.GetUserById(id) if err != nil { c.Data[“json”] = “找不到匹配的数据” } else { c.Data[“json”] = v } c.ServeJSON()}…最后,测试假如GetOne()对应的URL是localhost:8080/v1/user/:id请求http://localhost"8080/v1/user/1时,返回的数据{ “Id”: 1, “Username”: “testa”, “CreatedAt”: “2018-01-01T12:36:47+08:00”, “UpdatedAt”: “2018-01-01T12:36:47+08:00”, “Roles”:[ { “Id”: 1, “Name”: “测试角色A”, “CreatedAt”: “2018-01-01T12:36:47+08:00”, “UpdatedAt”: “2018-01-01T12:36:47+08:00”, }, { “Id”: 2, “Name”: “测试角色B”, “CreatedAt”: “2018-01-01T12:36:47+08:00”, “UpdatedAt”: “2018-01-01T12:36:47+08:00”, }, ]} ...

December 28, 2018 · 2 min · jiezi

一种巧妙的对象映射关系设计--JSON-ORM

项目介绍这是标准数据库封装的上半部分,智能查询(JSON-ORM)的实现。完整代码:https://github.com/zhoutk/gels设计思路我们通用的ORM,基本模式都是想要脱离数据库的,几乎都在编程语言层面建立模型,由程序去与数据库打交道。虽然脱离了数据库的具体操作,但我们要建立各种模型文档,用代码去写表之间的关系等等操作,让初学者一时如坠云雾。我的想法是,将关系数据库拥有的完善设计工具之优势与微服务结合起来,数据设计提供结构信息;前端送到后端的json对象自动映射成为标准的SQL查询语句。只要我们理解了标准的SQL语言,我们就能够完成数据库查询操作。我的这种ORM方式,服务端不需要写一行代码,只需完成关系数据库的设计,就能为前端提供标准服务接口。并且遵循一套统一的接口(已经实践检验,满足百分之九九的查询需求)来实现数据库封装,达到业务层可以随意切换数据库的目的。数据库查询操作接口。export default interface IDao { select(tablename: string, params: object, fields?: Array<string>): Promise<any>; //自动生成sql语句 execSql(sql: string, values: Array<any>, params: object, fields?: Array<string>): Promise<any>; //执行手动sql语句}智能查询(JSON-ORM)查询保留字:fields, page, size, sort, search, lks, ins, ors, count, sum, groupfields, 定义查询结果字段,支持数组和逗号分隔字符串两种形式由前端来确定返回的数据库字段信息,这样后端的设计可以适用面更广泛,而不会造成网络带宽的浪费。在KOA2的框架下,GET请求要支持输入数组,只能把同一个key多次输入,如:age=11&age=22。这样很不方便,我实现了一个参数转换函数,针对数组提供多种输入形式。arryParse arryParse(arr): Array<any>|null { //返回值为数据或空值 try { if (Array.isArray(arr) || G.L.isNull(arr)) { //如果输入是数组或空,直接返回 return arr } else if (typeof arr === ‘string’) { //若是字符串 if (arr.startsWith(’[’)) { //数组的字符串形式,进行转换 arr = JSON.parse(arr) } else { //逗号拼接的字符串,mysql的驱动同时支持参数以字符串形式或数组形式提供, //所以这里可以不加判断,直接用split函数将字符串转化为数组 arr = arr.split(’,’) } } } catch (err) { arr = null //数组的字符串形式转换失败,刘明输入参数是错误的 } return arr }查询示例:请求URL: /rs/users?username=white&age=22&fields=[“username”,“age”]生成sql: SELECT username,age FROM users WHERE username = ? and age = ?page, size, sort, 分页排序在mysql中这比较好实现,limit来分页是很方便的,排序只需将参数直接拼接到order by后就好了。 查询示例:请求URL: /rs/users?page=1&size=10&sort=age desc生成sql: SELECT * FROM users ORDER BY age desc LIMIT 0,10search, 模糊查询切换参数,不提供时为精确匹配提供字段查询的精确匹配与模糊匹配的切换,实现过程中,注意参数化送入参数时,like匹配,是要在参数两边加%,而不是在占位符两边加%。另外,同一个字段匹配两次模糊查询,需要特别处理,我提供了一种巧妙的方法:value = pool.escape(value).replace(/', '/g, “%’ and " + key + " like ‘%”) //将值用escape编码,数组将转化为逗号连接的字符串,用正则全局替换,变成and连接value = value.substring(1, value.length - 1) //去掉两头多余的引号where += key + " like ‘%" + value + “%’” //补齐条件查询语句,这种方式,比用循环处理来得快捷,它统一了数组与其它形式的处理方式查询示例请求URL: /rs/users?username=i&password=1&searchins, lks, ors这是最重要的三种查询方式,如何找出它们之间的共同点,减少冗余代码是关键。ins, 数据库表单字段in查询,一字段对多个值,例: 查询示例:请求URL: /rs/users?ins=[“age”,11,22,26]生成sql: SELECT * FROM users WHERE age in ( ? )ors, 数据库表多字段精确查询,or连接,多个字段对多个值,支持null值查询,例: 查询示例:请求URL: /rs/users?ors=[“age”,1,“age”,22,“password”,null]生成sql: SELECT * FROM users WHERE ( age = ? or age = ? or password is null )lks, 数据库表多字段模糊查询,or连接,多个字段对多个值,支持null值查询,例:查询示例:请求URL: /rs/users?lks=[“username”,“i”,“password”,null]生成sql: SELECT * FROM users WHERE ( username like ? or password is null )count, sum这两个统计求和,处理方式也类似,查询时一般要配合group与fields使用。count, 数据库查询函数count,行统计,例:查询示例:请求URL: /rs/users?count=[“1”,“total”]&fields=[“username”]生成sql: SELECT username,count(1) as total FROM userssum, 数据库查询函数sum,字段求和,例:查询示例:请求URL: /rs/users?sum=[“age”,“ageSum”]&fields=[“username”]group, 数据库分组函数group,例: 查询示例:请求URL: /rs/users?group=age&count=["",“total”]&fields=[“age”]生成sql: SELECT age,count() as total FROM users GROUP BY age不等操作符查询支持支持的不等操作符有:>, >=, <, <=, <>, =;逗号符为分隔符,一个字段支持一或二个操作。 特殊处:使用"=“可以使某个字段跳过search影响,让模糊匹配与精确匹配同时出现在一个查询语句中一个字段一个操作,示例:查询示例:请求URL: /rs/users?age=>,10生成sql: SELECT * FROM users WHERE age> ?一个字段二个操作,示例:查询示例:请求URL: /rs/users?age=>,10,<=,35生成sql: SELECT * FROM users WHERE age> ? and age<= ?使用”=“去除字段的search影响,示例:查询示例:请求URL: /rs/users?age==,22&username=i&search生成sql: SELECT * FROM users WHERE age= ? and username like ?相关视频课程运用typescript进行node.js后端开发精要 nodejs实战之智能微服务快速开发框架 JSON-ORM(对象关系映射)设计与实现资源地址凝胶(gels)项目: https://github.com/zhoutk/gels视频讲座资料: https://github.com/zhoutk/sifou个人博客: https://github.com/zhoutk/blog ...

November 23, 2018 · 2 min · jiezi