Java高级全套教程(十四)—— SpringData超详细实战详解

Java高级全套教程(十四)------ SpringData超详细实战详解

本章学习目标

本章为SpringData全套实战核心章节,聚焦SpringData各大主流模块的原理、配置、实战开发,摒弃繁琐理论堆砌,以原理吃透+代码实战+场景落地为核心,各知识点掌握要求如下:

知识点模块 掌握层级 学习要求说明
Spring Data 核心概念 了解 知晓SpringData定位、核心优势、模块分类及适用场景
Spring Data JPA 精通掌握 掌握项目搭建、多种查询方式、底层原理、DDD领域驱动设计实战
Spring Data JDBC 精通掌握 掌握轻量ORM开发、JdbcTemplate工具使用、MyBatis集成实战
Spring Data Redis 精通掌握 掌握序列化配置、五大数据类型实操、缓存业务场景开发
Spring Data ElasticSearch 精通掌握 掌握ES核心概念、索引操作、全文检索、分页排序、复杂条件查询
Spring Data MongoDB 精通掌握 掌握文档型数据库配置、CRUD、分页、条件查询实战
Spring Data Solr 基础了解 了解核心概念、现状、基础使用,无需深度开发
SpringData 综合实战案例 精通掌握 整合多模块实现企业级电商数据分层存储架构

一、Spring Data 核心概述

1.1 框架定位与核心优势

Spring Data是Spring生态专为数据持久化打造的开源子项目,核心目标是统一Java程序对接各类存储中间件的开发规范,解决传统开发中不同数据库、缓存、搜索引擎API不统一、代码冗余、重复造轮子的问题。

传统开发中,对接MySQL需写JDBC/MyBatis代码、对接Redis需整合Jedis、对接ES需手写原生API,技术栈杂乱;而SpringData提供统一的Repository编程模型,一套编码风格适配所有主流存储组件,极大提升开发效率。

其核心优势可总结为三点:

  • 统一API规范:所有模块均基于Repository接口实现,CRUD操作语法一致,降低学习和维护成本

  • 零冗余代码:无需手动编写SQL/模板代码,通过接口命名、注解即可实现绝大多数数据操作

  • 生态兼容性强:完美适配SpringBoot,自动配置、开箱即用,支持自定义拓展和原生API混用

1.2 核心模块适配场景

SpringData通过不同子模块适配差异化存储场景,企业常用模块及适用场景如下:

  • Spring Data JPA:适配关系型数据库(MySQL、Oracle),主打ORM对象映射,适合传统业务数据表开发

  • Spring Data JDBC:轻量级关系型数据库操作框架,无缓存、无延迟加载,适合简单、高性能的数据库操作场景

  • Spring Data Redis:适配Redis缓存中间件,实现内存数据读写,用于热点数据缓存、分布式锁、限流等场景

  • Spring Data MongoDB:适配MongoDB文档数据库,适合存储非结构化、海量低价值数据(评论、日志、用户行为数据)

  • Spring Data ElasticSearch:适配ES搜索引擎,用于全文检索、模糊查询、海量数据快速检索场景

  • Spring Data Solr:老旧搜索引擎适配模块,官方已停止维护,仅做技术了解,新项目优先使用ES

前置环境要求:本教程所有实战案例基于SpringBoot 2.7.x版本开发,JDK版本1.8及以上,需掌握SpringBoot基础注解、依赖引入、配置文件编写基础。

二、Spring Data JPA 实战详解(核心重点)

2.1 核心概念与技术定位

Spring Data JPA是对JPA官方规范 的高阶封装,底层基于Hibernate实现,彻底简化了传统JPA繁琐的API操作。相较于MyBatis的SQL手动编写模式,JPA以对象驱动为核心,通过操作实体类对象即可完成数据库CRUD,无需手写基础SQL,适合快速迭代的业务项目。

2.2 JPA与主流ORM框架对比

JPA是Java官方ORM规范,仅定义接口标准,无具体实现;主流实现方案为Hibernate、EclipseLink。与MyBatis的核心差异如下:

  • 编程模式:MyBatis面向SQL,手动编写语句;JPA面向对象,通过注解映射表关系

  • 多表操作:MyBatis手动关联多表查询;JPA通过@OneToMany、@ManyToOne注解配置对象关系,自动关联查询

  • 开发效率:JPA基础CRUD零代码,效率更高;MyBatis灵活度更高,适配复杂SQL场景

2.3 项目从零搭建实战

2.3.1 引入核心依赖

新建SpringBoot项目,在pom.xml中引入JPA核心依赖、MySQL驱动,JDK9及以上需补充jaxb依赖,完整依赖如下:

xml 复制代码
<dependencies>
    <!-- SpringData JPA 核心起步依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;

    <!-- MySQL8.0驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
        <scope>runtime</scope>
    &lt;/dependency&gt;

    <!-- JDK9+ 必备jaxb依赖 -->
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>
</dependencies>

2.3.2 全局配置文件配置

在application.yml中配置数据源、JPA参数,实现自动建表、SQL打印、字段映射适配,完整配置如下:

yaml 复制代码
spring:
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jpa_demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    username: root
    password: root
  # JPA核心配置
  jpa:
    database: mysql
    # 控制台打印执行的SQL语句
    show-sql: true
    # 开启自动建表
    generate-ddl: true
    hibernate:
      # 自动更新表结构,新增字段不删除原有数据
      ddl-auto: update
    # 解决驼峰命名与数据库下划线字段不匹配问题
    naming:
      physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

2.3.3 创建业务实体类

以用户业务为例,创建User实体类,通过JPA注解完成表和字段映射:

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "sys_user")
public class User {
    // 主键自增
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 用户名
    @Column(name = "user_name", nullable = false, length = 32)
    private String userName;

    // 用户手机号
    @Column(name = "phone", unique = true, length = 11)
    private String phone;

    // 用户年龄
    @Column(name = "age")
    private Integer age;

    // 创建时间
    @Column(name = "create_time")
    private Date createTime;

    // 用户状态 0-禁用 1-正常
    @Column(name = "status")
    private Integer status;
}

2.3.4 创建持久层Repository接口

自定义Repository接口,继承两大核心父接口,自动获得CRUD、复杂查询、分页排序能力:

java 复制代码
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

/**
 * JPA持久层接口
 * JpaRepository:提供基础CRUD、分页、排序
 * JpaSpecificationExecutor:提供动态条件、复杂查询
 */
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    // 后续自定义查询方法
}

2.3.5 基础CRUD测试

编写单元测试类,测试新增、修改、删除、查询等基础操作:

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.Optional;

@SpringBootTest
public class UserJpaTest {

    @Autowired
    private UserRepository userRepository;

    // 新增用户
    @Test
    void testSaveUser(){
        User user = new User();
        user.setUserName("张三");
        user.setPhone("13800138000");
        user.setAge(25);
        user.setCreateTime(new Date());
        user.setStatus(1);
        // save方法:主键不存在则新增,存在则修改
        userRepository.save(user);
    }

    // 根据ID查询用户
    @Test
    void testGetUserById(){
        Optional<User> userOptional = userRepository.findById(1L);
        // 优雅获取数据,避免空指针
        userOptional.ifPresent(user -> System.out.println("查询用户:" + user));
    }

    // 查询所有用户
    @Test
    void testListUser(){
        userRepository.findAll().forEach(System.out::println);
    }

    // 删除用户
    @Test
    void testDeleteUser(){
        userRepository.deleteById(1L);
    }
}

2.4 Spring Data JPA 底层执行原理

SpringData JPA无需编写接口实现类,核心依托AOP动态代理机制实现,完整执行链路如下:

  1. 程序启动时,Spring扫描所有继承JpaRepository的自定义接口

  2. 通过JdkDynamicAopProxy动态生成接口的代理对象,底层实现类为SimpleJpaRepository

  3. SimpleJpaRepository封装了所有CRUD方法,内部调用JPA核心API------EntityManager

  4. EntityManager的操作由Hibernate实现,Hibernate最终调用底层JDBC完成数据库交互

核心核心逻辑:自定义Repository接口 → 动态代理 → SimpleJpaRepository实现类 → EntityManager → Hibernate → JDBC → 数据库

2.5 五大查询方式实战

2.5.1 自带API查询

JpaRepository内置大量通用查询方法,无需自定义,直接调用即可:

java 复制代码
@Test
void testBaseQuery(){
    // 统计总记录数
    long count = userRepository.count();
    System.out.println("用户总数:" + count);

    // 判断ID是否存在
    boolean exists = userRepository.existsById(2L);
    System.out.println("用户是否存在:" + exists);

    // 延迟加载查询(需事务支持)
    @Transactional
    User user = userRepository.getOne(2L);
    System.out.println(user.getUserName());
}

2.5.2 JPQL查询(面向对象查询)

JPQL是JPA专属查询语句,语法类似SQL,但是基于实体类和属性查询,非数据库表和字段,通过@Query注解实现:

在UserRepository中自定义方法:

java 复制代码
// 根据用户名和状态查询用户
@Query("select u from User u where u.userName = ?1 and u.status = ?2")
List<User> findUserByUserNameAndStatus(String userName, Integer status);

// 修改用户状态(增删改需加@Modifying)
@Modifying
@Query("update User u set u.status = ?2 where u.id = ?1")
int updateUserStatus(Long id, Integer status);

测试方法:

java 复制代码
@Test
@Transactional
@Rollback(false) // 关闭自动回滚,保证数据生效
void testJpql(){
    // 查询测试
    List<User> userList = userRepository.findUserByUserNameAndStatus("张三",1);
    userList.forEach(System.out::println);

    // 修改测试
    int rows = userRepository.updateUserStatus(2L,0);
    System.out.println("修改行数:" + rows);
}

2.5.3 原生SQL查询

复杂场景可直接使用原生SQL,设置nativeQuery=true即可:

java 复制代码
// 原生SQL根据手机号查询用户
@Query(value = "select * from sys_user where phone = ?1",nativeQuery = true)
User findUserByPhone(String phone);

2.5.4 方法命名规则查询

SpringData JPA支持通过方法名自动生成查询SQL,无需注解,遵循固定命名规则(findBy+属性+条件关键字),常用规则实战:

java 复制代码
// 根据年龄区间查询
List<User> findByAgeBetween(Integer minAge, Integer maxAge);

// 根据用户名模糊查询
List<User> findByUserNameLike(String userName);

// 查询状态正常且年龄大于20的用户
List<User> findByStatusAndAgeGreaterThan(Integer status, Integer age);

// 根据手机号为空查询
List<User> findByPhoneIsNull();

2.5.5 动态条件查询(JpaSpecificationExecutor)

适用于多条件动态组合查询(条件不确定、动态拼接),通过Specification构建查询条件,支持分页、排序:

java 复制代码
@Test
void testSpecQuery(){
    // 1.构建动态条件
    Specification<User> spec = (root, query, cb) -> {
        List<Predicate> predicateList = new ArrayList<>();
        // 拼接状态条件:状态为正常
        predicateList.add(cb.equal(root.get("status"),1));
        // 拼接年龄条件:年龄大于20
        predicateList.add(cb.greaterThan(root.get("age"),20));
        return cb.and(predicateList.toArray(new Predicate[0]));
    };

    // 2.构建分页参数:第0页,每页5条
    Pageable pageable = PageRequest.of(0,5);
    // 3.执行分页查询
    Page<User> page = userRepository.findAll(spec,pageable);

    System.out.println("总条数:" + page.getTotalElements());
    System.out.println("总页数:" + page.getTotalPages());
    page.getContent().forEach(System.out::println);
}

2.6 DDD领域驱动设计实战

传统数据驱动开发需单独操作每张表,代码冗余、业务耦合度高;SpringData JPA完美适配DDD领域驱动设计,通过聚合根统一管理关联实体,一次性完成多表操作。

以「订单-订单详情」一对多业务为例实战:

2.6.1 聚合实体类创建

Order为聚合根,统一管理OrderDetail订单详情:

java 复制代码
// 订单聚合根实体
@Data
@Entity
@Table(name = "biz_order")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String orderNo; // 订单编号
    private String receiverPhone; // 收件手机号
    private Date createTime;

    // 一对多关联订单详情,级联新增
    @OneToMany(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "order_id")
    private List<OrderDetail> detailList = new ArrayList<>();
}

// 订单详情实体
@Data
@Entity
@Table(name = "biz_order_detail")
public class OrderDetail {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long productId; // 商品ID
    private Integer productNum; // 购买数量
    private Double productPrice; // 商品单价
}

2.6.2 聚合根持久层接口

java 复制代码
@Repository
public interface OrderRepository extends JpaRepository<Order,Long> {
}

2.6.3 聚合业务实操

仅操作聚合根,即可一次性保存订单和订单详情,无需单独操作详情表:

java 复制代码
@Test
void testDDDSave(){
    // 1.创建订单主表数据
    Order order = new Order();
    order.setOrderNo("ORD20260604001");
    order.setReceiverPhone("13900139000");
    order.setCreateTime(new Date());

    // 2.创建订单详情数据
    OrderDetail detail1 = new OrderDetail();
    detail1.setProductId(1001L);
    detail1.setProductNum(2);
    detail1.setProductPrice(99.9);

    OrderDetail detail2 = new OrderDetail();
    detail2.setProductId(1002L);
    detail2.setProductNum(1);
    detail2.setProductPrice(199.9);

    // 3.关联聚合数据
    order.getDetailList().add(detail1);
    order.getDetailList().add(detail2);

    // 4.仅保存聚合根,自动级联保存详情
    orderRepository.save(order);
}

三、Spring Data JDBC 实战详解

3.1 核心概念与定位

Spring Data JDBC是SpringData推出的轻量级ORM框架,相较于JPA更简洁、轻量化,舍弃了JPA的缓存、延迟加载、复杂关联等重型功能,专注于简单、高性能的数据库操作,同时支持集成MyBatis,兼顾简洁性与灵活性。

核心特点:无缓存、无延迟加载、上手简单、执行效率高、支持原生SQL、可集成MyBatis拓展功能。

3.2 项目搭建实战

3.2.1 核心依赖引入

xml 复制代码
&lt;dependencies&gt;
    <!-- SpringData JDBC核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc&lt;/artifactId&gt;
    &lt;/dependency&gt;
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

3.2.2 数据源配置

配置方式与JPA一致,无需额外JPA相关配置:

yaml 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jdbc_demo?serverTimezone=GMT%2B8&useUnicode=true
    username: root
    password: root

3.2.3 实体类创建

Spring Data JDBC实体类不支持@Entity、@GeneratedValue等JPA注解,仅需普通实体映射字段:

java 复制代码
import lombok.Data;

@Data
public class Student {
    private Integer id;
    private String studentName;
    private String studentNo;
    private Integer gender;
    private String className;
}

3.2.4 持久层接口创建

继承CrudRepository(基础CRUD)、PagingAndSortingRepository(分页排序):

java 复制代码
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends CrudRepository<Student,Integer>, PagingAndSortingRepository<Student,Integer> {
    // 命名规则查询:根据班级查询学生
    List<Student> findByClassName(String className);
}

3.3 双模式查询实战

3.3.1 Repository 接口查询

依托继承的接口实现零代码CRUD、分页、命名查询:

java 复制代码
@Test
void testRepositoryQuery(){
    // 新增
    Student student = new Student();
    student.setStudentName("李四");
    student.setStudentNo("2026060401");
    student.setGender(1);
    student.setClassName("高三一班");
    studentRepository.save(student);

    // 分页查询
    Pageable pageable = PageRequest.of(0,3);
    Page<Student> page = studentRepository.findAll(pageable);
    page.getContent().forEach(System.out::println);

    // 自定义命名查询
    List<Student> studentList = studentRepository.findByClassName("高三一班");
    studentList.forEach(System.out::println);
}

3.3.2 JdbcTemplate 原生查询

Spring自动注入JdbcTemplate工具类,用于复杂SQL场景,灵活可控:

java 复制代码
@Autowired
private JdbcTemplate jdbcTemplate;

// 查询单行数据
@Test
void testQuerySingle(){
    String sql = "select * from student where id = ?";
    Student student = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Student.class),1);
    System.out.println(student);
}

// 查询多行数据
@Test
void testQueryList(){
    String sql = "select * from student where class_name = ?";
    List<Student> studentList = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Student.class),"高三一班");
    studentList.forEach(System.out::println);
}

// 执行更新
@Test
void testUpdate(){
    String sql = "update student set student_name = ? where id = ?";
    int rows = jdbcTemplate.update(sql,"李小明",1);
    System.out.println("修改行数:" + rows);
}

3.4 Spring Data JDBC 集成 MyBatis 实战

JDBC模块原生功能简单,可集成MyBatis拓展缓存、复杂SQL能力,完整集成步骤如下:

3.4.1 引入MyBatis依赖

xml 复制代码
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

3.4.2 定义拓展查询接口

java 复制代码
public interface StudentExtRepository {
    // 自定义复杂查询:查询所有男生
    List<Student> listMaleStudent();
}

3.4.3 编写MyBatis映射文件

在resources/mapper下创建StudentMapper.xml:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.repository.StudentExtRepository">
    <select id="listMaleStudent" resultType="com.demo.entity.Student">
        select * from student where gender = 1
    </select>
</mapper>

3.4.4 实现拓展接口

java 复制代码
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public class StudentExtRepositoryImpl implements StudentExtRepository {

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    @Override
    public List<Student> listMaleStudent() {
        return sqlSessionTemplate.selectList(StudentExtRepository.class.getName() + ".listMaleStudent");
    }
}

3.4.5 主接口继承拓展接口

java 复制代码
@Repository
public interface StudentRepository extends CrudRepository<Student,Integer>,
        PagingAndSortingRepository<Student,Integer>, StudentExtRepository {
}

3.4.6 配置Mapper扫描

yaml 复制代码
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

3.5 JDBC与JPA核心对比

对比维度 Spring Data JPA Spring Data JDBC
框架重量 重型框架,依赖Hibernate 轻量框架,无额外依赖
核心功能 支持缓存、延迟加载、JPQL、级联操作 无缓存、无延迟加载、仅基础CRUD
SQL灵活性 复杂SQL支持较弱 支持JdbcTemplate,可手写任意SQL
拓展能力 原生功能齐全,无需拓展 可集成MyBatis拓展功能
适用场景 业务复杂、多表关联、快速开发 简单单表操作、高性能查询场景

四、Spring Data Redis 实战详解

4.1 核心概念

Redis是高性能内存键值数据库,支持String、Hash、List、Set、ZSet五大核心数据类型,常用于热点数据缓存、分布式锁、限流、计数器等场景。Spring Data Redis是Spring对Redis原生客户端(Jedis、Lettuce)的高度封装,提供统一、简洁的API操作Redis。

4.2 项目搭建与基础配置

4.2.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.2.2 全局配置

yaml 复制代码
spring:
  redis:
    # Redis服务地址
    host: localhost
    # Redis端口
    port: 6379
    # 连接池配置
    jedis:
      pool:
        max-active: 10  # 最大活跃连接
        max-idle: 8     # 最大空闲连接
        min-idle: 2     # 最小空闲连接
        max-wait: 1000  # 最大等待时间
    # 超时时间
    timeout: 3000ms

4.3 序列化器配置(核心优化)

SpringData Redis默认使用JDK序列化,存储数据为二进制乱码,可读性极差。实战中统一配置JSON序列化器,实现数据可视化存储:

java 复制代码
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // String类型key序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);

        // JSON类型value序列化
        Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jsonSerializer.setObjectMapper(mapper);

        redisTemplate.setValueSerializer(jsonSerializer);
        redisTemplate.setHashValueSerializer(jsonSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

4.4 五大数据类型完整实战

4.4.1 String 字符串操作

java 复制代码
@Autowired
private RedisTemplate<String,Object> redisTemplate;

@Test
void testStringOperate(){
    ValueOperations<String,Object> valueOps = redisTemplate.opsForValue();
    // 普通存储
    valueOps.set("project_name","SpringData实战教程");
    // 过期存储(10秒过期)
    valueOps.set("temp_code","123456",10, TimeUnit.SECONDS);
    // 自增计数器
    valueOps.increment("view_count",1);
    // 批量存储
    Map<String,Object> map = new HashMap<>();
    map.put("author","java高级");
    map.put("level","高级教程");
    valueOps.multiSet(map);

    // 数据获取
    System.out.println(valueOps.get("project_name"));
    System.out.println(valueOps.get("view_count"));
}

4.4.2 Hash 哈希操作

适合存储对象数据,无需序列化整存整取,支持单独修改字段:

java 复制代码
@Test
void testHashOperate(){
    HashOperations<String,String,Object> hashOps = redisTemplate.opsForHash();
    // 存储用户信息
    hashOps.put("user:info:1001","name","王五");
    hashOps.put("user:info:1001","age",28);
    hashOps.put("user:info:1001","phone","13700137000");

    // 获取单个字段
    System.out.println(hashOps.get("user:info:1001","name"));
    // 获取所有字段和值
    System.out.println(hashOps.entries("user:info:1001"));
    // 删除单个字段
    hashOps.delete("user:info:1001","phone");
}

4.4.3 List 列表操作

有序可重复列表,适合消息队列、时间线数据存储:

java 复制代码
@Test
void testListOperate(){
    ListOperations<String,Object> listOps = redisTemplate.opsForList();
    // 左侧插入
    listOps.leftPush("news:list","头条新闻1");
    listOps.leftPushAll("news:list","头条新闻2","头条新闻3");
    // 右侧插入
    listOps.rightPush("news:list","最新资讯");

    // 范围查询
    List<Object> newsList = listOps.range("news:list",0,-1);
    System.out.println(newsList);
    // 左侧弹出
    System.out.println(listOps.leftPop("news:list"));
}

4.4.4 Set 集合操作

无序不可重复集合,适合去重、交集、并集场景(好友、点赞用户):

java 复制代码
@Test
void testSetOperate(){
    SetOperations<String,Object> setOps = redisTemplate.opsForSet();
    // 添加数据
    setOps.add("user:like:1001","1002","1003","1004");
    setOps.add("user:like:1002","1003","1005","1006");

    // 交集(共同点赞用户)
    Set<Object> intersect = setOps.intersect("user:like:1001", "user:like:1002");
    System.out.println("共同好友:" + intersect);
    // 随机获取
    System.out.println(setOps.randomMember("user:like:1001"));
}

4.4.5 ZSet 有序集合操作

带权重有序集合,适合排行榜、积分排序场景:

java 复制代码
@Test
void testZSetOperate(){
    ZSetOperations<String,Object> zSetOps = redisTemplate.opsForZSet();
    // 添加排行榜数据(value、score积分)
    zSetOps.add("rank:list","张三",98);
    zSetOps.add("rank:list","李四",95);
    zSetOps.add("rank:list","王五",100);

    // 正序查询(分数从低到高)
    Set<Object> rankList = zSetOps.range("rank:list",0,-1);
    System.out.println("排行榜:" + rankList);
    // 加分
    zSetOps.incrementScore("rank:list","李四",3);
}

五、Spring Data ElasticSearch 实战详解

5.1 ES核心概念与对比

ElasticSearch是分布式全文检索引擎,底层基于Lucene实现,支持海量数据快速检索、模糊查询、分词检索,是企业主流搜索引擎方案。Spring Data ElasticSearch简化ES原生繁琐的REST API操作,提供Repository简化开发。

ES7.X核心概念与MySQL映射关系:

  • 索引(Index) ≈ MySQL数据表(Table)

  • 文档(Document) ≈ MySQL数据行(Row)

  • 字段(Field) ≈ MySQL字段(Column)

5.2 环境准备与项目搭建

本地启动ES、Kibana服务,默认端口9200、5601,开启跨域配置保证访问正常。

5.2.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

5.2.2 服务配置

yaml 复制代码
spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200

5.2.3 创建ES文档实体

配置索引、字段类型、分词器,实现文档映射:

java 复制代码
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import lombok.Data;

@Data
@Document(indexName = "goods_index",shards = 3,replicas = 1)
public class GoodsDoc {
    // 文档主键
    @Field(type = FieldType.Long)
    private Long id;

    // 商品名称(分词检索)
    @Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_smart")
    private String goodsName;

    // 商品描述
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String goodsDesc;

    // 商品价格(不分词)
    @Field(type = FieldType.Double,index = false)
    private Double price;

    // 商品分类
    @Field(type = FieldType.Keyword)
    private String category;
}

5.2.4 持久层接口

java 复制代码
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface GoodsEsRepository extends ElasticsearchRepository<GoodsDoc,Long> {
    // 模糊分词查询
    List<GoodsDoc> findByGoodsNameLike(String goodsName);
}

5.3 核心查询实战

5.3.1 基础CRUD与分页查询

java 复制代码
@Test
void testEsBaseOperate(){
    // 新增文档
    GoodsDoc goods = new GoodsDoc();
    goods.setId(1001L);
    goods.setGoodsName("华为Mate60智能手机");
    goods.setGoodsDesc("全网通5G、鸿蒙系统、超长续航");
    goods.setPrice(4999.9);
    goods.setCategory("手机数码");
    goodsEsRepository.save(goods);

    // 分页查询
    Pageable pageable = PageRequest.of(0,5);
    Page<GoodsDoc> page = goodsEsRepository.findAll(pageable);
    page.getContent().forEach(System.out::println);
}

5.3.2 原生复杂条件查询(Template)

java 复制代码
@Autowired
private ElasticsearchRestTemplate esTemplate;

@Test
void testEsComplexQuery(){
    // 构建布尔查询
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 精准匹配分类
    boolQuery.filter(QueryBuilders.termQuery("category","手机数码"));
    // 模糊匹配商品名称
    boolQuery.must(QueryBuilders.fuzzyQuery("goodsName","华为").maxExpansions(2));
    // 价格区间
    boolQuery.filter(QueryBuilders.rangeQuery("price").gte(3000).lte(6000));

    // 分页+排序
    Pageable pageable = PageRequest.of(0,3, Sort.by(Sort.Direction.DESC,"price"));
    NativeSearchQuery query = new NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .withPageable(pageable)
            .build();

    // 执行查询
    SearchHits<GoodsDoc> searchHits = esTemplate.search(query, GoodsDoc.class);
    searchHits.forEach(hit -> System.out.println(hit.getContent()));
}

六、Spring Data MongoDB 实战详解

6.1 核心概念与应用场景

MongoDB是一款开源的分布式文档型NoSQL数据库,数据存储格式为BSON(类JSON格式),无需预先定义表结构,支持动态增减字段,具备高可用、高并发、海量存储的特性。

Spring Data MongoDB是Spring生态针对MongoDB专门封装的持久化框架,延续SpringData统一的Repository编程模型,彻底屏蔽MongoDB原生复杂的API调用,实现零冗余代码开发。

核心适用场景:存储非结构化/半结构化数据、用户行为日志、商品评论、动态表单数据、海量低价值业务数据,不适合金融交易、订单对账等强事务性业务场景。

MongoDB与MySQL核心概念映射:

  • 数据库(Database) ≈ MySQL数据库

  • 集合(Collection) ≈ MySQL数据表

  • 文档(Document) ≈ MySQL数据行

  • 字段(Field) ≈ MySQL列字段

6.2 项目搭建与环境配置

6.2.1 引入核心依赖

在现有SpringBoot项目中引入MongoDB专用starter依赖,无需额外驱动包,开箱即用:

xml 复制代码
<!-- Spring Data MongoDB 核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

6.2.2 全局配置文件

配置MongoDB服务地址、数据库名称、连接超时、连接池参数,适配本地及线上环境:

yaml 复制代码
spring:
  data:
    mongodb:
      # MongoDB连接地址与数据库名
      uri: mongodb://localhost:27017/mongodb_demo
      # 连接超时时间
      timeout: 5000ms
      # 连接池配置
      pool:
        max-size: 15
        min-size: 3
        max-wait: 2000ms

6.3 文档实体类创建

MongoDB无固定表结构,通过注解绑定集合、设置主键、字段映射,支持动态字段扩展。以用户评论业务为例创建实体类:

java 复制代码
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoId;
import java.util.Date;

/**
 * 商品评论文档实体
 * 注解Document:绑定MongoDB集合名称
 */
@Data
@Document(collection = "goods_comment")
public class GoodsComment {
    // MongoDB主键ID(自动生成)
    @MongoId
    private String id;

    // 商品ID
    @Field("goods_id")
    private Long goodsId;

    // 评论用户ID
    @Field("user_id")
    private Long userId;

    // 评论内容
    @Field("comment_content")
    private String commentContent;

    // 评论星级 1-5星
    @Field("star_level")
    private Integer starLevel;

    // 评论时间
    @Field("create_time")
    private Date createTime;

    // 是否置顶 0-否 1-是
    @Field("is_top")
    private Integer isTop;

    // 动态扩展字段:评论配图地址
    @Field("img_url")
    private String imgUrl;
}

6.4 持久层接口定义

延续SpringData统一规范,继承MongoRepository接口,自动获取CRUD、分页、排序、基础条件查询能力:

java 复制代码
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface GoodsCommentRepository extends MongoRepository<GoodsComment,String> {
    // 自定义命名查询:根据商品ID查询所有置顶评论
    List<GoodsComment> findByGoodsIdAndIsTop(Long goodsId, Integer isTop);

    // 根据星级区间查询评论
    List<GoodsComment> findByStarLevelBetween(Integer minStar, Integer maxStar);
}

6.5 全套CRUD实战开发

6.5.1 基础新增、修改、删除操作

MongoDB新增数据无需提前建表,集合不存在时框架自动创建;save方法同样适配新增/修改逻辑,根据主键ID判断执行逻辑:

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.Optional;

@SpringBootTest
public class MongoCommentTest {

    @Autowired
    private GoodsCommentRepository commentRepository;

    // 新增评论数据
    @Test
    void testInsertComment(){
        GoodsComment comment = new GoodsComment();
        comment.setGoodsId(1001L);
        comment.setUserId(10001L);
        comment.setCommentContent("手机续航超强,拍照效果非常清晰,性价比很高");
        comment.setStarLevel(5);
        comment.setCreateTime(new Date());
        comment.setIsTop(1);
        comment.setImgUrl("https://demo-img.com/phone001.jpg");
        // 保存数据
        commentRepository.save(comment);
    }

    // 根据ID修改评论内容
    @Test
    void testUpdateComment(){
        Optional<GoodsComment> optional = commentRepository.findById("填入生成的主键ID");
        if(optional.isPresent()){
            GoodsComment comment = optional.get();
            comment.setCommentContent("手机续航超强,拍照清晰,系统流畅,非常推荐!");
            comment.setStarLevel(5);
            commentRepository.save(comment);
        }
    }

    // 根据ID删除评论
    @Test
    void testDeleteComment(){
        commentRepository.deleteById("填入生成的主键ID");
    }
}

6.5.2 基础查询与分页排序查询

支持自带API查询、自定义命名查询、分页排序查询,适配日常业务检索场景:

java 复制代码
// 基础查询
@Test
void testBaseSelect(){
    // 查询所有数据
    commentRepository.findAll().forEach(System.out::println);

    // 根据商品ID查询置顶评论
    List<GoodsComment> topCommentList = commentRepository.findByGoodsIdAndIsTop(1001L,1);
    topCommentList.forEach(System.out::println);

    // 查询4-5星高分评论
    List<GoodsComment> starCommentList = commentRepository.findByStarLevelBetween(4,5);
    starCommentList.forEach(System.out::println);
}

// 分页排序查询
@Test
void testPageSortSelect(){
    // 分页参数:第0页,每页5条,按评论时间倒序排序
    Pageable pageable = PageRequest.of(0,5, Sort.by(Sort.Direction.DESC,"createTime"));
    Page<GoodsComment> page = commentRepository.findAll(pageable);

    System.out.println("总数据量:" + page.getTotalElements());
    System.out.println("总页数:" + page.getTotalPages());
    System.out.println("当前页数据:");
    page.getContent().forEach(System.out::println);
}

6.6 MongoTemplate 复杂动态查询实战

简单查询可通过Repository命名规则实现,复杂多条件动态拼接、模糊查询、字段筛选、分组统计场景,需使用MongoTemplate工具类实现,灵活性拉满:

java 复制代码
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.Test;
import java.util.List;

@Test
void testDynamicQuery(){
    // 注入模板工具类
    @Autowired
    private MongoTemplate mongoTemplate;

    // 构建动态条件
    Query query = new Query();
    Criteria criteria = new Criteria();

    // 动态拼接条件:商品ID=1001、星级≥3星、评论内容包含"手机"
    criteria.and("goods_id").is(1001L)
            .and("star_level").gte(3)
            .and("comment_content").regex("手机");

    query.addCriteria(criteria);
    // 分页设置
    query.skip(0).limit(3);

    // 执行查询
    List<GoodsComment> commentList = mongoTemplate.find(query, GoodsComment.class);
    commentList.forEach(System.out::println);
}

七、Spring Data Solr 基础了解

7.1 技术现状与核心定位

Apache Solr是一款开源全文检索引擎,早期广泛应用于企业搜索业务,目前官方迭代缓慢、社区活跃度极低,已逐步被ElasticSearch替代。Spring Data Solr是SpringData适配Solr的拓展模块,仅用于老旧项目维护,新项目一律推荐使用Spring Data ES。

7.2 核心特点与对比

  • Solr基于传统Lucene封装,侧重结构化数据检索,配置繁琐、集群部署复杂

  • ES原生支持分布式集群、实时检索、海量数据存储,适配现代互联网业务

  • Spring Data Solr用法与ES、JPA高度一致,同样基于Repository接口开发,学习成本极低

企业开发规范:新项目禁止使用Solr,仅老旧历史项目维护时使用,本章不做深度实战讲解。

八、SpringData 企业级综合实战案例

8.1 业务场景架构设计

结合本章所有知识点,搭建电商分层数据存储架构,整合多SpringData模块实现企业级业务落地,架构分工如下:

  • Spring Data JPA:操作MySQL,存储核心交易数据(订单、用户、商品基础数据),保证事务一致性

  • Spring Data Redis:缓存热点数据(用户信息、商品热度、访问计数器、排行榜),提升接口响应速度

  • Spring Data MongoDB:存储非结构化数据(商品评论、用户浏览日志)

  • Spring Data ElasticSearch:实现商品全文检索、模糊搜索、分类筛选功能

8.2 核心业务接口实战

实现「商品查询+缓存加速+全文检索+评论加载」完整业务链路,模拟企业真实开发逻辑:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class GoodsBusinessService {

    // JPA持久层(MySQL商品数据)
    @Autowired
    private GoodsRepository goodsRepository;

    // ES持久层(商品检索)
    @Autowired
    private GoodsEsRepository goodsEsRepository;

    // MongoDB持久层(商品评论)
    @Autowired
    private GoodsCommentRepository commentRepository;

    // Redis模板(缓存操作)
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 分页查询商品(缓存优先+数据库兜底)
     */
    public Page<Goods> getGoodsPage(int pageNum, int pageSize){
        String cacheKey = "goods:page:" + pageNum;
        // 1.先查询缓存
        Object cacheData = redisTemplate.opsForValue().get(cacheKey);
        if(cacheData != null){
            return (Page<Goods>) cacheData;
        }

        // 2.缓存不存在,查询MySQL数据库
        PageRequest pageable = PageRequest.of(pageNum, pageSize);
        Page<Goods> goodsPage = goodsRepository.findAll(pageable);

        // 3.数据写入缓存,设置10分钟过期
        redisTemplate.opsForValue().set(cacheKey,goodsPage,10, TimeUnit.MINUTES);
        return goodsPage;
    }

    /**
     * 商品全文检索(ES高性能检索)
     */
    public List<GoodsDoc> searchGoods(String keyword){
        // ES模糊分词检索
        return goodsEsRepository.findByGoodsNameLike(keyword);
    }

    /**
     * 获取商品高分评论
     */
    public List<GoodsComment> getGoodsHighComment(Long goodsId){
        // 查询4-5星高分评论
        return commentRepository.findByStarLevelBetween(4,5);
    }
}

九、SpringData 核心总结与选型规范

9.1 各模块核心选型标准

企业开发中需根据业务场景精准选择SpringData模块,避免技术滥用,核心选型规范如下:

  1. 核心业务、事务性数据 (订单、用户、支付):优先使用 Spring Data JPA,开发效率高、支持事务、适配多表关联

  2. 简单单表、高性能查询 :选择 Spring Data JDBC,轻量无冗余、执行速度快

  3. 热点缓存、计数器、排行榜 :固定使用 Spring Data Redis

  4. 非结构化、海量低价值数据 (日志、评论、动态):使用 Spring Data MongoDB

  5. 全文检索、模糊匹配、海量数据搜索 :使用 Spring Data ElasticSearch

  6. 老旧项目维护:仅存量项目使用Spring Data Solr,新项目禁用

9.2 通用开发规范

  • 所有SpringData项目统一使用Repository接口编程,杜绝原生API硬编码,保证代码统一性

  • 关系型数据库优先使用方法命名查询、注解查询,复杂SQL再使用原生语句

  • 缓存、检索、文档数据库必须配置序列化、分词器等基础优化参数,适配生产环境

  • 生产环境禁止使用DDL自动更新,仅开发测试环境启用,避免数据丢失风险

相关推荐
Java爱好狂.1 小时前
Java高并发系统架构设计核心技术开源!
java·高并发·并发编程·java面试·java面试题·java程序员·java八股文
182******20831 小时前
2026年学C语言还有出路吗?学习需要报班吗?
c语言·开发语言·学习
武子康1 小时前
Java-16 深入浅出MyBatis 架构设计与源码剖析:从初始化到 SQL 执行全流程
java·后端
8Qi81 小时前
LeetCode 416:分割等和子集 —— (0-1背包)
java·算法·leetcode·动态规划·背包问题·01背包
逍遥运德1 小时前
Java编程高频的“技术点”-03:“下划线命名”参数,后端用"驼峰命名"接收
java·后端·架构
_Evan_Yao2 小时前
如何高效刷LeetCode?大一版“从零开始”
算法·leetcode·职场和发展
ricardo19732 小时前
Web Worker + 时间切片:破解主线程阻塞的两种武器
前端·面试
XiYang-DING2 小时前
【MyBatis】${}与 #{}的区别
java·tomcat·mybatis
神奇小汤圆2 小时前
业务 Agent 搭建指南:别急着重造 Agent,用知识、工具与评测跑通闭环
面试