前言

本文主要介绍JPA相关技术以及在项目中如何集成Spring Data JPA。后端基于Java17SpringBoot3开发。

相关技术简介

ORM(Object-Relational Mapping,对象-关系映射)

ORM(Object-Relational Mapping) 即对象-关系映射。在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中,一个类可以视作数据库中的一张表,类中的属性视作表中的字段,一个对象可以视为表中的一行记录。只要有一套程序能够做到建立对象与数据库的关联,操作对象就可以直接操作数据库数据,就可以说这套程序实现了ORM。也就是说ORM是建立了一个实体与数据库表的联系,这使得开发者对实体的直接操作而不是对数据库的操作,但操作实体也就等同于操作了数据库。主流的ORM框架有MyBatis、Hibernate、Spring Data JPA等。

JPA(Java Persistence API,Java持久层API)

JPA(Java Persistence API)是 Java EE 标准中的一部分,它通过提供提供一套对象关系模型(ORM)来帮助在开发应用过程中高效的管理关系型数据。和 JDBC 一样,JPA 本身并不是一个框架,只是一套标准接口,是ORM框架的标准和规范,目前比较成熟的 JPA 实现有 Hibernate、EclipseLink 等。

Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。

JPA的目标是提供一种统一的、面向对象的数据访问方式,使开发人员能够更加方便地进行数据库操作,而不需要关注底层数据库的细节。它抽象了不同数据库之间的差异,提供了一套通用的API,使开发人员能够以相同的方式操作不同的数据库。

JPA的核心概念包括实体(Entity)、实体管理器(EntityManager)、持久化上下文(Persistence Context)等。开发人员可以通过注解或XML配置来定义实体类和数据库表之间的映射关系,然后使用EntityManager进行增删改查等数据库操作。

Hibernate

Hibernate是一个标准的ORM框架,实现Jpa接口。Hibernate着手解决如何实现映射的方案,是一种处理映射关系方法类框架。

JPA和Hibernate 的关系就像JDBC和JDBC 驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。

Spring Data

Spring Data是Spring Framework的一个子项目,它提供了一种简化数据访问层的方式。Spring Data支持多种数据存储技术,包括关系型数据库、NoSQL数据库、搜索引擎等。

通过使用Spring Data,开发人员可以更轻松地进行数据访问,同时还能够利用Spring Framework的其他功能,如依赖注入、面向切面编程等。

Spring Data提供了一组通用的API和实现,可以帮助开发人员快速、简便地访问各种类型的数据存储。

Spring Data JPA

Spring Data JPA是Spring Data项目中的一个模块,它提供了对JPA(Java Persistence API)的支持。

Spring Data JPA通过提供一组简化的API和自动化的实现,使得开发人员可以更轻松地进行数据库访问和操作。它减少了编写重复、繁琐的数据访问代码的工作量,同时还提供了一些方便的特性,如查询方法的自动生成、分页和排序的支持等。

使用Spring Data JPA,开发人员只需定义好实体类和接口,通过继承一些预定义的接口,就可以实现常见的数据访问操作,而无需编写具体的SQL语句。这样可以大大简化开发过程,提高开发效率。

Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,但底层还是使用了 Hibernate 的 JPA 技术实现。

SpringBoot整合Spring Data JPA

将以实现对用户表SysUser的增删改查为例,介绍SpringBoot整合Spring Data JPA的详细过程。所示项目基于Java17SpringBoot3实现,数据库使用MySQL 8.4.5

1.引入maven依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>6.1.6.Final</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2.修改配置文件

# 应用服务 WEB 访问端口
server.port=8080

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/erp?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
#ddl-auto可选值如下:
#none,默认值,无任何操作;
#create,服务启动时没有表会新建表,有表则删除表和已有数据后重新建表;
#create-drop,服务启动时没有表会新建表,有表则清空数据并删除后重新建表,服务停止时删除所有表和数据;
#update,服务启动时没有表则新建表,有表则更新表结构,不清空已有数据;
#validate,服务启动时会校验实体类和数据库表结构是否一致,不一致会报错。

spring.jpa.hibernate.ddl-auto=update

上述配置中spring.jpa.hibernate.ddl-auto作用是根据实体类自动建表,无需手写ddl来建表。这样就使得开发者可以专注于实体类的设计,无需再去考虑建表SQL,可以提高开发效率。但此配置项更适合于开发环境时使用,不建议生产环境使用。

ddl-auto可选值如下:

none,默认值,无任何操作;
create,服务启动时没有表会新建表,有表则删除表和已有数据后重新建表;
create-drop,服务启动时没有表会新建表,有表则清空数据并删除后重新建表,服务停止时删除所有表和数据;
update,服务启动时没有表则新建表,有表则更新表结构,不清空已有数据;
validate,服务启动时会校验实体类和数据库表结构是否一致,不一致会报错。

3.定义Spring Data JPA配置类

定义一个配置类Bean,启用Spring Data JPA,也可以直接main方法所在类上直接添加@EnableJpaRepositories@EntityScan注解。

import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

/**
 * Spring Data JPA Bean配置
 * 启用Jpa,扫描指定包下的Repository类和指定包下的实体类
 */
@Configuration
@EnableJpaRepositories(basePackages = {"com.example.springboot_jpa.repository"})
@EntityScan(basePackages = "com.example.springboot_jpa.pojo")
public class JpaConfig {
}

4.定义实体类

为了简化代码,可以将所有数据库实体类共有的属性提取出来,定义一个基础类。

/**
 * 数据库实体基类,声明数据库实体类共有属性
 */
@Getter
@Setter
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
    /**
     * ID,唯一标识列,使用主键自增策略
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 创建时间
     */
    @CreatedDate
    private LocalDateTime createdTime;

    /**
     * 最后修改时间
     */
    @LastModifiedDate
    private LocalDateTime lastModifiedTime;

    /**
     * 创建人ID
     */
    private Long creatorId;

    /**
     * 最后修改人ID
     */
    private Long lastModifierId;
}

@MappedSuperclass,表示一个类是某些实体类的超类,它不会直接映射为数据库表,但是可以被其他实体类继承,它的子类映射的数据库表中会包含它自身声明的属性。
@Id,用于声明一个实体类的属性映射为数据库的主键列,一个JPA实体类中必须要有一个使用@Id注解的属性。
@GeneratedValue,用于指定主键的生成策略,通过strategy属性来设置。
@CreatedDate,表示该字段为创建时间字段,执行insert语句前会自动赋值。
@LastModifiedDate,表示该字段为最后修改时间字段,执行insert和update语句前会自动赋值。

定义用户实体类

用户实体类SysUser中还用到了部门类Department,使用Department主要是为了介绍@Transient注解,本文不再给出其详细代码。它同样继承自BaseEntity,有部门编号(code)部门名称(name)两个属性。

@Getter
@Setter
@Entity(name = "sys_user")
public class SysUser extends BaseEntity {
    /**
     * 用户名
     */
    @Column(unique = true)
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 电话
     */
    private String phone;
    /**
     * 个人介绍
     */
    @Lob
    private String introduce;
    /**
     * 所属部门ID
     */
    private Long departmentId;
    /**
     * 所属部门
     */
//    @Transient
//    private Department department;
}

@Entity,表示该类是一个实体类,可以映射为数据库表。name属性用于自定义表名,不设置name属性时默认会取类名转化为全小写+下划线分隔的字符串作为表名,比如类名为SysUser则默认映射的表名为sys_user。

在使用了@Entity注解的类中,必须要有一个使用@Id注解修饰的属性。
在使用了@Entity注解的类中,所有的属性都会被映射为数据库表中的字段,除非属性上使用了@Transient注解。

@Column,表示该属性是一个表中的字段,unique = true可以在对应字段上定义唯一约束,在有@Entity注解修饰的类中,属性上不加@Column注解也可以被映射为字段。

@Lob,表示该属性需要映射成数据库支持的大对象类型的字段,比如Clob或者Blob。

@Transient,表示该属性并非一个到数据库表中字段的映射,ORM框架映射时会忽略该属性。

5.定义Repository接口

JPA中的Repository可以理解为DAO(Data Access Object),即数据操纵对象。一个JPA的实体类需要有一个对应的Repository,用于操作该实体类所映射的数据库表。Repository可以仅仅是一个接口无需创建它的实现类,需要继承JpaRepository接口,T是实体类,ID是实体类中@Id修饰的属性的类型。程序启动时Spring Data JPA会自动生成该接口的实现,并注册为Spring Bean。

JpaRepository中定义了一系列最基本的CURD方法,开发者可以直接使用,也可以根据业务需求自定义查询方法。Spring Data JPA支持多种查询定义方式,主要包括:

基于方法名创建查询,可以根据方法名中的关键词构造出实际的查询SQL,在附录中给出了Spring Data JPA所支持的关键词;
基于@Query注解创建JPQL查询或者原生SQL查询,nativeQuery属性设置为true时表示使用原生SQL;
基于Specification构造动态查询,此方式需要Repository继承JpaSpecificationExecutor接口,T是实体类;


以下代码是使用不同方法创建查询的示例,基于Specification的查询方式在下一章节中给出。

public interface SysUserRepository extends JpaRepository<SysUser, Long>,
        JpaSpecificationExecutor<SysUser> {
    /**
     * 根据username精确查询
     */
    Optional<SysUser> findByUsername(@Param("username") String username);

    long countByUsername(@Param("username") String username);

    @Query("select u from sys_user u where u.username = ?1")
    Optional<SysUser> findByUsername2(@Param("username") String username);
}

6.使用Repository接口

在前文中我们已经定义好了一个用户实体类SysUser和一个用户表的DAO接口SysUserRepository,下面我们就来使用它们完成简单的增删改查操作。以下代码中我们定义了一个服务类接口SysUserService和它的具体实现SysUserServiceImpl

public interface SysUserService {
    /**
     * 分页查询
     */
    Page<SysUser> page(Pageable pageable);
    /**
     * 查询全部用户
     */
    List<SysUser> list();
    /**
     * 根据ID查询
     */
    SysUser retrieve(Long id);
    /**
     * 根据用户名查询
     */
    SysUser findByUsername(String username);
    /**
     * 新增用户
     */
    SysUser create(SysUser user);
    /**
     * 修改用户
     */
    SysUser update(SysUser user);
    /**
     * 批量删除用户
     */
    void remove(List<Long> ids);
    /**
     * count
     */
    long countByName(String username);
}
@Service
public class SysUserServiceImpl implements SysUserService {
    private final SysUserRepository userRepository;

    public SysUserServiceImpl(SysUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public Page<SysUser> page(Pageable pageable) {
        return userRepository.findAll(pageable);
    }

    @Override
    public List<SysUser> list() {
        return userRepository.findAll();
    }

    @Override
    public SysUser retrieve(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @Override
    public SysUser findByUsername(String username) {
        // 使用基于方法名定义的查询
        //return userRepository.findByUsername(username).orElse(null);
        // 使用基于@Query定义的查询
        // return userRepository.findByUsername2(username).orElse(null);
        // 使用Specification查询
         return userRepository.findOne(
                 (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username").as(String.class), username)
         ).orElse(null);
    }

    @Override
    public SysUser create(SysUser user) {
        return userRepository.save(user);
    }

    @Override
    public SysUser update(SysUser user) {
        if (userRepository.findById(user.getId()).isEmpty()) {
            throw new RuntimeException("未查询到数据");
        }
        return userRepository.save(user);
    }

    @Override
    public void remove(List<Long> ids) {
        userRepository.deleteAllById(ids);
    }

    @Override
    public long countByName(String username) {
       return userRepository.countByUsername(username);
    }
}

7.controller 测试

@RestController
public class Controller {

    @Autowired
    private SysUserService sysUserService;

    @PostMapping("/createUser")
    public SysUser test() {
        SysUser sysUser = new SysUser();
        sysUser.setUsername("test");
        sysUser.setPassword("test");
        sysUser.setPhone("123456789");
        sysUser.setIntroduce("test");
        sysUser.setDepartmentId(1L);
        System.out.println(sysUser.toString());
        return sysUserService.create(sysUser);
    }

    @GetMapping("findByName")
    public SysUser findByName(@RequestParam("name") String name) {
        SysUser byUsername = sysUserService.findByUsername(name);
        System.out.println(byUsername);
        return byUsername;
    }


    @GetMapping("countByName")
    public long countByName(@RequestParam("name") String name) {
        long l = sysUserService.countByName(name);

        return l;
    }
}

JPA 连表查询和分页

实体类

@Entity
@Data
@NoArgsConstructor
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String companyName;
    private String description;

    public Company(String name, String description) {
        this.companyName = name;
        this.description = description;
    }
}
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class School {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String name;
    private String description;
}

自定义 SQL语句实现连表查询

首先我们需要创建一个包含我们需要的 Person 信息的 DTO 对象,我们简单第将其命名为 ,用于保存和传输我们想要的信息。UserDTO

@Data
@NoArgsConstructor
@Builder(toBuilder = true)
@AllArgsConstructor
public class UserDTO {
    private String name;
    private String phone;
    private String companyName;
    private String schoolName;
}

下面我们就来写一个方法查询出 user的基本信息。

    @Query("select new com.example.springboot_jpa.pojo.dto.UserDTO(u.username, u.phone, c.companyName, s.name) " +
            "from sys_user u  left  join  Company c on u.id=c.id " +
            "left  join School s on u.id = s.id " +
            "where u.id = ?1")
    Optional<UserDTO> getUserInformation(@Param("id") long id);

controller测试

    @GetMapping("getUserInformation")
    public UserDTO getUserInformation(@RequestParam("id") long id) {
        UserDTO userDTO = sysUserService.getUserInformation(id);

        return userDTO;
    }

    @GetMapping("page")
    public Page<SysUser> getUserPage(@RequestParam(value = "startPage", required = true, defaultValue = "0") int startPage,
                                     @RequestParam(value = "stopPage", required = true, defaultValue = "5") int stopPage) {
        //分页page的构造
        //Page<SysUser> userPage = sysUserService.page(PageRequest.of(0, 10));

        PageRequest page = PageRequest.of(startPage, stopPage, Sort.Direction.ASC, "id");
        //page是jpa提供的方法
        Page<SysUser> userPage = sysUserService.page(page);

        //查询结果总数
        System.out.println("查询结果总数 = " + userPage.getTotalElements());// 6
        //按照当前分页大小,总页数
        System.out.println("总页数 = " + userPage.getTotalPages());// 2
        System.out.println("获取内容 = " + userPage.getContent());

        return userPage;
    }

分页查询输出

{
    "content": [
        {
            "id": 1,
            "createdTime": null,
            "lastModifiedTime": null,
            "creatorId": null,
            "lastModifierId": null,
            "username": "test",
            "password": "test",
            "phone": "123456789",
            "introduce": "test",
            "departmentId": 1
        },
        {
            "id": 2,
            "createdTime": null,
            "lastModifiedTime": null,
            "creatorId": null,
            "lastModifierId": null,
            "username": "dayu",
            "password": "dayu",
            "phone": "123456",
            "introduce": "dayu",
            "departmentId": 2
        },
        {
            "id": 3,
            "createdTime": null,
            "lastModifiedTime": null,
            "creatorId": null,
            "lastModifierId": null,
            "username": "xiaoyu",
            "password": "xiaoyu",
            "phone": "123654",
            "introduce": "xiaoyu",
            "departmentId": 3
        },
        {
            "id": 4,
            "createdTime": null,
            "lastModifiedTime": null,
            "creatorId": null,
            "lastModifierId": null,
            "username": "ljfi",
            "password": "ljfi",
            "phone": "123654",
            "introduce": "ljfi",
            "departmentId": 4
        }
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "pageSize": 5,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": true,
    "totalPages": 1,
    "totalElements": 4,
    "size": 5,
    "number": 0,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "first": true,
    "numberOfElements": 4,
    "empty": false
}

Spring Data JPA 基于方法名创建查询支持的关键词

数据来源:Spring Data JPA 3.2.2官方文档https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query-methods.query-creation

分类: SpringBoot

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注