一、介绍
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: rootpassword: sql:init:mode: alwaysjpa:show-sql: true//表示在控制台输出执行的SQL语句。hibernate:ddl-auto: update//Hibernate在启动时如何自动创建、更新或验证数据库表结构properties:hibernate:dialect: org.hibernate.dialect.MySQL8Dialect //指定了Hibernate使用的数据库方言,这里使用的是MySQL 8的方言。logging:level:sql: debugcom:example: debugpattern: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) // 表示该字段在数据库表中的长度为16private UUID id; // 用户IDprivate 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 {@Autowiredprivate EntityManager manager;@Testpublic 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 {@Testpublic 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
@Testpublic 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
@Testpublic 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 中,一个实体类可以与另一个实体类之间建立一对一的关系。例如,假设我们有两个实体类:
User
和Profile
,每个用户可以拥有一个个人资料,而每个个人资料只能属于一个用户。在这种情况下,我们可以使用@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;@ManyToOneprivate Student04 student;@ManyToOneprivate 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 {@Autowiredprivate EntityManager manager;@Testpublic void test_init() {// 初始学生1Student04 s = new Student04();s.setName("BO");manager.persist(s);// 初始化学生2Student04 s2 = new Student04();s2.setName("SUN");manager.persist(s2);//初始化课程1Course04 c = new Course04();c.setName("Web框架");manager.persist(c);}@Testpublic 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
@Autowiredprivate User05Repository user05Repository;@AutowiredEntityManager manager;@Testpublic void test_addUser() {User05 user05 = new User05();user05.setName("a");user05Repository.save(user05);user05.setName("b");}
测试2
@Testpublic void test_addUser2() {User05 user05 = user05Repository.findById(1).orElse(null);log.debug("{}", user05.getInsertTime());}
3.4BaseRepository
@Testpublic 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 中,我们可以通过定义一个接口并继承
JpaRepository
或CrudRepository
等基础仓库接口来创建自定义的仓库。然而,有时候我们可能有一些接口并不需要作为仓库来使用,而是仅仅作为其他组件之间的数据传输对象(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;}@Overridepublic 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
是自定义的仓库基类。
测试
@Testpublic 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;@ManyToOneprivate 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 {@Autowiredprivate User06Repository user06Repository;@Autowiredprivate 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
@Testpublic 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
@Testpublic void test_fetch() {log.debug(user06Service.getAddress(1).getUser().getName());}@Testpublic 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);}
测试
@Testpublic 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 {@Autowiredprivate 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)@Testpublic 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;@Versionprivate 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 {@Autowiredprivate 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;// 初始化方法,用于添加一个用户@Testpublic void init() {User08 u = new User08();u.setName("BO");user08Service.addUser(u);}// 测试更新用户的方法,使用多线程并发更新同一个用户@Testpublic 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 {@Autowiredprivate 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实例@Autowiredprivate User09Service user09Service;// 初始化方法,用于添加一个用户@Testpublic void init() {User09 user09 = new User09();user09.setName("BO");user09.setBalance(1000);user09Service.addUser(user09);}// 测试购买方法,使用多线程并发购买商品@Testpublic 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;@Transientprivate List<Address11> addresses;
}
@Transient
是JPA注解,用于表示某个属性不需要持久化到数据库中。当实体类中的某个属性不需要被映射到数据库表中时,可以使用@Transient
注解来标记该属性表示它不会被映射到数据库表中。这样,在执行数据库操作时,
password
属性将不会被保存或查询。