前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot整合H2(内置一个月对JPA的学习)

springboot整合H2(内置一个月对JPA的学习)

作者头像
花花522
发布2023-03-07 16:08:42
3.5K0
发布2023-03-07 16:08:42
举报
文章被收录于专栏:花花爱咖啡

文旦

介绍

什么是h2

H2是Thomas Mueller提供的一个开源的、纯java实现的关系数据库。它可以被嵌入Java应用程序中使用,或者作为一个单独的数据库服务器运行。

什么是JPA

JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

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

JPA由EJB 3.0软件专家组开发,作为JSR-220实现的一部分。但它又不限于EJB 3.0,你可以在Web应用、甚至桌面应用中使用。JPA的宗旨是为POJO提供持久化标准规范,由此可见,经过这几年的实践探索,能够脱离容器独立运行,方便开发和测试的理念已经深入人心了。Hibernate3.2+、TopLink 10.1.3以及OpenJPA都提供了JPA的实现。

JPA的总体思想和现有Hibernate、TopLink、JDO等ORM框架大体一致。总的来说,JPA包括以下3方面的技术:

ORM映射元数据

JPA支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;

API

用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。

查询语言

这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合

使用H2工程

因为我们使用JPA和H2,所以我们需要同时添加这两个依赖,同时为了偷懒,使用lombok

springboot版本我们使用2.4.1

pom.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.huahua</groupId>
    <artifactId>spring-boot-h2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-h2</name>
    <description>spring-boot-h2</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

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

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</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>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.1</version>
                <configuration>
                    <mainClass>cn.huahua.springbooth2.SpringBootH2Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

我们使用的是阿里云的starter服务器创建的,创建后竟然已经有配置文件了

代码语言:javascript
复制
# 应用名称
spring.application.name=spring-boot-h2
#************H2  Begin****************
#创建表的MySql语句位置
spring.datasource.schema=classpath:schema.sql
#插入数据的MySql语句的位置
spring.datasource.data=classpath:data.sql
#remote visit
spring.h2.console.settings.web-allow-others=true
#console url。Spring启动后,可以访问 http://127.0.0.1:8080/h2-console 查看数据库
spring.h2.console.path=/h2-console
#default true。咱也可以用命令行访问好数据库,感兴趣的同学点这个链接 http://www.h2database.com/html/tutorial.html?highlight=Mac&search=mac#firstFound
spring.h2.console.enabled=true
spring.h2.console.settings.trace=true
#指定数据库的种类,这里 file意思是文件型数据库
spring.datasource.url=jdbc:h2:file:~/test
#用户名密码不需要改,都是临时值
spring.datasource.username=san
spring.datasource.password=
#指定Driver,有了Driver才能访问数据库
spring.datasource.driver-class-name=org.h2.Driver

有两个配置是用来初始化我们的数据库的

代码语言:javascript
复制
#创建表的MySql语句位置
spring.datasource.schema=classpath:schema.sql
#插入数据的MySql语句的位置
spring.datasource.data=classpath:data.sql

# 如果不指定会在内存中 关闭就没了
#指定数据库的种类,这里 file意思是文件型数据库
spring.datasource.url=jdbc:h2:file:~/test
#这是内存数据库
spring.datasource.url=jdbc:h2:mem:testdb
  • 尝试启动
  • 里面提示了缺少文件,我们创建两个文件
  • data.sql
代码语言:javascript
复制
insert into "user_info"
values (default,'admin','admin');
  • schema.sql
代码语言:javascript
复制
CREATE TABLE "user_info"
(
    id INTEGER PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);
  • 启动截图

启动后直接终止,忘记导入web依赖,没有占用端口

加入依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 启动正常,访问管理面板
  • 输入账号san 密码留空

什么是网页管理

h2 web consloe是一个数据库GUI管理应用,就和phpMyAdmin类似。程序运行时,会自动启动h2 web consloe。当然你也可以进行如下的配置。

spring.h2.console.settings.web-allow-others=true,进行该配置后,h2 web consloe就可以在远程访问了。否则只能在本机访问。spring.h2.console.path=/h2-console,进行该配置,你就可以通过YOUR_URL/h2-console访问h2 web consloe。YOUR_URL是你程序的访问URl。spring.h2.console.enabled=true,进行该配置,程序开启时就会启动h2 web consloe。当然这是默认的,如果你不想在启动程序时启动h2 web consloe,那么就设置为false。

我们会发现找不到我们的库,尝试修改配置 改为内存数据库

代码语言:javascript
复制
spring.datasource.url=jdbc:h2:mem:testdb

注意修改地址

表结构有了

查看数据

噢啦

集成JPA

  • 上面已经导入了依赖,实际上就是
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
  • 创建实体类
代码语言:javascript
复制
@Data
@Entity("t_user")
//只加了@entity 没加主键 idea会爆红
public class User {

}
  • id策略
  • AUTO主键由程序控制, 是默认选项 ,不设置就是这个
  • IDENTITY 主键由数据库生成, 采用数据库自增长, Oracle不支持这种方式
  • SEQUENCE 通过数据库的序列产生主键, MYSQL 不支持
  • Table 提供特定的数据库产生主键, 该方式更有利于数据库的移植

实体类最终代码

代码语言:javascript
复制
package cn.huahua.springbooth2.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * @author LoveHuahua
 * @date 2022年06月12日 23:38
 * @description believe in yourself
 */
@Data
@Entity(name = "t_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Long id;

    private String username;

    private String password;
}
  • repository层
代码语言:javascript
复制
package cn.huahua.springbooth2.dao;

import cn.huahua.springbooth2.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
  • controller
代码语言:javascript
复制
package cn.huahua.springbooth2.controller;

import cn.huahua.springbooth2.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author LoveHuahua
 * @date 2022年06月12日 23:54
 * @description believe in yourself
 */
@RestController
public class TestJpaController {

    @Autowired
    UserRepository userRepository;

    @GetMapping("/testList")
    public Object testList() {
        return userRepository.findAll();
    }
}
  • 启动,打开h2控制台,发现一个有意思的现象,两边的表都创建了
  • 说明jpa也会自动建表的(在扫描到@Entity)的时候
  • 测试接口

目前没有数据,我们在控制台添加几条


重新测试接口

新增操作

代码语言:javascript
复制
/**
 * 测试jpa的新增
 * @param
 * @return
 */
@GetMapping("/testInsert")
public Object testInsert() {
    ArrayList<User> entities = new ArrayList<>();
    entities.add(new User(10L,"John", "John"));
    entities.add(new User(50L,"John", "John"));
    entities.add(new User(100L,"John", "John"));
   return userRepository.saveAll(entities);
}

结论 在@GeneratedValue(strategy = GenerationType.AUTO)时JPA会忽略ID 同时需要注意一点,如果你的id已经使用过了,这个insert会被忽略,如果有数据变更,会执行update,否则控制台只有select语句

  • id为10 这条没有执行(控制台只有两个insert)

单表

查询

分页查询

  • Pageable类
代码语言:javascript
复制
@GetMapping("/testList")
public Object testList(Integer page, Integer size) {
    Pageable pageable = PageRequest.of(page, size);
    Page<User> all = userRepository.findAll(pageable);
    return all;
}

使用JPA命名查询

根据用户名查询用户

在repository中增加方法

代码语言:javascript
复制
package cn.huahua.springbooth2.dao;

import cn.huahua.springbooth2.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

    /**
     * 参数名不影响,只要是第一个位置就行 实际上会生成 select * from t_user where username = ?1 
     * 根据用户名查询用户
     * @param username 用户名
     * @return
     */
    public User findUserByUsername(String username);
}
代码语言:javascript
复制
@GetMapping("/testSelectCondition")
public Object testSelectCondition(String username) {
  
    return userRepository.findUserByUsername(username);
}
根据用户名和密码查询用户
  • repository层
代码语言:javascript
复制
/**
 * 根据用户名和密码查询用户
 * @param username
 * @return
 */
public User findUserByUsernameAndPassword(String username, String password);	
  • 接口
代码语言:javascript
复制
@GetMapping("/testSelectAndCondition")
public Object testSelectAndCondition(String username,String password) {
    return userRepository.findUserByUsernameAndPassword(username, password);

}

http://localhost:8080/testSelectAndCondition?username=John222&password=John

根据用户名模糊搜索

http://localhost:8080/testSelectLikeCondition?username=Joh%25

注意这个%25 因为使用GET请求的时候 百分号是需要转义的

代码语言:javascript
复制
/**
 * 根据用户名模糊搜索
 * @param username
 * @return
 */
public List<User> findByUsernameLike(String username);

命名查询语法如下

Keyword

Sample

JPQL snippet

Distinct

findDistinctByLastnameAndFirstname

select distinct … where x.lastname = ?1 and x.firstname = ?2

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is, Equals

findByFirstname,findByFirstnameIs,findByFirstnameEquals

… where x.firstname = ?1

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age <= ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull, Null

findByAge(Is)Null

… where x.age is null

IsNotNull, NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1 (parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1 (parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1 (parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> ages)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstname) = UPPER(?1)

自定义查询

自定义sql分为两种 一种是通过JPQL,还有一种方式是原生sql执行

JPQL

一句JPQL一定是HQL,但是HQL不一定是JPQL,HQL是Hibernate提供的 而JPQL是JPA的一部分

两种参数传递

  • 使用参数名传递
代码语言:javascript
复制
    /**
     * 自定义sql 参数的第一种形式
     *
     * @param username
     * @return
     */
    @Query(value = "SELECT t FROM t_user t WHERE t.username = :username")
    public List<User> customerSql(String username);
  • 使用参数传递
代码语言:javascript
复制
/**
 * 自定义sql 参数的第二种形式
 *
 * @param username
 * @return
 */
@Query(value = "SELECT t FROM t_user t WHERE t.username = ?1")
public List<User> customerSql2(String username);
  • 测试报错
代码语言:javascript
复制
For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.

修改代码

添加@Param注解 指定参数名

使用占位参数是不需要注解的

最终repository代码

代码语言:javascript
复制
/**
 * 自定义sql 参数的第一种形式
 *
 * @param username
 * @return
 */
@Query(value = "SELECT t FROM t_user t WHERE t.username = :username")
public List<User> customerSql(@Param("username") String username);


/**
 * 自定义sql 参数的第二种形式
 *
 * @param username
 * @return
 */
@Query(value = "SELECT t FROM t_user t WHERE t.username = ?1")
public List<User> customerSql2(String username);

测试LIKE模糊条件

代码语言:javascript
复制
/**
 * 自定义sql like的使用
 *
 * @param username
 * @return
 */
@Query(value = "SELECT t FROM t_user t where t.username like %?1%  ")
public List<User> customerLikeSql(@Param("username") String username);


/**
 * 自定义sql like的使用
 *
 * @param username
 * @return
 */
//也可以是    @Query(value = "SELECT t FROM t_user t WHERE t.username like %:username%")
@Query(value = "SELECT t FROM t_user t WHERE t.username like concat('%',:username ,'%')")
public List<User> customerLikeSql2(@Param("username") String username);

网上有的说不行 不确定是版本问题还是啥

原生SQL

代码语言:javascript
复制
/**
 * 自定义sql like的使用
 *
 * @param username
 * @return
 */
@Query(value = "SELECT t.* FROM t_user t where t.username like '%'||:username||'%'  ",nativeQuery = true)
public List<User> customerNativeLikeSql(@Param("username") String username);


/**
 * 自定义sql like的使用
 *
 * @param username
 * @return
 */
@Query(value = "SELECT t.* FROM t_user t where t.username like '%'||?1||'%'  ",nativeQuery = true)
public List<User> customerNativeLikeSql2(@Param("username") String username);
  • 测试

控制台

复杂自定义查询

  • repository
代码语言:javascript
复制
/**
 * 复杂查询 分页 + 排序 + 筛选
 * @param username
 * @return
 */
@Query(value = "SELECT * FROM t_user as t WHERE t.username like  '%'||:username||'%'",nativeQuery = true,countQuery = "select count(1) from(SELECT * FROM t_user as t WHERE t.username like '%'||:username||'%') as count")
public Page<User> customerNativeDifficultSql(@Param("username") String username, Pageable pageable);
  • controller
代码语言:javascript
复制
@GetMapping("/testNativeExecuteDifficultQuery")
public Object testNativeExecuteDifficultQuery(String username,Integer page,Integer size){
    Sort sort = Sort.by(Sort.Direction.ASC, "username");

    PageRequest request = PageRequest.of(page, size, sort);

    return userRepository.customerNativeDifficultSql(username,request);
}
  • 测试

自动分页 排序 计算总数

学习网站 https://www.baeldung.com/spring-data-jpa-query

一对一

单向

场景说明

app收集学生信息.每一用户都需要上传学生信息 -> 一个用户对应一个学生信息 -> 一对一关联在student使用user_id来标识

  • 新建学生实体
代码语言:javascript
复制
package cn.huahua.springbooth2.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import javax.persistence.*;

/**
 * @author LoveHuahua
 * @date 2022年07月10日 19:15
 * @description believe in yourself
 */
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {


    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Long id;


    /**
     * 学生班级
     */
    @Column(name = "class_name")
    private String className;


    /**
     * 教师名称
     */
    @Column(name = "teacher_name")
    private String teacherName;
}
  • 修改用户实体 建立关联
代码语言:javascript
复制
package cn.huahua.springbooth2.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.management.MalformedObjectNameException;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author LoveHuahua
 * @date 2022年06月12日 23:38
 * @description believe in yourself
 */
@Data
@Entity(name = "t_user")
@AllArgsConstructor
@NoArgsConstructor
public class User {

    /**
     * 主键
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    /**
     * 账号
     */
    @Column(name = "username")
    private String username;

    /**
     * 密码
     */
    @Column(name = "password")
    private String password;

    /**
     * 一对一关联 用户关联学生表
     */
    @OneToOne(cascade = CascadeType.ALL)
    private Student student;

  
    public User(String username, String password, Student student) {
        this.username = username;
        this.password = password;
        this.student = student;
    }

}
  • 修改插入接口
代码语言:javascript
复制
    /**
     * 测试jpa的新增
     *
     * @param
     * @return
     */
    @GetMapping("/testInsert")
    public Object testInsert() {
        /**
         * 插入用户表数据
         */
        ArrayList<User> entities = new ArrayList<>();
        User user1 = new User("John", "John",new Student(null,"JAVA1班","JAVA老师"));
        entities.add(user1);
        User user2 = new User("John2", "John2",new Student(null,"JAVA2班","JAVA老师2"));
        entities.add(user2);
        User user3 = new User("John3", "John3",new Student(null,"JAVA3班","JAVA老师3"));
        entities.add(user3);


        return userRepository.saveAll(entities);
    }
  • 请求测试添加接口http://localhost:8080/testInsert
  • 查看表结构
  • 外键默认命名 表名_id 进行映射

如果手动指定外键字段,使用@JoinColumn指定字段

例如

代码语言:javascript
复制
@JoinColumn(name = "my_student_id",referencedColumnName = "id")

效果如下:

混淆点:

@JoinColumn(name = "student_id",referencedColumnName = "id")

  • name = 本表的属性
  • referencedColumnName 关联student中的字段

@JoinColumn和@PrimaryKeyJoinColumn的区别

Use PrimaryKeyJoinColumn

Use JoinColumn

地址:https://stackoverflow.com/questions/3417097/jpa-difference-between-joincolumn-and-primarykeyjoincolumn

双向

双向其实就是在关系的另一边,也进行一遍关系的维护,例如下面的实体类

代码语言:javascript
复制
package cn.huahua.springbooth2.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import javax.persistence.*;

/**
 * @author LoveHuahua
 * @date 2022年07月10日 19:15
 * @description believe in yourself
 */
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    public Student(Long id, String className, String teacherName) {
        this.id = id;
        this.className = className;
        this.teacherName = teacherName;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Long id;


    /**
     * 学生班级
     */
    @Column(name = "class_name")
    private String className;


    /**
     * 教师名称
     */
    @Column(name = "teacher_name")
    private String teacherName;

    /**
     * 双向
     */
    @OneToOne(cascade = CascadeType.ALL,mappedBy = "student")
    private User user;
}
  • 编写测试接口代码
代码语言:javascript
复制
/**
 * 一对一双向测试
 * @param user
 * @return
 */
@PostMapping("/oneToOne")
public Object oneToOne(@RequestBody User user) {
    List<User> all = userRepository.findAll();
    return all;
}
  • 测试
  • 我们会发现一个很奇怪的问题,就是数据对了很多,我们可以捋一下,我们user中持有了student,student中有持有了user,相互持有,所以是不是会导致json死循环
  • 所以死循环跟jpa没关系,而跟我们的序列化有关,所以我们可以通过jackson提供的@JsonIgnoreProperties注解忽略sudent中的user属性,这样就不会有循环序列化的问题了
  • 修改user实体类
代码语言:javascript
复制
/**
 * 一对一关联 用户关联学生表
 */
@OneToOne
@JsonIgnoreProperties(value = {"user"})
@JoinColumn(name = "id",referencedColumnName = "user_id")
private Student student;

新增

新增其实已经看过了,就是调用save方法,我们使用restful风格去编写下四种操作

  • 编写controller
代码语言:javascript
复制
/**
 * 一对一 测试新增
 * @param user
 * @return
 */
@PutMapping("/oneToOne")
public Object oneToOneSave(@RequestBody User user) {
    userRepository.save(user);
    return ResponseEntity.ok().body("success");

}
  • 请求参数
代码语言:javascript
复制
{
    "id":1,
    "username":"",
    "password":"",
    "student":{
        "id":1,
        "className":"JAVA1班",
        "teacherName":"花花1号"
    }
}
  • 使用apifox请求
  • 查看数据

删除

请求地址http://localhost:8080/oneToOne

返回

查看控制台

  • 我们可以看到 同时删除了两张表,这就是级联删除

如果id不存在 会直接报错

代码语言:javascript
复制
org.springframework.dao.EmptyResultDataAccessException: No class cn.huahua.springbooth2.entity.User entity with id 1 exists!

测试几种级联操作

  • 级联操作

@OneToMany有一个属性是cascade

jpa的级联类型(Cascade Types)包括:

  • ALL 全部
    • 包含下面所有的信息
  • PERSIST(当父需要保存时,子也会默认保存,删除的时候 删除父也不行 不会执行delete)
  • MERGE(当父对象更新,更新操作会传递到子对象)
  • { "id":1, "username":"", "password":"", "student":{ "id":1, "className":"JAVA1班", "teacherName":"花花1号 测试更新" } } 效果: 更新user会自动更新student,注意要传递student的id 不然会一直执行保存 /** * 一对一双向测试 * @param user * @return */ @DeleteMapping("/oneToOne") public Object oneToOneDelete(@RequestBody User user) { userRepository.deleteById(user.getId()); return ResponseEntity.ok().body("success"); }
    • 发现控制台没有delete语句 查看数据库 也并没有删除操作
    • 尝试删除 object references an unsaved transient instance - save the transient instance before flushing : cn.huahua.springbooth2.entity.User.student -> cn.huahua.springbooth2.entity.Student at org.hibernate.engine.spi.CascadingActions$8.noCascade
    • 保存不生效,所以执行插入会直接报错
    • 修改成@OneToOne(cascade = {CascadeType.MERGE,CascadeType.PERSIST})
    • 重新插入数据,新增一个编辑接口
  • REMOVE(删除操作,传递子对象)
  • 我们使用@OneToOne(cascade = {CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REMOVE})
  • REFRESH(自动刷新)
    • 官方翻译使用了CascadeType.REFRESH后,会级联的获取子对象在数据库的信息。
  • DETACH
    • 如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。
    • 这个一般加载被控端(外键的那种表)

修改

修改我们上面已经看过了,其实还是save 这里不展示了

查询

查询也是一样 上面已经用很大篇幅说了 调用findAll就行

一对多

单向

场景: 校内组织了论坛活动,每个用户都可以发布多个文章 -> 一对多

  • 新建文章实体
代码语言:javascript
复制
package cn.huahua.springbooth2.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * @author LoveHuahua
 * @date 2022年07月09日 22:38
 * @description believe in yourself
 */
@Entity(name = "article")
@Data
public class Article {

    /**
     * 主键id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;
    /**
     * 标题
     */
    @Column(name = "title")
    private String title;
    /**
     * 文章内容
     */
    @Column(name = "content")
    private String content;


    public Article(String title, String content) {
        this.title = title;
        this.content = content;
    }


    public Article() {

    }
}
  • 在用户实体中建立一对多关联
代码语言:javascript
复制
    @OneToMany(cascade = CascadeType.ALL)
//为了避免权限问题 我们使用级联操作为 CascadeType.ALL
    private List<Article> articles = new ArrayList<>();
  • 修改用户实体后
代码语言:javascript
复制
package cn.huahua.springbooth2.entity;

import lombok.Data;

import javax.management.MalformedObjectNameException;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author LoveHuahua
 * @date 2022年06月12日 23:38
 * @description believe in yourself
 */
@Data
@Entity(name = "t_user")
public class User {

    /**
     * 主键
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    /**
     * 账号
     */
    @Column(name = "username")
    private String username;

    /**
     * 密码
     */
    @Column(name = "password")
    private String password;

    /**
     * 一对一关联 用户关联学生表
     */
    @OneToOne
    @JoinColumn(name = "id",referencedColumnName = "user_id")
    private Student student;


    @OneToMany(cascade = CascadeType.ALL)
    private List<Article> articles = new ArrayList<>();


    public User(Long id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public User(String username, String password, List<Article> articles) {
        this.username = username;
        this.password = password;
        this.articles = articles;
    }

    public User() {

    }
}

修改数据插入操作

代码语言:javascript
复制
    /**
     * 测试jpa的新增
     *
     * @param
     * @return
     */
    @GetMapping("/testInsert")
    public Object testInsert() {

        /**
         * 插入用户表数据
         */
        ArrayList<User> entities = new ArrayList<>();
        User user1 = new User( "John", "John",new ArrayList<Article>(){{
            add(new Article("标题1", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
        }});
        entities.add(user1);
        User user2 = new User( "John2", "John2",new ArrayList<Article>(){{
            add(new Article("标题2", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
        }});
        entities.add(user2);
        User user3 = new User( "John3", "John3",new ArrayList<Article>(){{
            add(new Article("标题3", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
        }});
        entities.add(user3);

        /**
         * 插入学生数据
         */
        studentRepository.save(new Student(null, 1L, "JAVA1班", "花花1"));
        studentRepository.save(new Student(null, 2L, "JAVA2班", "花花2"));
        studentRepository.save(new Student(null, 3L, "JAVA3班", "花花3"));


;
        return userRepository.saveAll(entities);
    }

因为我们设置了级联操作为ALL,所以保存是会传递的

  • 我们生成了三张表

默认中间表的命名是一对多中一表名_一对多中多的表名

使用@JoinColumn(name = "user_id")指定关联字段 避免中间表的产生

注意点

@JoinColumn如果不加,也可以生成一对多的关联,但是会生成中间表,一般情况下1对多我们不会建立的,所以都会加上

双向

  • 修改文章实体类
代码语言:javascript
复制
@ManyToOne(cascade = CascadeType.ALL) //级联操作
@JoinColumn(name = "my_user_id",referencedColumnName = "id") //定义外键 也就是多表的字段是my_user_id
private User user;
  • 修改用户实体类
代码语言:javascript
复制
    @OneToMany(cascade = CascadeType.ALL,mappedBy = "user",fetch=FetchType.EAGER)
//    @JoinColumn(name = "user_id") //注意这里 使用mappedBy 不然会有两个外键
//    @JsonIgnoreProperties(value = {"user"}) 要忽略Article内部的user 不然序列化会死循环
    private List<Article> articles = new ArrayList<>();

存在问题

使用增加接口,会发现我们的my_user_id字段没有字段关联

  • 经过搜索 大概结论是 需要手动set我们的关系 代码如下
代码语言:javascript
复制
/**
 * 一对多 测试新增
 * @param user
 * @return
 */
@PutMapping("/oneToMany")
public Object oneToMany(@RequestBody User user) {
    for (Article article : user.getArticles()) {
        article.setUser(user);
    }
    userRepository.save(user);
    return ResponseEntity.ok().body("success");
}

请求测试接口

代码语言:javascript
复制
{
    "id":1,
    "username":"",
    "password":"",
    "student":{
        "id":1,
        "className":"JAVA1班",
        "teacherName":"花花1号 测试更新"
    },
    "articles":[
        {
            "title":"标题2",
            "bookName":"java从入门到精通2"
        },
        {
            "title":"标题3",
            "bookName":"java从入门到精通3"
        }
    ]
}
  • 查看表数据

多对多

单向

场景: 图书馆的借书场景,一本书可以被借多次,一个用户可以借多本

  • 修改user实体类
代码语言:javascript
复制
/**
 * 学生借的书
 */
@ManyToMany(cascade = CascadeType.ALL)
private List<Book> books = new ArrayList<>();
  • book实体类
代码语言:javascript
复制
package cn.huahua.springbooth2.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

/**
 * @author LoveHuahua
 * @date 2022年07月13日 23:38
 * @description believe in yourself
 */
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    /**
     * 书本名称
     */
    @Column(name = "book_name")
    private String bookName;


}
  • 给user增加有参构造
代码语言:javascript
复制
public User(String username, String password, List<Article> articles,List<Book> books) {
    this.username = username;
    this.password = password;
    this.articles = articles;
    this.books = books;
}
  • 修改插入语句
代码语言:javascript
复制
/**
 * 测试jpa的新增
 *
 * @param
 * @return
 */
@GetMapping("/testInsert")
public Object testInsert() {

    /**
     * 插入用户表数据
     */
    ArrayList<User> entities = new ArrayList<>();
    User user1 = new User("John", "John", new ArrayList<Article>() {{
        add(new Article("标题1", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
    }}, new ArrayList<Book>() {{
        add(new Book(null, "java从入门到精通1"));
    }});
    entities.add(user1);
    User user2 = new User("John2", "John2", new ArrayList<Article>() {{
        add(new Article("标题2", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
    }}, new ArrayList<Book>() {{
        add(new Book(null, "java从入门到精通2"));
    }});
    entities.add(user2);
    User user3 = new User("John3", "John3", new ArrayList<Article>() {{
        add(new Article("标题3", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
    }}, new ArrayList<Book>() {{
        add(new Book(null, "java从入门到精通3"));
    }});
    entities.add(user3);

    /**
     * 插入学生数据
     */
    studentRepository.save(new Student(null, 1L, "JAVA1班", "花花1"));
    studentRepository.save(new Student(null, 2L, "JAVA2班", "花花2"));
    studentRepository.save(new Student(null, 3L, "JAVA3班", "花花3"));


    return userRepository.saveAll(entities);
}
  • 请求 查看数据库
  • 生成了一张中间表,关联了两个表的关系
  • 请求查询接口

发现我们什么都没动,他就已经可以实现多表关联了

  • 两个字段 这两个名字我们没有指定,但是jpa自动用表名id/字段名id给我们生成的
    • t_user_id
    • books_id
  • 手动指定关联字段

修改books参数

代码语言:javascript
复制
    /**
     * 学生借的书
     */
    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "t_book_user",joinColumns = {
            @JoinColumn(name = "user_id"),
    },inverseJoinColumns = {
            @JoinColumn(name = "book_id")
    })
    private List<Book> books = new ArrayList<>();
  • 这时候我们可以看到数据表结构已经是我们定义的了

双向

  • 修改用户表
代码语言:javascript
复制
/**
 * 学生借的书
 */
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "t_book_user", joinColumns = {
        @JoinColumn(name = "user_id"),
}, inverseJoinColumns = {
        @JoinColumn(name = "book_id")
})
@JsonIgnoreProperties(value = {"users"})
private List<Book> books = new ArrayList<>();
  • 修改书本表
代码语言:javascript
复制
    @ManyToMany(cascade = CascadeType.ALL,mappedBy = "books")
    private List<User> users = new ArrayList<User>();
  • 修改测试数据添加接口
代码语言:javascript
复制
/**
 * 测试jpa的新增
 *
 * @param
 * @return
 */
@GetMapping("/testInsert")
public Object testInsert() {
    Book book = new Book(null, "java从入门到精通3");

    /**
     * 插入用户表数据
     */
    ArrayList<User> entities = new ArrayList<>();
    User user1 = new User("John", "John",new Student(null,"JAVA1班","JAVA老师"),new ArrayList<Article>() {{
        add(new Article("标题1", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
    }}, new ArrayList<Book>() {{
        add(new Book(null, "java从入门到精通1"));
        add(book);
    }});
    entities.add(user1);
    User user2 = new User("John2", "John2",new Student(null,"JAVA2班","JAVA老师2"), new ArrayList<Article>() {{
        add(new Article("标题2", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
    }}, new ArrayList<Book>() {{
        add(new Book(null, "java从入门到精通2"));
        add(book);
    }});
    entities.add(user2);
    User user3 = new User("John3", "John3",new Student(null,"JAVA3班","JAVA老师3"), new ArrayList<Article>() {{
        add(new Article("标题3", "有秀儿1不理我啊啊啊啊啊啊啊啊啊啊啊啊啊"));
    }}, new ArrayList<Book>() {{
        add(book);
    }});
    entities.add(user3);


    return userRepository.saveAll(entities);
}
  • 查看列表接口http://localhost:8080/testList?page=0&size=10
    • 测试需要把@JsonIgnoreProperties(value = {"users"})删除了,并且为了防止死循环,在book中的users添加了@JsonIgnoreProperties(value = {"books"})
    • 响应
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 花花爱咖啡 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 使用H2工程
  • 集成JPA
    • 新增操作
      • 单表
        • 查询
        • 使用JPA命名查询
        • JPQL
        • 原生SQL
        • 复杂自定义查询
      • 一对一
        • 单向
        • 双向
        • 新增
        • 删除
        • 修改
        • 查询
      • 一对多
        • 单向
        • 双向
      • 多对多
        • 单向
        • 双向
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档