Spring Boot 与 Spring Data JPA 操作 MySQL 数据库(二)

Spring Boot 与 Spring Data JPA 操作 MySQL 数据库(二)

I. 引言

A. Spring Boot 和 Spring Data JPA 的简介

Spring Boot 是 Spring Framework 的一种快速开发框架,它通过自动配置、约定优于配置等方式,帮助开发人员快速构建 Spring 应用程序。Spring Data JPA 是 Spring 框架提供的一种简化数据库访问的解决方案,它基于 JPA 规范,提供了一种统一的数据访问方式,支持多种关系型数据库。

B. 目的和意义

本文将介绍如何使用 Spring Boot 和 Spring Data JPA 集成 MySQL 数据库,通过使用 JPA 规范,简化 MySQL 数据库的操作,并使用 Spring Boot 的自动配置功能,进一步简化开发流程。

II. Spring Boot 和 Spring Data JPA 的集成

A. 添加依赖

在 pom.xml 文件中添加 Spring Boot 和 Spring Data JPA 的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

C. 配置数据源

在 application.properties 文件中配置 MySQL 数据库的连接信息:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/my_database?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

B. 使用关联查询

除了基本的 CRUD 操作外,Spring Data JPA 还支持关联查询。假设我们有一个班级表 class,每个学生属于一个班级,那么我们可以通过关联查询找到每个班级对应的学生列表。

1. 创建班级实体类

首先,我们需要创建一个班级实体类,用于映射数据库中的班级表。创建一个 Class 实体类:

@Entity
@Table(name = "class")
public class Class {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @OneToMany(mappedBy = "class")
    private List<Student> students;

    // getters and setters
}

可以看到,Class 实体类中包含一个 @OneToMany 注解,用于指定与 Student 实体类的一对多关联关系,mappedBy 属性指定了映射关系的反向属性名,即 Student 实体类中的 class 属性。

2. 修改学生实体类

接着,我们需要在 Student 实体类中添加一个关联属性 class,用于指定每个学生所属的班级。

@Entity
@Table(name = "student")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(name = "age")
    private Integer age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "class_id")
    private Class clazz;

    // getters and setters
}

可以看到,Student 实体类中包含一个 @ManyToOne 注解,用于指定与 Class 实体类的多对一关联关系,fetch 属性指定了懒加载策略,JoinColumn 属性指定了映射关系的外键字段名。

3. 创建班级仓库接口

接下来,我们需要创建一个班级仓库接口,用于查询班级信息及其关联的学生列表。

@Repository
public interface ClassRepository extends JpaRepository<Class, Long> {

    @Query("SELECT c FROM Class c JOIN FETCH c.students WHERE c.id = :classId")
    Class findByIdWithStudents(@Param("classId") Long classId);
}

可以看到,ClassRepository 接口中包含一个自定义的查询方法 findByIdWithStudents,该方法通过 JOIN FETCH 关键字同时查询班级信息和关联的学生列表。

当我们的关联查询工作正常后,我们可以继续进行分页查询的测试。

C. 分页查询

在实际的应用中,我们经常需要对数据进行分页展示,以避免数据过多导致页面加载过慢,同时也能提供更好的用户体验。在 Spring Data JPA 中,实现分页查询也非常简单。

1. 分页查询的基本使用

我们可以使用 PagingAndSortingRepository 提供的 findAll(Pageable pageable) 方法进行分页查询,其中 Pageable 是 Spring Data JPA 提供的分页参数,我们可以通过它设置需要查询的页码、每页显示的数据条数、排序规则等。具体可以看下面的示例代码:

@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {

}
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;

    public Page<User> getUsersByPage(int pageNum, int pageSize) {
        Pageable pageable = PageRequest.of(pageNum, pageSize, Sort.Direction.ASC, "id");
        return userRepository.findAll(pageable);
    }
}

在上面的示例代码中,我们通过 PageRequest 创建了一个 Pageable 对象,并且设置了需要查询的页码、每页显示的数据条数、排序规则等。然后我们就可以通过 userRepository.findAll(pageable) 方法进行分页查询了。

2. 分页查询的高级用法

除了基本的分页查询外,Spring Data JPA 还提供了一些高级的分页查询方法,我们可以根据自己的需要进行使用。下面是一些常用的高级分页查询方法。

根据条件进行分页查询

在实际的应用中,我们经常需要根据某些条件进行分页查询,可以使用 Specification 进行条件查询,具体可以看下面的示例代码:

public class UserSpecification {
    
    public static Specification<User> ageGreaterThan(int age) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get("age"), age);
    }

    public static Specification<User> usernameLike(String username) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("username"), "%" + username + "%");
    }
}
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;

    public Page<User> getUsersByCondition(int pageNum, int pageSize, int age, String username) {
        Pageable pageable = PageRequest.of(pageNum, pageSize, Sort.Direction.ASC, "id");
        Specification<User> spec = Specification.where(UserSpecification.ageGreaterThan(age)).and(UserSpecification.usernameLike(username));
        return userRepository.findAll(spec, pageable);
    }
}

在上面的示例代码中,我们定义了一个 UserSpecification 类,其中包含了两个静态方法 ageGreaterThanusernameLike,分别用于创建根据年龄和用户名进行查询的 Specification 对象。然后在 UserService 中我们使用 Specification.where 方法将两个 Specification 对象进行组合,最终查询出符合条件的用户数据。

D. 自定义分页查询结果的映射

当我们进行分页查询时,Spring Data JPA默认会返回一个Page对象,其中包含了当前页的记录以及分页信息,如总页数、当前页数、每页大小等。但是,有时候我们需要自定义返回结果,比如只返回当前页的记录,不返回总页数等信息。这时,我们可以使用Spring Data JPA提供的投影(Projection)功能来自定义返回结果。

投影是Spring Data JPA中非常强大的一个功能,它可以帮助我们自定义查询结果的结构和内容。在分页查询中,我们可以使用投影来自定义返回结果,只返回我们需要的数据,而不返回分页信息。

投影有两种方式:接口投影和类投影。接口投影是定义一个接口,并在接口中定义需要返回的属性,Spring Data JPA会根据接口的定义自动生成查询语句并返回结果。类投影是定义一个类,并在类中定义需要返回的属性,同时还需要使用构造函数或者@Value注解来对属性进行赋值。

下面我们以接口投影为例,演示如何自定义分页查询结果的映射。

  1. 定义投影接口

我们先定义一个投影接口,用来返回需要的属性,如下所示:

public interface UserProjection {
    String getUsername();
    String getEmail();
}

在上面的代码中,我们定义了一个名为UserProjection的接口,包含了getUsername()和getEmail()两个方法,这两个方法返回我们需要返回的属性。注意,方法名必须与属性名对应,否则Spring Data JPA会在查询时出错。

  1. 修改仓库接口

接下来我们需要修改仓库接口,让它返回UserProjection接口的实例而不是实体类的实例。修改后的代码如下所示:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Page<UserProjection> findByUsernameContaining(String username, Pageable pageable);
}

在上面的代码中,我们将返回值类型改为Page,表示返回的是一个UserProjection接口的分页结果。

  1. 使用投影查询

最后,我们可以像之前一样使用分页查询方法进行查询,如下所示:

Page<UserProjection> users = userRepository.findByUsernameContaining("john", PageRequest.of(0, 10));

在上面的代码中,我们调用了findByUsernameContaining()方法进行分页查询,返回的是UserProjection接口的分页结果。我们可以直接使用users.getContent()方法来获取当前页的数据列表,而不用管其他分页信息。

至此,我们已经成功地使用投影来自定义分页查询结果的映射了。当然,这只是投影功能的冰山一角,它还有很多其他的用法和技巧,可以根据实际情况进行探索。

IV. Spring Boot和Spring Data JPA的优化

Spring Data JPA是一个很强大的ORM框架,它能帮我们快速的开发数据访问层,但是在实际项目中,为了获得更好的性能和更好的可维护性,我们还需要对Spring Data JPA进行一些优化。

A. 缓存

缓存是提高系统性能的重要手段之一,通过缓存可以减少数据库访问次数,从而提高系统响应速度。在Spring Data JPA中,缓存是默认开启的,它使用的是Hibernate内置的缓存机制。

Hibernate缓存机制分为一级缓存和二级缓存。一级缓存是Session级别的缓存,二级缓存是SessionFactory级别的缓存。在Spring Data JPA中,默认情况下使用的是一级缓存,它可以减少对数据库的访问次数,但是它的作用域只在当前Session内,所以多个Session之间无法共享缓存数据。如果想要在多个Session之间共享缓存数据,就需要使用二级缓存。

为了使用二级缓存,需要在实体类上添加注解@Cacheable@Cache@Cacheable注解用来标识这个实体类是可缓存的,@Cache注解用来指定缓存的策略和缓存的名称。

@Entity
@Table(name = "book")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "bookCache")
public class Book {
    ...
}

上面的代码中,@Cacheable注解用来标识这个实体类是可缓存的,@Cache注解用来指定缓存的策略和缓存的名称。其中,usage属性用来指定缓存的策略,它有四个选项:READ_ONLYNONSTRICT_READ_WRITEREAD_WRITETRANSACTIONAL,分别表示只读缓存、非严格读写缓存、读写缓存和事务缓存。region属性用来指定缓存的名称,它可以为每个实体类指定不同的缓存名称。

或者采用另一种缓存方式:Ehcache

1. 添加依赖

首先,我们需要添加缓存依赖。在 Maven 中,我们可以添加以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2. 配置缓存

然后,我们需要在应用程序的配置文件中配置缓存。在本例中,我们使用 Ehcache 作为缓存提供程序。我们需要添加以下配置:

spring:
  cache:
    type: ehcache
3. 在仓库中使用缓存

最后,我们需要在仓库中启用缓存。在 Spring Data JPA 中,我们可以使用 @Cacheable@CacheEvict 注解启用和清除缓存。例如:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
 
    @Cacheable("users")
    User findByUsername(String username);
 
    @CacheEvict(value = "users", allEntries = true)
    void evictCache();
 
}

在上面的示例中,@Cacheable 注解将 findByUsername 方法的结果缓存起来,缓存的名称为 “users”。当需要清除缓存时,我们可以使用 @CacheEvict 注解清除缓存。在本例中,我们使用 allEntries = true 参数来清除所有缓存条目。

B. 多数据源

在实际项目中,经常会遇到需要连接多个数据库的情况,这时候就需要使用多数据源功能。在Spring Boot中,使用多数据源也非常简单,只需要定义多个数据源的Bean,并在使用的时候指定数据源即可。

当配置多数据源时,我们需要为每个数据源创建自己的 EntityManagerFactoryTransactionManager。在Spring Boot中,我们可以使用@Primary注解来指定默认的数据源。在本例中,我们将使用名为primaryDataSource的默认数据源和名为secondaryDataSource的第二个数据源。

为了使用多个数据源,我们需要在application.properties文件中定义每个数据源的配置。以下是一个示例配置:

# primary datasource configuration
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/primary_db
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

# secondary datasource configuration
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/secondary_db
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

现在我们需要创建两个DataSource bean来表示每个数据源。我们可以使用@ConfigurationProperties注解和@Bean注解来创建这些bean。以下是示例代码:

@Configuration
public class DataSourceConfig {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

}

我们使用@Primary注解来指定primaryDataSource作为默认数据源。这将确保Spring使用默认数据源进行事务管理和实体管理。

接下来,我们需要为每个数据源配置EntityManagerFactoryTransactionManager。我们可以使用LocalContainerEntityManagerFactoryBeanJpaTransactionManager类来实现这一点。以下是示例代码:

@Configuration
@EnableTransactionManagement
public class JpaConfig {

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("primaryDataSource") DataSource dataSource) {
        return builder.dataSource(dataSource)
                .packages("com.example.primary.entity")
                .persistenceUnit("primaryPU")
                .build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("secondaryDataSource") DataSource dataSource) {
        return builder.dataSource(dataSource)
                .packages("com.example.secondary.entity")
                .persistenceUnit("secondaryPU")
                .build();
    }

    @Primary
    @Bean
    public JpaTransactionManager primaryTransactionManager(
            @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

    @Bean
    public JpaTransactionManager secondaryTransactionManager(
            @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

}

我们使用EntityManagerFactoryBuilder类创建LocalContainerEntityManagerFactoryBean bean。使用packages方法指定实体类所在的包,使用persistenceUnit方法指定持久化单元名称。在这里,我们分别为每个数据源配置了一个实体管理器工厂。

  1. 在这个示例中,我们为每个数据源都定义了一个事务管理器。对于主数据源primaryDataSource,我们使用@Primary注解将其标注为默认数据源,并且为它定义了一个名为primaryTransactionManager的事务管理器;对于次数据源secondaryDataSource,我们为它定义了一个名为secondaryTransactionManager的事务管理器。

  2. 在定义事务管理器的过程中,我们还需要使用@Qualifier注解来指定对应的EntityManagerFactory。例如,对于主数据源,我们使用@Qualifier(“primaryEntityManagerFactory”)来指定使用名为primaryEntityManagerFactory的EntityManagerFactory,对于次数据源,我们使用@Qualifier(“secondaryEntityManagerFactory”)来指定使用名为secondaryEntityManagerFactory的EntityManagerFactory。

  3. 最后,我们需要在使用事务注解的方法上添加@Transactional注解。默认情况下,Spring会使用默认的事务管理器,也就是使用@Primary注解标注的那个事务管理器。如果我们需要使用其他数据源的事务管理器,我们可以在@Transactional注解中指定对应的事务管理器的名称。例如:

@Transactional("secondaryTransactionManager")
public void saveUser(User user) {
    entityManager.persist(user);
}

在这个示例中,我们使用@Transactional注解来标注saveUser方法,指定使用名为secondaryTransactionManager的事务管理器。这样,在保存用户的时候,就会使用次数据源的事务管理器来进行事务控制。

当然,使用Hibernate提供的二级缓存也需要配置,Spring Boot默认使用Hibernate的二级缓存,可通过配置文件修改为使用Ehcache等其他缓存提供商的实现。

C. JPA的性能调优

为了优化JPA的性能,我们需要了解一些优化技巧:

  1. 避免N+1查询问题:N+1查询问题是指一个查询N个实体对象,又执行了N次额外的SQL查询,造成不必要的性能开销。解决这个问题的方法是使用Fetch策略,一次性获取所有需要的数据。

  2. 使用缓存:缓存可以避免重复的数据库访问,提高查询效率。Hibernate提供了一级缓存和二级缓存,可以根据需要进行选择和配置。

  3. 懒加载:懒加载是指在需要使用某个属性或关联对象时才进行加载,避免不必要的数据读取和查询。使用懒加载可以有效地提高查询效率和减少内存开销。

  4. 选择合适的数据源:根据应用程序的特点和需求,选择合适的数据源和数据库引擎。例如,对于读多写少的场景,可以选择使用MySQL Cluster等支持高可用的数据库。

  5. 避免使用复杂查询:尽量避免使用复杂的查询语句,这样可以减少数据库的开销,提高查询效率。

  6. 使用数据库索引:根据应用程序的需求和查询模式,使用数据库索引可以加速查询效率。但是过多的索引会增加数据库写入的开销,也会占用额外的存储空间。

  7. 避免使用Hibernate的动态代理:Hibernate的动态代理机制可以实现懒加载和缓存等功能,但是也会带来额外的性能开销和内存开销。在某些场景下,可以选择关闭Hibernate的动态代理机制,手动控制加载和缓存等功能。

结论

本文介绍了Spring Boot和Spring Data JPA的融合,包括集成、使用示例和优化等方面。通过使用Spring Boot和Spring Data JPA,可以快速构建Web应用程序,并实现数据持久化和查询等功能。同时,本文还介绍了一些JPA的性能优化技巧,可以帮助开发人员进一步提高查询效率和减少资源占用。Spring Boot和Spring Data JPA是Java开发人员的重要工具,具有广泛的应用前景和市场需求。我们相信,通过本文的学习和实践,您可以更好地掌握Spring Boot和Spring Data JPA的技术特点和优势,从而更好地应对实际的开发需求和挑战。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值