高效使用JavaEE ORM框架

高效使用JavaEE ORM框架

虽然Java领域有无数的ORM框架,如,,TopLink,JDO,JPA……但是这些ORM框架基本上大同小异。很多初学者对JDBC的复杂性望而却步,就简单认为使用ORM就会省时省力,结果恰恰相反,任何好的框架都是给专家准备的,任何急功近利试图偷懒的方法往往适得其反。要正确使用ORM还真不是一件简单的事情。本文仅简单整理一下ORM的原理,基本用法,以及如何避免各种陷阱的基本编程原则。

ORM的原理

先说ORM的实现原理。其实,要实现JavaBean的属性到数据库表的字段的映射,任何ORM框架不外乎是读某个配置文件把JavaBean的属性和数据库表的字段自动关联起来,当从数据库Query时,自动把字段的值塞进JavaBean的对应属性里,当做INSERT或UPDATE时,自动把JavaBean的属性值绑定到SQL语句中。但是,几乎所有的ORM都提供“按需读取”的功能,比如一个User有id,name,email和address这4个字段,但是address字段很少用,于是ORM只读取前3个字段,直到调用User的getAddress()方法时,才去数据库中读取address的值。这个功能显然不能通过User的get/set完成,因此,ORM需要采用某种方式生成一个User类的子类,并且覆写get/set方法,这样,才能在调用get方法时有机会从数据库中读取。类似的对User的修改检测也是这样实现的。

两种增强的方式

ORM为我们自己的JavaBean实现子类的方法很多,这个过程简单称之为“增强”,基本上有两种方法:Hibernate使用CGLIB在加载我们的User类时动态创建了一个子类,而JDO则要求编译完User类后再利用它提供的工具对User类进行改造,以便实现JDO需要的各种接口。请注意:就是这种极其变态的设计导致了使用JDO的极大困难,在我们编译完源码后,还需要额外执行一个增强命令,或者额外编写Ant任务,极大地影响了快速开发和单元测试,所以,凡是采用静态生成持久类的ORM,要在第一时间直接排除,切记!

理解持久和非持久状态

所有的ORM框架都有持久和非持久的概念。简单地说,当我们new一个User实例时,它是非持久对象,当我们调用ORM的save()之类的方法后,这个实例就变成持久对象了。当我们通过ORM从数据库读取到一个User对象时,这个对象是持久对象,当关闭当前的事务后,这个对象变成非持久对象。

虽然这个过程很容易理解,但是,难点在于当我们设计一个方法时,我们必须准确地知道当前操作的对象是持久对象还是非持久对象,否则,各种灵异事件会接踵而来,比如插入了重复记录等等。举例说明,当我们需要创建一个User对象时,save(User)方法必须传入非持久对象,当我们需要更新一个User对象时,update(User)方法必须传入一个持久对象,有些ORM比如Hibernate,为了方便用户,提供了saveOrUpdate()方法,自动判断是否是持久对象,是则更新,否则创建。我的建议是永远不要使用这些看上去很简单的方法,否则将很难判断ORM到底做了什么操作,也就很难追踪到逻辑错误。

正确使用CRUD

正因为我们需要时刻区分一个对象的持久化状态,所以,编写CRUD(Create,Retrieve,Update,Delete的缩写)要严格遵循以下原则:

Create:对于Create操作,传入的永远是非持久化对象,一旦调用了create方法,就变成持久化对象;

Retrieve:所有通过ORM从数据库读取的对象都是持久化对象,直到当前会话结束;

Update:对于Update操作,传入的必须是持久化对象,而通常需要更新的对象是从页面获得的,因此,编写Update方法要按照以下步骤:

从Web页面中获得了一个User对象(包含ID),这个对象肯定是非持久化对象;

当得到该User对象时,千万不可直接做Update操作,因为从Web页面得到的数据都是不可信任的,修改HTTP请求非常简单,有经验的开发人员利用一个FireFox插件就能完成。正确的做法是根据该User对象的ID从数据库中查询到持久化的User对象,然后把待修改的属性复制到持久化的User对象中,最后Update该持久化的User对象,简单的代码如下:        01. void update(User u) {   02.    // 从数据库读取User:   03.    User p = load(User.class, u.getId());   04.    // 检查当前用户有无权限修改:   05.    // TODO: ...   06.    // 复制属性:   07.    p.setName(u.getName());   08.    p.setAddress(u.getAddress());   09.    // 不允许修改的属性例如email就不要复制了,然后更新:   10.    update(p);   11. }

Delete:对于Delete操作,ORM通常只关心映射到主键的ID属性,不过,正确的做法仍然是根据ID先通过ORM读取,然后判断权限,最后删除。简单的代码如下:        1. void delete(String id) {   2.    // 从数据库读取User:   3.    User p = load(User.class, u.getId());   4.    // 检查当前用户有无权限修改:   5.    // TODO: ...   6.    // 删除:   7.    delete(p);   8. }

严格按照正确的方法做CRUD操作,使用ORM才能事半功倍。

级联读取

数据库表支持外键关联,因此,ORM也可以把多个JavaBean按照数据库的外键关系联系起来,比如可以在读User对象时把其关联的Profile对象也一并读出来,即所谓级联读取。这又是一个使用起来要非常小心谨慎的功能。

首先,我的建议是级联读取的层次最好是0或1,一般不要超过3,千万不可设为无限,否则,一个简单的查询可能就读取了上万条记录,在开发时由于并发用户只有1,往往看不出问题,等到部署了发现服务器经常内存溢出,其实是级联读取太多导致的。

其次,级联有一对多和多对一两种(一对一可以并入第二种),要非常小心地使用一对多,除非有十分的把握确定“多”的一端只有不超过100条记录。比如设计论坛时读取“版面”Board时如果有一对多顺便把“话题”Topic一并读入了,随着Topic越来越多,每次读取Board的内存占用也越来越多,直到最后内存溢出。因此,我的建议是最好不用一对多,凡是有一对多的需求全部采用分页查询的方式解决。

最后,大多数ORM对级联读取都是采用join的方式,在数据量很大的情况时,join操作很慢,而且无法水平分割数据库表。对读取要求很高的应用,最好不要设置级联读取。

缓存

绝大多数ORM都会提供缓存,通常还分为一级缓存和二级缓存。一级缓存只在当前会话内有效,当我们在一个会话里反复读取同一个对象时,只有第一次ORM会从数据库中读取,后续请求会直接从缓存中读取,例如:        1. int id=12345;   2. User u1 = load(User.class, id); // 从数据库读   3. User u2 = load(User.class, id); // 从一级缓存读   4. System.out.println(u1==u2); // True

实际上,很少有人会写出这样的代码,所以,一级缓存几乎没有什么作用。

而二级缓存就作用于整个应用。不过,当数据量很小的时候,通过增大数据库服务器的内存就和使用缓存没什么区别,当数据量非常大的时候,二级缓存的命中率很低,原因当然是缓存大小不够,因此,针对海量数据通常都要自己动手,用memcached做专门的缓存服务器来提高性能。所以,二级缓存不开也罢。

确定事务范围,小心使用Lasy Loading

使用ORM也需要对数据库事务有一定了解,通常ORM的一个会话对应一个数据库事务,如果事务持续时间长,占用的数据库资源就长,数据库并发处理能力就会降低,所以,事务范围越短越好。对于Web应用,把事务限制在Controller中就比限制在Controller+View中要短,通过MVC框架提供的各种拦截器可以很方便地开启和关闭事务。当事务限制在Controller时,到了View渲染的时候,就无法使用LasyLoading功能了。Lasy Loading虽然简单,但不当使用也会造成严重的性能问题,所以还是不用为妙。

以上对ORM框架的使用做了一个简单的概括,实际应用中还需要通过大量实践慢慢探索。

Read more

60个“特征工程”计算函数(Python代码)

60个“特征工程”计算函数(Python代码)

转自:coggle数据科学 近期一些朋友询问我关于如何做特征工程的问题,有没有什么适合初学者的有效操作。 特征工程的问题往往需要具体问题具体分析,当然也有一些暴力的策略,可以在竞赛初赛前期可以带来较大提升,而很多竞赛往往依赖这些信息就可以拿到非常好的效果,剩余的则需要结合业务逻辑以及很多其他的技巧,此处我们将平时用得最多的聚合操作罗列在下方。 最近刚好看到一篇文章汇总了非常多的聚合函数,就摘录在下方,供许多初入竞赛的朋友参考。 聚合特征汇总 pandas自带的聚合函数 * 其它重要聚合函数 其它重要聚合函数&分类分别如下。 def median(x):     return np.median(x) def variation_coefficient(x):     mean = np.mean(x)     if mean != 0:         return np.std(x) / mean     else:         return np.nan def variance(x):     return

By Ne0inhk
90w,确实可以封神了!

90w,确实可以封神了!

要说24年一定最热的技术,还得是AIGC! 前段时间阿里旗下的开源项目,登上GitHub热榜! AI大热,如今ChatGPT的优异表现,必然会出现各种细分场景应用的工具软件,和大量岗位项目! 山雨欲来风满楼,强人工智能的出现,所有科技公司已经开始巨量扩招此领域的人才。算法的岗位,近三个月已经增长68%!这件事在HR届也是相当震撼的。 目前各行各业都不景气的市场,人工智能岗位却一直保持常青!甚至同属AI边缘岗都比其他岗薪资高40%! 与此同时,AI算法岗上岸也不简单,竞争激烈,好公司核心岗位不用说,谁都想去。 所以事实就是,想要上岸,门槛也逐渐变高,项目经历、实习经历都很重要,越早明白这个道理就越能提前建立起自己的优势。 但我在b站逛知识区的时候,经常看到有些同学,因为一些客观原因导致无法参加实习,这种情况下,如果你想提升背景,增加项目经历的话,可以试试这个《CV/NLP 算法工程师培养计划》。 目前已经有上千位同学通过该计划拿到offer了,最新一期学员就业薪资最高能拿到78K!年薪94w! 优势就是有BAT大厂讲师带领,手把手带做AI真实企业项目(包含CV、NLP等

By Ne0inhk
再见nohup!试试这个神器,Python Supervisor!

再见nohup!试试这个神器,Python Supervisor!

👇我的小册 45章教程:() ,原价299,限时特价2杯咖啡,满100人涨10元。 作者丨Ais137 https://juejin.cn/post/7354406980784373798 1. 概述 Supervisor 是一个 C/S 架构的进程监控与管理工具,本文主要介绍其基本用法和部分高级特性,用于解决部署持久化进程的稳定性问题。 2. 问题场景 在实际的工作中,往往会有部署持久化进程的需求,比如接口服务进程,又或者是消费者进程等。这类进程通常是作为后台进程持久化运行的。 一般的部署方法是通过 nohup cmd & 命令来部署。但是这种方式有个弊端是在某些情况下无法保证目标进程的稳定性运行,有的时候 nohup 运行的后台任务会因为未知原因中断,从而导致服务或者消费中断,进而影响项目的正常运行。 为了解决上述问题,通过引入 Supervisor 来部署持久化进程,提高系统运行的稳定性。 3. Supervisor 简介 Supervisor is a client/

By Ne0inhk
第一本给程序员看的AI Agent图书上市了!

第一本给程序员看的AI Agent图书上市了!

AI Agent火爆到什么程度? OpenAI创始人奥特曼预测,未来各行各业,每一个人都可以拥有一个AI Agent;比尔·盖茨在2023年层预言:AI Agent将彻底改变人机交互方式,并颠覆整个软件行业;吴恩达教授在AI Ascent 2024演讲中高赞:AI Agent是一个令人兴奋的趋势,所有从事AI开发的人都应该关注。而国内的各科技巨头也纷纷布局AI Agent平台,如:钉钉的AI PaaS、百度智能云千帆大模型平台等等。 Agent 是未来最重要的智能化工具。对于程序员来说,是时候将目光转向大模型的应用开发了,率先抢占AI的下一个风口AI Agent。 小异带来一本新书《大模型应用开发 动手做 AI Agent》,这本书由《GPT图解》的作者黄佳老师创作,从0到1手把手教你做AI Agent。现在下单享受5折特惠! ▼点击下方,即可5折起购书 有这样一本秘籍在手,程序员们这下放心了吧,让我们先来揭开 Agent 的神秘面纱。 AI Agent 面面观

By Ne0inhk