JPQL是否操作内存实体不访问数据库?与原生SQL有何区别?
JPQL核心描述的准确含义
JPQL与SQL类似,但它操作的是对象、属性和关联关系,而非数据表和列。
这句话的核心是描述JPQL的抽象层级,完全不是指JPQL只操作内存对象、不访问数据库,别理解偏了:
- 写JPQL时,
FROM后填的是加了@Entity注解的Java实体类名,不是数据库里的物理表名 - 查询条件、关联、返回字段填的是实体类的属性名、实体间配置好的关联字段(比如用户实体上的
orderList一对多属性),不是数据库的列名、外键字段 - 你不需要手动写表和表之间的外键关联条件,JPA实现(比如Hibernate)会根据你在实体上写的映射规则,自动把JPQL翻译成对应数据库的SQL去执行,这也是你观测到JPQL最终会生成SQL访问数据库的原因。
举个最简单的例子,写JPQL的时候你可以直接写SELECT u FROM User u JOIN u.roles r WHERE r.roleCode = 'admin',不需要手动写JOIN t_user_role ON t_user.id = t_user_role.user_id JOIN t_role ON t_user_role.role_id = t_role.id这种硬编码表名、外键的逻辑,这些关联逻辑框架会根据映射自动补全。
JPQL和原生SQL的实际区别
两者最终都会生成SQL访问数据库,核心差异在抽象层和适用场景:
- 耦合对象不同:JPQL和Java实体模型耦合,只要实体和数据库表的映射关系配置正确,哪怕后续改了表名、列名,只要调整映射注解就行,JPQL代码不用改;原生SQL直接和数据库物理结构耦合,表名、列名、外键关系改了,SQL必须同步修改。
- 跨库适配能力不同:JPQL会根据你配置的数据库方言,自动生成对应数据库的适配语法,比如分页逻辑、内置函数的差异,框架会帮你抹平;原生SQL如果要兼容多种数据库,得自己写分支判断适配不同语法。
- 面向对象能力支持不同:JPQL天然支持多态查询,比如你有一个
Payment抽象父实体,下面有WechatPay、AlipayPay两个子实体对应不同的表,写SELECT p FROM Payment p就能自动把两个子表的数据查出来,映射成对应的子类实例;原生SQL要实现这个效果得自己写多表union、自己判断类型做实例转换。 - 出错概率不同:JPQL的语法、类名、属性名在应用启动或者编译阶段就能做校验,很少出现运行时SQL语法错误,也天然避免SQL注入问题;原生SQL写法灵活,但很容易因为拼写错误出语法问题,字符串拼接不当还会有SQL注入风险。
持久化上下文跟踪的规则
不是只有JPQL查出的实体才会被跟踪:
- 不管是JPQL、Criteria API,还是符合JPA规范的派生查询方法,查出来的匹配实体映射的结果,默认都会加入当前持久化上下文被跟踪,你修改实体属性后,事务提交时会自动把变更同步到数据库。
- 原生SQL查出的结果只要是按照JPA规范映射到了实体类,默认也会被持久化上下文跟踪。一般就是你用
createNativeQuery执行原生SQL时,第二个参数传了实体类.class的场景,框架会自动把结果集映射成实体实例加入上下文。 - 只有两种情况原生SQL结果不会被跟踪:一是你查的是部分字段,返回结果是Object数组、自定义DTO,没有对应完整的实体映射;二是你写的原生SQL查出来的结果没有合法的主键映射(比如连表查出来重复主键、查的是无主键视图),这种情况框架没法完成实体绑定,自然不会跟踪。
- 顺带提一句:原生SQL查实体很容易因为映射不匹配、结果集字段和实体不一致出现跟踪异常、数据覆盖的问题,非必要不建议这么用。
内容的提问来源于stack exchange,提问作者microwth
相关产品推荐
相关产品推荐

