spring-jpa

news2024/10/24 13:22:17

一、介绍

1.1ORM

1.2 Java Persistence API

放在javaee版本 

优点

  • 支持持久化复杂的Java对象,简化Java应用的对象持久化开发
  • 支持使用JPQL语言进行复杂的数据查询
  • 使用简单,支持使用注解定义对象关系表之间的映射
  • 规范标准化,由Java官 方统一规定的API接口
  • 实用性,多种框架实现了JPA标准
  • 可移植性,使用JPA的应用可以自由选择遵循JPA标准的框架,并能随时更换
  • 支持全局事务处理、保证数据完整性、支持并发访问、支持大数据处理

相关框架

 

1.3与MyBatis异同

JPA是全自动面向对象持久化技术,可完全屏蔽JDBC/SQL,实现自动的对象/记录映射。

MyBatis是半自动持久化框架。仅屏蔽封装JDBC操作,但仍需编写SQL语句完成对象/记录映射

且MyBatis仍需针对每个数据表,编写基本CURD模板代码

1.4入门案例

1.1 创建新模块

 <!-- junit-platform-launcher -->
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <scope>test</scope>
        </dependency>

1.2配置

spring:
  datasource:
    url: 'jdbc:mysql://'
    username: root
    password: 
  sql:
    init:
      mode: always
  jpa:
    show-sql: true
    //表示在控制台输出执行的SQL语句。
    hibernate:
      ddl-auto: update
      //Hibernate在启动时如何自动创建、更新或验证数据库表结构
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect 
        //指定了Hibernate使用的数据库方言,这里使用的是MySQL 8的方言。



logging:
  level:
    sql: debug
    com:
      example: debug
  pattern:
    console: '%-5level %C.%M[%line] - %msg%n'
server:
  port: 8080

1.5实体类

package com.yanyu.example01;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;

@Data // 使用Lombok自动生成getter和setter方法
@NoArgsConstructor // 使用Lombok自动生成无参构造方法
@Entity // 表示这是一个实体类,用于映射数据库表
public class User {
    @Id // 表示这是主键字段
    @GeneratedValue // 表示主键值是自动生成的
    @Column(length = 16) // 表示该字段在数据库表中的长度为16
    private UUID id; // 用户ID
    private String name; // 用户名
    private LocalDate birthday; // 用户生日
    @Column(columnDefinition = "timestamp default current_timestamp", // 设置默认值为当前时间戳
            insertable = false, // 不允许插入数据时指定该字段的值
            updatable = false) // 不允许更新数据时指定该字段的值
    private LocalDateTime insertTime; // 记录创建时间
    @Column(columnDefinition = "timestamp default current_timestamp " + // 设置默认值为当前时间戳
            "on update current_timestamp", // 更新数据时自动更新为当前时间戳
            insertable = false, // 不允许插入数据时指定该字段的值
            updatable = false) // 不允许更新数据时指定该字段的值
    private LocalDateTime updateTime; // 记录更新时间
}

自动创建数据表 

@Entity

@Entity是Java Persistence API (JPA)中的一个注解,用于标记一个类作为数据库实体。这个注解告诉JPA框架,这个类应该被映射到数据库中的一个表。

@Id@GeneratedValue

测试

package com.yanyu.example01;

import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;

import org.springframework.transaction.annotation.Transactional;


import java.time.LocalDate;

@SpringBootTest
@Slf4j
@Transactional
@Rollback(value = false)
public class UserTest {
    @Autowired
    private EntityManager manager;

    @Test
    public void test_addUser() {
        User user = new User();
        user.setName("asasasas");
        user.setBirthday(LocalDate.of(1990, 8, 8));
        manager.persist(user);
        log.debug("{}", user.getId());
    }
}

@Transactional

@Transactional是Spring框架中的一个注解,用于声明一个方法或类需要进行事务管理。当使用@Transactional注解时,Spring会自动为该方法或类开启一个新的事务,并在方法执行完毕后自动提交或回滚事务。

 @Rollback

@Rollback是Spring框架中的一个注解,用于声明一个方法在执行过程中出现异常时需要进行回滚操作。当使用@Rollback注解时,Spring会自动为该方法开启一个新的事务,并在方法执行完毕后自动提交或回滚事务。

EntityManager

**EntityManager是Java Persistence API (JPA)中定义的一个接口,它负责管理实体对象的持久化操作**。

具体来说,EntityManager提供了一系列的功能,允许开发者对数据库中的实体对象进行增删改查等操作。以下是EntityManager的一些关键职责和特性:

1. **持久化实体对象**:通过EntityManager,可以将普通Java对象转换为持久化实体,即保存到数据库中。
2. **管理事务**:EntityManager允许开始、提交或回滚事务,确保数据库操作的一致性。
3. **查询功能**:提供创建和执行查询的方法,包括JPQL(Java Persistence Query Language)和Criteria API等方式来检索数据。
4. **管理实体生命周期**:EntityManager负责管理实体对象的生命周期,包括对象的创建、更新、加载和删除等操作。
5. **实现ORM**:作为ORM(Object-Relational Mapping,对象关系映射)的核心组件,EntityManager将面向对象的编程模型映射到数据库的关系模型上。
6. **访问Persistence Context**:EntityManager提供了访问Persistence Context的API,Persistence Context是实体对象的缓存,用于管理持久化过程中的实体对象状态。

总的来说,EntityManager是JPA规范中的核心接口,它为Java应用程序提供了一个标准化的方式来处理数据库中的实体对象。通过使用EntityManager,开发者可以更加专注于业务逻辑,而不必关心底层数据库的具体实现细节。

@table

@Column

 DateTime & Timestamp

1.6UUID 

package com.yanyu.util;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.util.UUID;

@Slf4j
public class UUIDTest {
    @Test
    public void test_uuid() {
        UUID uuid = UUID.randomUUID();
        log.debug("{}", uuid);
        uuid = UUID.randomUUID();
        log.debug("{}", uuid);
        uuid = UUID.randomUUID();
        log.debug("{}", uuid);
        uuid = UUID.randomUUID();
        log.debug("{}", uuid);
        uuid = UUID.randomUUID();
    }
}

1.7SnowFlake

对应数据库中的主键(uuid、自增id、雪花算法、redis、zookeeper

 二、实体关系

2.1关系概述

2.2一对多

package com.yanyu.example02.onetomany;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;


import java.util.List;

@Data
@NoArgsConstructor
@Entity
public class User02 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @OneToMany(mappedBy = "user")
    private List<Address02> addresses;
}

@OneToMany 是 Java Persistence API (JPA) 中的一个注解,用于表示实体类之间的一对多关系。 

package com.yanyu.example02.onetomany;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;




@Data
@NoArgsConstructor
@Entity
public class Address02 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String detail;
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User02 user;
}
  • @ManyToOne 是 Java Persistence API (JPA) 中的一个注解,用于表示实体类之间的多对一关系。
  • @JoinColumn 是 Java Persistence API (JPA) 中的一个注解,用于指定实体类中关联属性的外键列名。

    在 JPA 中,当两个实体类之间存在关联关系时,可以使用 @JoinColumn 注解来指定关联属性的外键列名。这通常用于多对一或一对多的关系映射。

测试1

 @Test
    public void test_addUserAddress() {
        User02 u = new User02();
        u.setName("BO");
        manager.persist(u);

        Address02 a1 = new Address02();
        a1.setDetail("956");
        manager.persist(a1);

        Address02 a2 = new Address02();
        a2.setDetail("956");
        manager.persist(a2);
    }

 测试2

 @Test
    public void test_rel() {
        User02 u = manager.find(User02.class, 1);

        Address02 a1 = manager.find(Address02.class, 1);
        a1.setUser(u);
        Address02 a2 = manager.find(Address02.class, 2);
        a2.setUser(u);
    }

2.3一对一

@OneToOne 是 Java Persistence API (JPA) 中的一个注解,用于表示实体类之间的一对一关系。

在 JPA 中,一个实体类可以与另一个实体类之间建立一对一的关系。例如,假设我们有两个实体类:UserProfile,每个用户可以拥有一个个人资料,而每个个人资料只能属于一个用户。在这种情况下,我们可以使用 @OneToOne 注解来表示这种关系。

2.4多对多

 

package com.yanyu.example04.manytomany;


import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@Entity
public class Student04 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @OneToMany(mappedBy = "student")
    private List<Elective04> electives;
}
package com.yanyu.example04.manytomany;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@Entity
public class Course04 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @OneToMany(mappedBy = "course")
    private List<Elective04> electives;

}
package com.yanyu.example04.manytomany;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;



@Entity
@Data
@NoArgsConstructor
public class Elective04 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String detail;
    @ManyToOne
    private Student04 student;
    @ManyToOne
    private Course04 course;
}

 测试

package com.yanyu.example04;


import com.yanyu.example04.manytomany.Course04;
import com.yanyu.example04.manytomany.Elective04;
import com.yanyu.example04.manytomany.Student04;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;



@SpringBootTest
@Slf4j
@Transactional
@Rollback(value = false)
public class ManyToManyTest {
    @Autowired
    private EntityManager manager;

    @Test
    public void test_init() {
        // 初始学生1
        Student04 s = new Student04();
        s.setName("BO");
        manager.persist(s);
        // 初始化学生2
        Student04 s2 = new Student04();
        s2.setName("SUN");
        manager.persist(s2);
        //初始化课程1
        Course04 c = new Course04();
        c.setName("Web框架");
        manager.persist(c);
    }

    @Test
    public void  test_rel() {
        // ID位1的学生,选择了ID为1的课程
        Student04 s = manager.find(Student04.class, 2);
        Course04 c = manager.find(Course04.class, 1);
        Elective04 elective = new Elective04();
        elective.setDetail("qqqqq");
        elective.setStudent(s);
        elective.setCourse(c);
        manager.persist(elective);
    }
}

三、jpa项目

3.1概念

数据源

持久化上下文

实体管理器 

总结

 项目状态

即,想要完成持久化操作,无论增/删/改,均需通过调用相应方法,将其置于持久化上下文中,当事务结束后,自动将操作结果同步到数据库

 

3.2 常用接口介绍

3.3JpaRepository Interface 

创建实体类

package com.yanyu.example05.baserepository.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;


import java.time.LocalDateTime;

@Entity
@Data
@NoArgsConstructor
public class User05 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @Column(columnDefinition = "timestamp default current_timestamp",
            insertable = false,
            updatable = false)
    private LocalDateTime insertTime;
}

创建接口

package com.yanyu.example05.baserepository.repository;


import com.yanyu.example05.baserepository.entity.User05;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface User05Repository extends JpaRepository<User05, Integer> {
}

 

测试1

 @Autowired
    private User05Repository user05Repository;
    @Autowired
    EntityManager manager;
    @Test
    public void test_addUser() {
        User05 user05 = new User05();
        user05.setName("a");
        user05Repository.save(user05);
        user05.setName("b");
    }

测试2

@Test
    public void test_addUser2() {
        User05 user05 = user05Repository.findById(1).orElse(null);
        log.debug("{}", user05.getInsertTime());
    }

3.4BaseRepository

 @Test
    public void test_refresh() {
        User05 user05 = new User05();
        user05.setName("SUN");
        manager.persist(user05);
        user05.setName("BO");
        manager.refresh(user05);
        log.debug("{}", user05.getName());
        log.debug("{}", user05.getId());
        log.debug("{}", user05.getInsertTime());
    }

Spring-data-jpa并不提供强制从同步数据的refresh()方法。必须通过扩展spring-data实现

写扩展接口

package com.yanyu.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
    void refresh(T t);
}

@NoRepositoryBean 是 Spring Data JPA 中的一个注解,用于表示某个接口不应该被作为仓库(repository)来使用。

在 Spring Data JPA 中,我们可以通过定义一个接口并继承 JpaRepositoryCrudRepository 等基础仓库接口来创建自定义的仓库。然而,有时候我们可能有一些接口并不需要作为仓库来使用,而是仅仅作为其他组件之间的数据传输对象(DTO)。在这种情况下,我们可以使用 @NoRepositoryBean 注解来标记该接口,告诉 Spring Data JPA 不要将其视为一个仓库。

 实现扩展

package com.yanyu.repository.impl;

import com.yanyu.repository.BaseRepository;
import jakarta.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;



public class BaseRespostoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
    private EntityManager manager;
    public BaseRespostoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.manager = entityManager;
    }

    @Override
    public void refresh(T t) {
        manager.refresh(t);
    }
}
package com.yanyu.example05.baserepository.repository;

import com.yanyu.example05.baserepository.entity.User05;
import com.yanyu.repository.BaseRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface User05Repository extends BaseRepository<User05, Integer> {
}

环境配置

package com.yanyu.example05.baserepository.repository;

import com.yanyu.example05.baserepository.entity.User05;
import com.yanyu.repository.BaseRepository;
import com.yanyu.repository.impl.BaseRespostoryImpl;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.stereotype.Repository;

@Repository
@EnableJpaRepositories(repositoryBaseClass = BaseRespostoryImpl.class)
public interface User05Repository extends BaseRepository<User05, Integer> {
}

@EnableJpaRepositories(repositoryBaseClass = BaseRespostoryImpl.class) 是 Spring Data JPA 中的一个注解,用于启用 JPA 仓库。

其中 repositoryBaseClass 属性指定了仓库的基类,即所有自定义的仓库都需要继承该基类。在这个例子中,BaseRespostoryImpl 是自定义的仓库基类。

测试

 @Test
    public void test_refresh2() {
        User05 user05 = new User05();
        user05.setName("b");
        user05Repository.save(user05);
        user05Repository.refresh(user05);
//        user05Repository.refresh(user05);
        log.debug("{}", user05.getName());
        log.debug("{}", user05.getId());
        log.debug("{}", user05.getInsertTime());
    }

3.5关联属性加载策略Fatch

默认,当查询加载对象时,除关联属性外全部直接加载。整型/字符串/日期时间等等

实体类

package com.yanyu.example06.fetch.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;



@Data
@NoArgsConstructor
@Entity
public class Address06 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String detail;
    @ManyToOne
    private User06 user;
}
package com.yanyu.example06.fetch.entity;

import lombok.Data;
import lombok.NoArgsConstructor;

import jakarta.persistence.*;
import java.util.List;

@Data
@NoArgsConstructor
@Entity
public class User06 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @OneToMany(mappedBy = "user")
    private List<Address06> addresses;
}

持久层

package com.yanyu.example06.fetch.repository;


import com.yanyu.example06.fetch.entity.Address06;
import com.yanyu.repository.BaseRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface Address06Repository extends BaseRepository<Address06, Integer> {
}
package com.yanyu.example06.fetch.repository;

import com.yanyu.example06.fetch.entity.User06;
import com.yanyu.repository.BaseRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface User06Repository extends BaseRepository<User06, Integer> {
}

服务

package com.yanyu.example06.fetch.service;

import com.yanyu.example06.fetch.entity.Address06;
import com.yanyu.example06.fetch.entity.User06;
import com.yanyu.example06.fetch.repository.Address06Repository;
import com.yanyu.example06.fetch.repository.User06Repository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@Slf4j
public class User06Service {
    @Autowired
    private User06Repository user06Repository;
    @Autowired
    private Address06Repository address06Repository;

    public void addUser(User06 user06) {
        user06Repository.save(user06);
    }
    public void addAddress(Address06 address06) {
        address06Repository.save(address06);
    }

    public User06 getUser(int id) {
        User06 u = user06Repository.findById(id).orElse(null);
        return u;
    }

    public Address06 getAddress(int id) {
        return address06Repository.findById(id).orElse(null);
    }
}

测试1

@Test
    public void init() {
        User06 u = new User06();
        u.setName("BO");
        user06Service.addUser(u);

        Address06 a = new Address06();
        a.setDetail("956");
        a.setUser(u);
        user06Service.addAddress(a);

        Address06 a2 = new Address06();
        a2.setDetail("925");
        a2.setUser(u);
        user06Service.addAddress(a2);
    }

测试2

  @Test
    public void test_fetch() {
        log.debug(user06Service.getAddress(1).getUser().getName());
    }

    @Test
    public void test_fetch2() {
        user06Service.getUser(1)
                .getAddresses()
                .forEach(a -> log.debug(a.getDetail()));
    }

 

3.6级联操作

在 JPA (Java Persistence API) 中,cascade 属性用于指定实体关联关系中的级联操作。当对一个实体执行某些操作(如保存、更新、删除)时,这些操作可以级联到与之关联的其他实体上。

JPA 提供了一系列的 CascadeType 枚举值来定义级联行为,包括:

  • ALL: 所有操作都级联。
  • MERGE: 合并操作会级联。
  • PERSIST: 持久化操作会级联。
  • REFRESH: 刷新操作会级联。
  • REMOVE: 删除操作会级联。
  • DETACH: 分离操作会级联。

通常,cascade 属性用在 @OneToOne@OneToMany@ManyToOne@ManyToMany 注解中,以定义实体间的关系如何级联

 

·不建议直接在实体类声明使用级联操作,应通过业务逻辑操作手动完成相关持久化操作

四、复杂查询

4.1JPQL

JPQL(Java Persistence Query Language)是一种用于查询Java持久化对象的语言,类似于SQL。它允许开发人员编写针对Java对象的查询,而无需直接操作数据库表和列。

 创建实体类

 SELECT和FROM

JPQL语句中的SELECT和FROM关键字用于指定查询的字段和表。

SELECT关键字后面跟要查询的字段,可以是单个字段或多个字段,用逗号分隔。例如:

String jpql = "SELECT p.name, p.age FROM Person p";

这个查询将返回Person实体中所有记录的name和age字段。

FROM关键字后面跟要查询的表名,可以是一个或多个表,用逗号分隔。例如:

String jpql = "SELECT p.name, c.name FROM Person p, Car c WHERE p.id = c.ownerId";

这个查询将返回Person和Car两个表中所有记录的name字段,其中Person表的id字段与Car表的ownerId字段相等。

其他语法

 

函数

joins

格式必须为“实体.属性” 

package com.example.jpaexamples.example07.jpql.repository;

import com.example.jpaexamples.example07.jpql.entity.Address07;
import com.example.jpaexamples.example07.jpql.entity.User07;
import com.example.jpaexamples.repository.BaseRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface Address07Repository extends BaseRepository<Address07, Integer> {
    @Query("select a.user from Address07 a where a.detail=:detail")
    List<User07> list(@Param("detail") String detail);

    @Query("select a.user from Address07 a where a.user.id=:uid")
    User07 find(@Param("uid") int uid);

    @Query("select a.user from Address07 a where a.detail=:detail and a.user.name=:uname")
    List<User07> list(@Param("detail") String detail, @Param("uname") String uname);

    @Query("from Address07 a where a.detail=:detail")
    Page<Address07> list(@Param("detail") String detail, Pageable pageable);

}

Spring-data支持基手属性名称自动创建查询。即,一仅按约定编写查询方法,无需编写PQL语句 

4.2Pagination分页

import java.util.List;

@Repository
public interface Address07Repository extends BaseRepository<Address07, Integer> {
   

    @Query("from Address07 a where a.detail=:detail")
    Page<Address07> list(@Param("detail") String detail, Pageable pageable);

}

测试

@Test
    public void test_page() {
        address07Repository.list("956", PageRequest.of(0, 20))
                .getContent()
                .forEach(address07 -> log.debug("{}", address07.getUser().getName()));
    }

 Pageable是Spring Data JPA中的一个接口,用于分页查询。它包含了分页信息,如当前页码、每页显示的记录数等。要使用Pageable,需要在Repository接口中定义一个返回Page的方法,并在方法参数中添加Pageable类型的参数。例如:

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findAll(Pageable pageable);
}

在Service层中,可以通过调用这个方法来实现分页查询:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

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

    public Page<User> findUsers(int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }
}

在这个例子中,`findUsers`方法接收两个参数:`page`表示当前页码,`size`表示每页显示的记录数。通过`PageRequest.of(page, size)`创建一个`Pageable`对象,然后将其传递给`userRepository.findAll()`方法进行分页查询。

 4.3Modifying


@Repository
public interface User07Repository extends BaseRepository<User07, Integer> {
   

    @Modifying
    @Query("update User07 u set u.name=:newname where u.id=:id")
    int update(@Param("id") int id, @Param("newname") String name);

}

 测试

 @Transactional
    @Rollback(value = false)
    @Test
    public void test_update() {
        user07Repository.update(1, "ZHANG");
    }

需要启动事务

@Modifying是Spring Data JPA中的一个注解,用于标记在Repository接口中的方法需要进行数据修改操作。在使用@Query注解时,如果查询语句中包含UPDATE、INSERT或DELETE等修改数据的SQL语句,就需要在对应的方法上添加@Modifying注解。

例如,下面的代码演示了如何在UserRepository接口中使用@Modifying注解:

import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;

public interface UserRepository extends CrudRepository<User, Long> {

    @Modifying
    @Transactional
    @Query("update User u set u.name = ?1 where u.id = ?2")
    int updateUserName(String name, Long id);
}

在上面的代码中,updateUserName方法使用了@Modifying注解,表示该方法需要进行数据修改操作。同时,由于该方法涉及到事务操作,所以还需要添加@Transactional注解

 五、事务和并发

5.1相关概念

5.2乐观锁

实体类

package com.yanyu.example08.optimistic.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;



@Data
@NoArgsConstructor
@Entity
public class User08 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @Version
    private int version;
}

@Version是Spring Data JPA中的一个注解,用于标记实体类中的某个字段为版本控制字段。在JPA中,版本控制字段通常用于实现乐观锁机制,防止多个事务同时修改同一条记录时出现数据不一致的情况。

当多个事务同时修改同一条User记录时,如果其中一个事务先提交,那么它的version值会加1,而其他事务在提交时会发现version值已经发生了变化,从而抛出OptimisticLockingFailureException异常,提示用户数据已经被其他事务修改过。

持久层

package com.yanyu.example08.optimistic.repository;


import com.yanyu.example08.optimistic.entity.User08;
import com.yanyu.repository.BaseRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface User08Repository extends BaseRepository<User08, Integer> {
}

业务处理

package com.yanyu.example08.optimistic.service;


import com.yanyu.example08.optimistic.entity.User08;
import com.yanyu.example08.optimistic.repository.User08Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional()
public class User08Service {
    @Autowired
    private User08Repository user08Repository;

    public void addUser(User08 u) {
        user08Repository.save(u);
    }

    public void updateUser(int id, String nname) {
        user08Repository.findById(id)
                .orElse(null)
                .setName(nname);
    }
}

@Transactional(isolation = )是Spring框架中的一个注解,用于声明事务的隔离级别。在数据库中,事务的隔离级别是指多个事务同时执行时,它们之间的相互影响程度。

例如,当两个事务同时修改同一条记录时,如果没有设置事务隔离级别,可能会出现脏读、不可重复读和幻读等问题。而设置了事务隔离级别后,可以保证事务的并发执行不会对其他事务造成影响。

@Transactional(isolation = )中的参数可以是以下四个值之一:

  • Isolation.DEFAULT:使用数据库默认的隔离级别;
  • Isolation.READ_UNCOMMITTED:允许读取未提交的数据;
  • Isolation.READ_COMMITTED:只允许读取已提交的数据;
  • Isolation.REPEATABLE_READ:在同一个事务中多次读取同一行数据时,返回的结果是一致的;
  • Isolation.SERIALIZABLE:最高的隔离级别,完全串行化执行,避免了脏读、不可重复读和幻读等问题。

 测试

package com.yanyu.example08.optimistic;

import com.yanyu.example08.optimistic.entity.User08;
import com.yanyu.example08.optimistic.service.User08Service;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest // 表示这是一个Spring Boot的测试类
@Slf4j // 使用Lombok提供的日志功能
public class OptimisticTest {
    @Autowired // 自动注入User08Service实例
    private User08Service user08Service;

    // 初始化方法,用于添加一个用户
    @Test
    public void init() {
        User08 u = new User08();
        u.setName("BO");
        user08Service.addUser(u);
    }

    // 测试更新用户的方法,使用多线程并发更新同一个用户
    @Test
    public void test_updateUser() throws InterruptedException {
        new Thread(() -> { // 创建第一个线程
            user08Service.updateUser(1, "aaa"); // 更新用户ID为1的用户的名字为"aaa"
            try {
                Thread.sleep(1000); // 让线程休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start(); // 启动第一个线程
        new Thread(() -> { // 创建第二个线程
            user08Service.updateUser(1, "bbb"); // 更新用户ID为1的用户的名字为"bbb"
            try {
                Thread.sleep(1000); // 让线程休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start(); // 启动第二个线程

        Thread.sleep(2000); // 让主线程休眠2秒,确保两个子线程都执行完毕
    }
}

5.3悲观锁 

实体类

package com.yanyu.example09.pessimistic.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;



@Data
@NoArgsConstructor
@Entity
public class User09 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private int balance;
}

 持久层

package com.yanyu.example09.pessimistic.repository;


import com.yanyu.example09.pessimistic.entity.User09;
import com.yanyu.repository.BaseRepository;
import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;



@Repository
public interface User09Repository extends BaseRepository<User09, Integer> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("from User09 u where u.id=:id")
    User09 find(@Param("id") int id);
}

@Lock 是 Java Persistence API (JPA) 中的一个注解,用于在事务中锁定数据库记录。当使用 @Lock 注解时,JPA 会在事务开始时锁定指定的记录,并在事务结束时释放锁。这样可以确保在事务期间,其他事务无法修改被锁定的记录,从而保证数据的一致性。

在这个例子中,finde 方法使用了 @Lock 注解,并指定了锁模式为 =PESSIMISTIC_WRITE。这意味着在事务期间,其他事务将无法修改被锁定的记录。

业务处理

package com.yanyu.example09.pessimistic.service;


import com.yanyu.example09.pessimistic.entity.User09;
import com.yanyu.example09.pessimistic.repository.User09Repository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@Slf4j
public class User09Service {
    @Autowired
    private User09Repository user09Repository;

    public void addUser(User09 u) {
        user09Repository.save(u);
    }

    public void buy(int uid, int expense) {
        User09 u = user09Repository.find(1);
        int newBanance = u.getBalance() - expense;
        if (newBanance >= 0) {
            u.setBalance(newBanance);
            log.debug("花费后余额:{}", newBanance);
        } else {
            log.debug("余额不足");
        }

    }
}

测试

package com.yanyu.example09.pessimistic;

// 导入相关类和接口
import com.yanyu.example09.pessimistic.entity.User09;
import com.yanyu.example09.pessimistic.service.User09Service;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

// 使用SpringBootTest注解,表示这是一个Spring Boot测试类
@SpringBootTest
// 使用Slf4j注解,简化日志操作
@Slf4j
public class PessimisticTest {
    // 自动注入User09Service实例
    @Autowired
    private User09Service user09Service;

    // 初始化方法,用于添加一个用户
    @Test
    public void init() {
        User09 user09 = new User09();
        user09.setName("BO");
        user09.setBalance(1000);
        user09Service.addUser(user09);
    }

    // 测试购买方法,使用多线程并发购买商品
    @Test
    public void test_buy() throws InterruptedException {
        // 创建第一个线程,执行购买操作
        new Thread(() -> {
            user09Service.buy(1, 800);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        // 创建第二个线程,执行购买操作
        new Thread(() -> {
            user09Service.buy(1, 800);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 主线程休眠2秒,确保两个子线程都执行完毕
        Thread.sleep(2000);
    }
}

5.4没有约束

package com.example.jpaexamples.example11.noconstraint.entity;

import lombok.Data;

import javax.persistence.*;

@Data // 使用Lombok注解,自动生成getter和setter方法
@Entity // 表示这是一个实体类
// 不使用物理外键,也应加索引
//@Table(indexes = {@Index(columnList = "user_id")})
public class Address11 {
    @Id // 表示该字段是主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略为自增
    private int id; // 定义一个整型变量id,作为地址的主键
    private String detail; // 定义一个字符串变量detail,表示地址的详细信息
    @ManyToOne // 表示与User11实体类之间存在多对一的关系
    // 无外键约束
    @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
    private User11 user; // 定义一个User11类型的变量user,表示地址所属的用户
}

@JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))表示在数据库表中没有外键约束。其中,@JoinColumn是JPA注解,用于指定关联的外键列;foreignKey属性用于设置外键约束;@ForeignKey注解用于定义外键约束的属性;value属性设置为ConstraintMode.NO_CONSTRAINT表示不使用外键约束。

@ForeignKey注解的value属性用于指定外键约束的名称。除了name属性之外,@ForeignKey注解还可以设置以下属性:

  • foreignKeyDefinition:用于定义外键约束的具体定义,例如"ON DELETE CASCADE"等。
  • constraintDefinition:用于定义外键约束的定义,例如"FOREIGN KEY (user_id) REFERENCES user(id)"等。
package com.example.jpaexamples.example11.noconstraint.entity;

import lombok.Data;

import javax.persistence.*;
import java.util.List;

@Data
@Entity
public class User11 {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @Transient
    private List<Address11> addresses;
}

 @Transient是JPA注解,用于表示某个属性不需要持久化到数据库中。当实体类中的某个属性不需要被映射到数据库表中时,可以使用@Transient注解来标记该属性

表示它不会被映射到数据库表中。这样,在执行数据库操作时,password属性将不会被保存或查询。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1497916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云服务器购买搭建

1.首先在阿里云官网购买ESC云服务器 2.在域名处&#xff0c;购买域名&#xff0c;申请免费的SSL证书&#xff0c;但是这个证书需要去备案&#xff0c;这个周期比较长。 3.登录你的服务器就可以安装你需要的环境&#xff0c;进行开发&#xff0c;当然使用docker很好用。 以下…

【开源物联网平台】FastBee认证方式和MQTT主题设计

&#x1f308; 个人主页&#xff1a;帐篷Li &#x1f525; 系列专栏&#xff1a;FastBee物联网开源项目 &#x1f4aa;&#x1f3fb; 专注于简单&#xff0c;易用&#xff0c;可拓展&#xff0c;低成本商业化的AIOT物联网解决方案 目录 一、接入步骤 1.1 设备认证 1.2 设备交…

【Leetcode】3028.边界上的蚂蚁

题目描述 思路 题目中要求我们返回 蚂蚁返回到边界的次数。简单来想&#xff0c;就是蚂蚁原来的位置的一维坐标为0&#xff0c;然后经过&#xff0c;若干次移动&#xff0c;统计有几次坐标再次变为0的个数。 我们利用前缀和&#xff0c;像定义一个数组&#xff0c;算出前缀和数…

贪心算法详解及机器人运动应用Demo

一、引言 贪心算法是一种在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最好或最优的算法。贪心算法在有最优子结构的问题中尤为有效。今天&#xff0c;我们将通过一个机器人运动的Demo来详细解析贪心算…

网络编程作业day7

作业项目&#xff1a;基于UDP的聊天室 服务器代码&#xff1a; #include <myhead.h>//定义客户信息结构体 typedef struct magtye {char type; //消息类型char name[100]; //客户姓名char text[1024]; //客户发送聊天信息 }msg_t;//定义结构体存储…

基于YOLOv5的驾驶员疲劳驾驶行为​​​​​​​检测系统

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文主要内容:详细介绍了疲劳驾驶行为检测整个过程&#xff0c;从数据集到训练模型到结果可视化分析。 博主简介 AI小怪兽&#xff0c;YOLO骨灰级玩家&#xff0c;1&#xff09;YOLOv5、v7、v8优化创新&#xff0c;轻松涨点和模型轻量…

Java特性之设计模式【过滤器模式】

一、过滤器模式 概述 ​ 过滤器模式&#xff08;Filter Pattern&#xff09;或标准模式&#xff08;Criteria Pattern&#xff09;是一种设计模式&#xff0c;这种模式允许开发人员使用不同的标准来过滤一组对象&#xff0c;通过逻辑运算以解耦的方式把它们连接起来。这种类型的…

几何工具的使用

Geometry - Creation 创建几何 CogCreateCircleTool&#xff1a;创建圆CogCreateEllipseTool:创建椭圆CogCreateLineBisectPointsTool&#xff1a;带有两个点的平行线CogCreateLineParallelTool:在某一点创建某条线的平行线CogCreateLinePerpendicularTool:在某一点创建某条线…

STL中push_back和emplace_back效率的对比

文章目录 过程对比1.通过构造参数向vector中插入对象&#xff08;emplace_back更高效&#xff09;2.通过插入实例对象&#xff08;调用copy函数&#xff09;3.通过插入临时对象&#xff08;调用move函数&#xff09; 效率对比emplace_back 的缺点 我们以STL中的vector容器为例。…

力扣每日一题 找出字符串的可整除数组 数论

Problem: 2575. 找出字符串的可整除数组 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 灵神题解 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( 1 ) O(1) O(1) Code class Solution {public int[] divisibilityArray(String word, int m){in…

外包干了一周,技术明显倒退。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;2019年我通过校招踏入了南京一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

【JavaEE初阶】 JVM 运行时数据区简介

文章目录 &#x1f343;前言&#x1f332;堆&#xff08;线程共享&#xff09;&#x1f384;Java虚拟机栈&#xff08;线程私有&#xff09;&#x1f38b;本地方法栈&#xff08;线程私有&#xff09;&#x1f333;程序计数器&#xff08;线程私有&#xff09;&#x1f334;方法…

ospf虚链路实验简述

1、ospf虚链路实验简述 ospf虚链路配置 为解决普通区域不在骨干区域旁&#xff0c;通过配置Vlink-peer实现不同区域网络设备之间建立逻辑上的连接。 实验拓扑图 r1: sys sysname r1 undo info enable int loopb 0 ip add 1.1.1.1 32 ip add 200.200.200.200 32 quit int e0/0/…

京东老矣,尚能饭否?

图片&#xff5c;《冰与火之歌》截图 ©自象限原创 作者丨程心 编辑丨罗辑 从2004年1月&#xff0c;京东正式涉足电商至今&#xff0c;整整二十年过去了。 2024年3月6日&#xff0c;京东发布了2023年第四季度及全年财报。数据显示&#xff0c;2023Q4京东收入3061亿元人民…

【软件测试】如何申请专利?

一、专利类型 在软件测试领域&#xff0c;可以申请发明专利、实用新型专利和外观设计专利。其中&#xff0c;发明专利是最常见的专利类型&#xff0c;它保护的是软件测试方法、系统和装置等技术方案。 二、申请专利的条件 申请专利需要满足新颖性、创造性和实用性三个条件。…

【Linux】开始使用gdb吧!

开始使用gdb吧&#xff01; 1 下载安装2 开始使用3 实践运用补充一下 print 的 功能 &#xff08;类似监视窗口的作用&#xff09;和显示堆栈的功能Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff01;&am…

【力扣白嫖日记】1070.产品销售分析III

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1070.产品销售分析III 表&#xff1a;Sales 列名类型sale_idintproduct_idintyearintquantityintpriceint …

01背包问题 刷题笔记

思路 dp 用f[i][j]来表示当体积为j时 考虑前i件物品可以获得的 最大值 记住f[i][j]本身是个价“价值” 考虑两种状态 是否将第i件物品放入背包里面 将背包的体积从小到大递增来进行考虑 首先 考虑条件 如果当前增加的体积放不下下一件物品 则该体积 可以获得的最大值可以直接…

Easticsearch性能优化之索引优化

Easticsearch性能优化之索引优化 一、合理的索引设计二、合理的分片和副本三、合理的索引设置 对于性能优化&#xff0c;Elasticsearch&#xff08;以下简称ES&#xff09;的索引优化是提高性能的关键因素之一。合理的设计索引&#xff0c;合理的分片和副本以及合理的缓存设置等…

Truenas入门级教程

Truenas入门教程 前言&#xff1a;系统相关配置 采用I3 4160&#xff0c;采用了2块500G的硬盘&#xff0c;内存为8G&#xff0c;两个网卡只用了其中一个&#xff0c;系统安装的是core版本 硬件采用DELL3020MT机箱&#xff0c;自带3个SATA网口&#xff0c;后期网口不够&#…