1. 1. 前置概念:
    1. 1.1. POJO
    2. 1.2. ORM框架
    3. 1.3. JPA
  2. 2. 搭建一个 SpringBoot. 3.x + Hibernate + HQL查询的项目
  3. 3. HQL中的 sql注入
    1. 3.1. 参数直接拼接
  4. 4. HQL中的预编译
    1. 4.1. (1) 命名参数占位
    2. 4.2. (2) 位置参数占位
    3. 4.3. (3) 列表占位 (in查询)

前置概念:

POJO

POJO (Plain Old Java Object) 是指 普通的 Java 对象,它没有继承自特定的类,也没有实现特定的接口。POJO 是一种简单的、符合 Java Bean 规范的 Java 类。可以把它理解成一个最普通的 JavaBean。

POJO 的特点是:

  • 无特定继承:POJO 不需要继承自特定的父类,通常不继承任何框架类。
  • 无特定接口:POJO 不需要实现任何特定的接口。
  • 属性和方法:POJO 通常拥有一组私有属性,并提供公共的 getter 和 setter 方法来访问这些属性。
  • 无框架依赖:POJO 不依赖于任何特定的框架或技术(如 EJB 等)。它是一个完全独立的 Java 类。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Person {
private String name;
private int age;

// 无参构造方法
public Person() {}

// 带参构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}

// Getter 和 Setter 方法
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

在 ORM框架中,POJO类通常用作映射数据库。

——————————————————————————————————————————————————

ORM框架

ORM框架(Object-Relational Mapping Framework,面向对象与关系型数据库映射框架)是一种技术,它使得开发者能够在面向对象编程中使用对象来表示数据库中的数据,而不需要直接操作数据库中的表、行和列。ORM框架通过将对象的属性与数据库表中的字段相映射,实现了面向对象程序与关系型数据库之间的转换。

ORM的核心概念是将数据库中的表(table)列(column)、映射到类(class)、和**对象(object)**的属性(field),使得开发者可以通过操作对象来进行数据库操作,而不必编写大量的SQL语句。ORM框架通过为开发者提供高级API,简化了数据库操作,自动生成SQL语句来完成对数据库的增、删、改、查操作。

工作原理

ORM框架通过映射关系将数据库表与程序中的类关联起来。通常包括以下几个步骤:

  • 类映射:每个数据库表通常对应一个类,类的每个字段(属性)映射到表的每一列。类对象的操作会反映到数据库表的操作。
  • CRUD操作:开发者通过操作对象(增、删、改、查),ORM框架会自动将这些操作转换为相应的SQL语句,并执行这些SQL语句来操作数据库。
  • 映射配置:ORM框架通常需要通过注解或XML配置文件指定类与数据库表之间的映射关系。

熟知的 ORM框架包括 Mybatis-plus,Hibernate等等。

——————————————————————————————————————————————————

JPA

JPA(Java Persistence API)是一个用于 Java 平台上的对象关系映射(ORM)规范,它提供了一组标准化的接口和方法来简化 Java 应用程序与数据库之间的交互。JPA 使得 Java 开发者可以以面向对象的方式操作关系型数据库,并且与底层数据库的细节分离,避免了直接操作 SQL 语句。

JPA 本身并不是一个实现,而是一个规范。它定义了 Java 应用程序与关系型数据库交互的标准,开发者可以通过实现 JPA 规范的 ORM 框架(如 Hibernate、EclipseLink、OpenJPA)来使用 JPA 功能。

概念组成:

实体类(Entity)

  • 实体类是一个 Java 类,它代表了数据库中的一张表。每个实体类的实例代表该表的一行记录。实体类通常需要通过注解(如 @Entity)来标识,JPA 会根据这些实体类生成对应的数据库表。

持久化上下文(Persistence Context)

  • 持久化上下文是 JPA 管理实体的生命周期的容器。它负责跟踪所有已持久化实体的状态,并确保数据的一致性和持久性。持久化上下文与事务紧密结合。

实体管理器(EntityManager)

  • 实体管理器是 JPA 提供的一个接口,它用于管理实体对象的生命周期,执行对数据库的操作,如保存、更新、删除和查询。实体管理器是 JPA 操作数据库的核心接口。

查询语言(JPQL)

  • JPA 提供了一种面向对象的查询语言,称为 JPQL(Java Persistence Query Language)。JPQL 允许开发者使用面向对象的语法来执行数据库查询,而无需直接编写 SQL。JPQL 查询的是实体对象而不是数据库表。

注解(Annotations)

  • JPA 使用注解来描述实体类与数据库表之间的映射关系。例如,@Entity 用于标识实体类,@Id 用于标识主键,@Column 用于定义列映射等。

事务管理

  • JPA 提供了对事务的支持,确保操作的原子性。它可以与 JTA(Java Transaction API)进行集成,实现分布式事务控制。

JPA 常见注解:

  1. @Entity:用于标识一个类是实体类,映射到数据库表。
  2. @Id:用于指定实体类的主键。
  3. @GeneratedValue:用于指定主键的生成策略。
  4. @Column:用于指定实体类属性和数据库列之间的映射关系。
  5. @OneToMany@ManyToOne@ManyToMany@OneToOne:用于定义实体类之间的关联关系。
  6. @Table:用于指定数据库表的名称。
  7. @Query:用于定义自定义的 JPQL 查询。

——————————————————————————————————————————————————

搭建一个 SpringBoot. 3.x + Hibernate + HQL查询的项目

Hibernate 是一个强大的 对象关系映射(ORM) 框架,它用于简化 Java 程序与关系型数据库之间的交互。ORM 的核心思想是将数据库表与 Java 对象进行映射,从而让开发者可以通过操作 Java 对象来实现对数据库的增、删、改、查等操作,避免了直接编写繁琐的 SQL 语句。个人感觉 Hibernate和 mybatis-plus的结构有些相似,都是使用了 ORM框架,但是 hibernate明显要更为复杂。

配置文件 pom.xml和 application.properties不做赘述。

(1) 首先创建 JPA实体类 person,对应的是数据库中的 person表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package org.kgty.sql_hibernate;
import jakarta.persistence.*;

@Entity
@Table(name = "person")
public class person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "username")
private String username;

@Column(name = "password")
private String password;

public person() {}

public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

(2) 其次创建仓库接口,在 Spring Data JPA 中,可以通过继承 JpaRepository 来快速创建一个数据访问层接口。 JpaRepository 接口中提供了许多现成的数据库查询方法,比如 findById()方法,等等。如果提供的数据库查询方法无法满足需求,开发可以进行自定义。

1
2
3
4
5
6
7
8
package org.kgty.sql_hibernate.repository;
import org.kgty.sql_hibernate.person;
import org.springframework.data.jpa.repository.JpaRepository;

public interface personRepository extends JpaRepository<person, Long> {
// 可以添加一些自定义查询方法
person findByUsername(String name);
}

使用默认的 findByUsername(String name)方法,数据库查询效果相当于

1
select * from person where username = 'xxxx';

并且默认的查询方法使用了预编译,可以避免 sql注入。

(3) 除了使用默认提供的查询方法外,还可以使用 HQL构造查询语句来查询数据库,在服务层中使用:

HQL(Hibernate Query Language,Hibernate 查询语言)是 Hibernate 框架中提供的一种查询语言,它与 SQL 类似,但 HQL 主要用于操作 Hibernate 实体对象而不是数据库表。HQL 使开发者能够使用面向对象的语法进行数据库查询,它是 Hibernate 框架特有的语言,旨在简化数据库操作,并减少对底层数据库表的直接依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.kgty.sql_hibernate.service;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import org.hibernate.query.Query;
import org.kgty.sql_hibernate.person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;


@Service
public class personService {
@Autowired
private EntityManager entityManager;

@Transactional
public List<person> getPersonByUsername(String username) {

//使用 HQL语句进行数据库操作
String hql = "FROM person p WHERE p.username = :username";
Query query = (Query) entityManager.createQuery(hql);
query.setParameter("username", username);
return query.getResultList(); // 返回符合条件的所有结果
}

}

(4) 创建controller层,路由中使用服务层方法,启动项目,成功访问,得到数据库回显数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.kgty.sql_hibernate.controller;
import org.kgty.sql_hibernate.service.personService;
import org.kgty.sql_hibernate.person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/person")
public class personController {
@Autowired
private personService personService;

// 查询指定名字的用户
@GetMapping("/name/{name}")
public List<person> getPersonByName(@PathVariable String name) {
return personService.getPersonByUsername(name);
}

}

image-20250121133607254

HQL中的 sql注入

参数直接拼接

1
String hql = "FROM person p WHERE p.username = '"+username+"'";

image-20250121133955588

image-20250121134011122

HQL支持运行原生 SQL语句 - createNativeQuery(),若直接拼接参数会造成 sql注入:

1
Query query = (Query) entityManager.createNativeQuery("select * from person where username like '"+username+"'");

HQL中的预编译

为了避免 SQL注入,HQL给出了几种参数绑定方式,即同理预编译的占位符。

(1) 命名参数占位

1
2
3
4
String hql = "FROM person p WHERE p.username = :username";
Query query = (Query) entityManager.createQuery(hql);
query.setParameter("username", username);
return query.getResultList(); // 返回符合条件的所有结果

使用 : 后面跟输入参数的方式进行占位。

PS: 这个东西前段时间 京东面试的时候面试官问到了,当时只知道 ? 进行占位,完全没听说过冒号这个说法,现在看来是认知浅薄了。

占位符起到作用,进行了预编译:

image-20250121135454669

(2) 位置参数占位

1
2
3
4
String hql = "FROM person p WHERE p.username = ?1";
Query query = (Query) entityManager.createQuery(hql);
query.setParameter(1, username);
return query.getResultList(); // 返回符合条件的所有结果

效果同理,预编译,不做赘述。

(3) 列表占位 (in查询)

1
2
3
4
5
6
7
public List<person> getPersonByUsername(List<String> username) {
//使用 HQL语句进行数据库操作
String hql = "FROM person p WHERE p.username in :username";
Query query = (Query) entityManager.createQuery(hql);
query.setParameter("username", username);
return query.getResultList(); // 返回符合条件的所有结果
}

使用列表可以进行批量查询:

image-20250121141709931

同样会进行预编译:

image-20250121141728709