Spring Data

Spring Data

Spring Data JPA

查询方式

查询方式有五种:

  1. 调用父接口中的方法
  2. 方法命名规则查询(按照一定的规范定义接口中的方法, 自动生成SQL)
  3. JPQL查询
  4. 本地SQL查询
  5. Specification动态查询

多表关系

OneToOne 关系

(略)

OneToMany 关系

One:主表

Many:从表

OneToMany 关系通过从表的外键来刻画

ManyToMany 关系

不区分主表和从表,通过中间表来代替外键,对 ManyToMany 关系进行刻画

OneToMany 和 ManyToMany 的区别:

  • OneToMany 关系中,站在主表到从表的角度看,是一对多关系;而站在从表到主表的角度看,是一对一关系。
  • ManyToMany 关系中,无论站在两张表的那一方去看,都是一对多关系。

所谓的维护关系和放弃关系的维护权,本质上就是在调用save()方法时,需不需要额外去保存关系(可能是中间表,也可能是外键)。在保存维护关系的类之前,需要将不维护关系的实体类先保存。如果先保存维护关系的实体类A,那么同时触发保存关系表,而关系表设计到实体类A和实体类B,此时实体类B还没有保存,因此会报错。

image-20230510153934822

案例演示

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "article")
@Getter
@Setter
public class Article {

    /**
     * GenerationType.IDENTITY: 数据库的主键需要设置为AUTO_INCREMENT才会生效, 否则报错
     * <p>
     * 虽然id可以唯一的标识一个对象, 但是如果重写hashCode和equals方法并不可以仅仅通过id, 因为在插入的时候, id还并没有生成
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

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

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

    @Column(name = "create_time")
    private Date createTime;

    @Column(name = "update_time")
    private Date updateTime;


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 上面设置Article本身的属性字段, 下面设置与其它类的关系
    // 使用@JoinTable表示负责维护关系, 需要后保存
    @ManyToMany
    @JoinTable(
            // 中间表在数据库中的名称
            name = "article_types",
            // 中间表中对应到本表主键的列名, 因为主键可能是多列共同组成的一个复合列, 因此使用数组
            joinColumns = {
                    @JoinColumn(name = "aid", referencedColumnName = "id")
            },
            // 中间表中对应到另一张表主键的列名
            inverseJoinColumns = {
                    @JoinColumn(name = "tid", referencedColumnName = "id")
            }
    )
    private Set<Type> types = new HashSet<>();

    public void addType(Type type) {
        this.types.add(type);
    }

}
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;


@Getter
@Setter
@Entity
@Table(name = "type")
public class Type {
    /**
     * 类型id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    /**
     * 类型名称
     */
    private String name;


    /**
     * mappedBy用来表示主动放弃关系的维护权
     */
    @ManyToMany(mappedBy = "types")
    private Set<Article> articles = new HashSet<>();

    public void addArticle(Article article) {
        this.articles.add(article);
    }

}
@SpringBootTest
public class ApplicationMainTest {
    @Autowired
    private ArticleRepository articleRepository;

    @Autowired
    private TypeRepository typeRepository;

    @Test
    public void manyToManyRelationshipTest() {
        // 创建Article
        Article article01 = new Article();
        article01.setAuthor("Root");
        article01.setTitle("JPA");
        article01.setCreateTime(new Date());
        Article article02 = new Article();
        article02.setAuthor("Admin");
        article02.setTitle("Spring Data");
        article02.setCreateTime(new Date());

        // 创建Type
        Type type01 = new Type();
        type01.setName("TypeA");
        Type type02 = new Type();
        type02.setName("TypeB");

        // 设置关系
        article01.addType(type01);
        article01.addType(type02);
        article02.addType(type01);
        article02.addType(type02);
        // TODO: 1. 为什么会在这里报错java.lang.StackOverflowError呢?
        //  上面的也没有报错. hashCode的问题.
        //  并且奇怪的是, 在将@Data注解换成getter和setter方法后就正常了
        //  [解决]: 由于两个循环依赖的对象会互相调用toString方法, 从而导致栈溢出, @Data注解中默认包含@ToString注解. 可以显式地使用@ToString注解
        type01.addArticle(article01);
        type01.addArticle(article02);
        type02.addArticle(article01);
        type02.addArticle(article02);

        // TODO: 为什么在保存成功后(insert)会执行select和update?
        //  [解决]: 莫名其妙地解决

        // 和OneToMany关系一样, 需要先保存不维护关系的实体Type, 否则会报错. 
        // 不维护关系的实体可以认为仅仅保存自己, 不会产生循环依赖.
        // 而维护关系的实体, 在保存自己的时候, 还要去保存关系, 因此如果此时type没有保存, 就不会有tid, 因此报错
        typeRepository.save(type01);
        typeRepository.save(type02);
        articleRepository.save(article01);
        articleRepository.save(article02);
    }
}

测试结果:没有多余的select语句

级联操作(需要改进)

TODO:对级联操作的理解有误,下面的涉及到cascade的不一定正确,有待改进。

操作一个对象时,同时操作与他相关联的对象。

以User和Role为例,User和Role是多对多关系,数据库中有三张表 userroleuser_role

  • 情况1:User和Role都不使用@JoinTable(没有维护关系)

    userDao.save(user);
    roleDao.save(role);
    // 此时user表和role表中都有数据, 但是user_role表中没有数据
    // 维护关系发生在save()时, 但是User和Role都没有去维护关系, 即都没有向user_role表中添加数据
  • 情况2:User和Role都使用@JoinTable(都维护关系)

    (TODO:期待报错主键重复,目前测试失败)

    userDao.save(user);
    roleDao.save(role);
    // 此时roleDao.save()时出现主键重复的问题, 
    // 假设userId=1, roleId=2, 在userDao.save()时向user_role表中添加数据, 但是在roleDao.save()时再次向user_role表中添加相同的主键
  • 情况2改进:User和Role任意一方放弃关系的维护,即调用save方法不会向user_role表中添加数据

    放弃关系的维护,通过@ManyToMany中的mappedBy属性

    (TODO:期待在任意一方添加上mappedBy属性后执行成功,目前测试失败)

    userDao.save(user);
    roleDao.save(role);
  • 情况3:在负责维护关系的实体类中使用级联操作(cascade)

    不妨假设维护关系的实体类为User,那么期待仅仅通过userDao.save()就可以同时保存role

    userDao.save(user);

Spring Data Redis

Spring Data Redis中提供的序列化器,可以通过 RedisSerializer 接口来查看有哪些序列化器

序列化器 作用
StringRedisSerializer 简单的字符串序列化
GenericToStringSerializer 可以将任何对象泛化为字符串并序列化
Jackson2jsonRedisSerializer 序列化对象为json字符串
GenericJackson2jsonRedisSerializer 序列化对象为json字符串,更容易反序列化
OxmSerializer 序列化对象为xml字符串
JdkSerializationRedisSerializer(默认) 序列化对象为二进制数据

   转载规则


《Spring Data》 熊水斌 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
MySQL从入门到精通 MySQL从入门到精通
MySQL 简介学习路径 SQL 事务 存储引擎 索引 SQL 优化 锁 MySQL 安装 卸载预安装的mariadb rpm -qa | grep mariadb rpm -e --nodeps mariadb-libs 安装网络工具
2023-05-11
下一篇 
Spring源码解析 Spring源码解析
通过 new RuntimeException().getStackTrace() 从方法调用栈的栈帧 StackTraceElement 里面来找到主启动类 执行流程// 获取initializer, 排序 SpringApplicati
2023-05-01
  目录