JPA 常见用法
一、主键自增长
1.1 @Id: 指定为主键
1.2 @GeneratedValue:指定主键自增长类型
1.2.1 strategy:主键自增长方式
-
GenerationType.TABLE: 通过一张特定的表来维护
@Id @GeneratedValue( strategy = GenerationType.TABLE, generator="pk_gen") @TableGenerator( name ="pk_gen", // 表示该表主键生成策略的名称 table="tb_generator", // 表示表生成策略所持久化的表名 pkColumnName="gen_name", // 表示在持久化表中,该主键生成策略所对应键值的名称 valueColumnName="gen_value", // 表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加 pkColumnValue="PAYABLEMOENY_PK", // 表示在持久化表中,该生成策略所对应的主键 initialValue=0, // 表示主键初识值,默认为0。 allocationSize=1 // 每次主键值增加的大小 )
-
GenerationType.SEQUENCE:通过序列来维护
通常要与注解 @SequenceGenerator 搭配使用
Mysql 不支持序列、Oracle,PostgreSQL,DB2支持
@Id @GeneratedValue( strategy = GenerationType.SEQUENCE, generator="workflow_node_seq") @SequenceGenerator( name="workflow_node_seq", // 表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中。 sequenceName="workflow_node_id_seq", // 表示生成策略用到的数据库序列名称 initialValue=0, // 表示主键初识值,默认为0。 allocationSize=1 // 每次主键值增加的大小 )
-
GenerationType.IDENTITY:通过数据库本身来维护,数据库主键自增长
大部分数据库均支持,但 sql 设置方式可能略有不同
-
GenerationType.AUTO:持久化引擎来维护自增长
缺点:多张表使用同一个增长序列
1.2.2generator: 自增长维护规则名称
二、表关联
2.1 @OneToOne: 一对一关系
2.1.1 单向关联
-
User
@Data @Entity @Table(name = "t_user") public class User { // 主表 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @OneToOne @JoinColumn(name = "user_info_id",referencedColumnName = "id") // referencedColumnName 可省略,默认使用主键 private UserInfo userInfo; }
-
UserInfo
@Data @Entity @Table(name = "t_user_info") public class UserInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; }
2.1.2 双向关联
-
User
@Data @Entity @Table(name = "t_user") @ToString(exclude = "userInfo") // toString 排除 userInfo 属性,解决 死循环问题 public class User { // 从表 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @OneToOne @JoinColumn(name = "user_info_id",referencedColumnName = "id") // referencedColumnName 可省略,默认使用主键 private UserInfo userInfo; }
-
UserInfo
@Data @Entity @Table(name = "t_user_info") @ToString(exclude = "user") public class UserInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @OneToOne @JoinColumn(name = "user_id") private User user; }
2.2 @ManyToOne & OneToMany:多对一和一对多关系
2.2.1 单向关联
-
@ManyToOne
Student
@Data @Entity @Table(name = "t_student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @ManyToOne @JoinColumn(name = "group_id") private Group group; }
Group
@Data @Entity @Table(name = "t_group") public class Group { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; }
-
@OneToMany
Group
@Data @Entity @Table(name = "t_group") public class Group { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // 默认 Lazy 获取不到, 关联属性为 集合时必须 EAGER, 或者在查询方法上加@Transactional(readOnly = true) 注解 @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "group_id") List<Student> studentList; }
Student
@Data @Entity @Table(name = "t_student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; }
2.2.2 双向关联
-
Group
@Data @Entity @Table(name = "t_group") @ToString(exclude = "studentList") // toString 排除 studentList 属性,解决 死循环问题 public class Group { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // 默认 Lazy 获取不到, 关联属性为 集合时必须 EAGER,或者在查询方法上加@Transactional(readOnly = true) 注解 @OneToMany(mappedBy = "group", fetch = FetchType.EAGER) List<Student> studentList; }
-
Student
@Data @Entity @Table(name = "t_student") @ToString(exclude = "group") // toString 排除 group 属性,解决 死循环问题 public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @ManyToOne @JoinColumn(name = "group_id") private Group group; }
2.3 @ManyToMany:多对多关系
-
单向关联
User
@Data @Entity @Table(name = "t_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // 默认 Lazy 获取不到, 关联属性为 集合时必须 EAGER,或者在查询方法上加@Transactional(readOnly = true) 注解 @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "t_user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private List<Role> roles; }
Role
@Data @Entity @Table(name = "t_role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; }
-
双向关联
User
@Data @Entity @Table(name = "t_user") @ToString(exclude = "roles") // toString 排除 roles 属性,解决 死循环问题 public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // 默认 Lazy 获取不到, 关联属性为 集合时必须 EAGER,或者在查询方法上加@Transactional(readOnly = true) 注解 @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "t_user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private List<Role> roles; }
Role
@Data @Entity @Table(name = "t_role") @ToString(exclude = "userList") // toString 排除 userList 属性,解决 死循环问题 public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // 默认 Lazy 获取不到, 关联属性为 集合时必须 EAGER,或者在查询方法上加@Transactional(readOnly = true) 注解 @ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER) private List<User> userList; }
2.4 父子表关联
适用于 拆表
2.4.1 单表关联:@SecondaryTable
@Data
@Entity
@Table(name = "t_student")
@SecondaryTable(name = "t_student_info", // 子表表名
pkJoinColumns = @PrimaryKeyJoinColumn(name = "student_id") // 关联主键
)
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String sex;
private String age;
@Column(table = "t_student_info") // 该字段在子表 t_student_info 中
private String birthDay;
}
2.4.2 多表关联:@SecondaryTables
@Data
@EqualsAndHashCode(callSuper = false)
@Entity
@Table(name = "t_product")
@SecondaryTables({
@SecondaryTable(name = "t_product_info", pkJoinColumns = @PrimaryKeyJoinColumn(name = "product_id")),
@SecondaryTable(name = "t_product_detail", pkJoinColumns = @PrimaryKeyJoinColumn(name = "product_id"))
})
public class Product extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(table = "t_product_info")
private BigDecimal prices;
@Column(table = "t_product_detail")
private String brand;
}
三、自动填充
3.1 主类加 @EnableJpaAuditing
@EnableJpaAuditing
@SpringBootApplication
public class JpaMain {
public static void main(String[] args) {
SpringApplication.run(JpaMain.class,args);
}
}
3.2 Config 中 增加 bean
@Configuration
public class AutoFillConfig {
@Bean
public AuditorAware<String> auditorAware() {
AuditorAware<String> aa = () -> {
// TODO 获取当前用户名
String username = "测试";
return Optional.of(username);
};
return aa;
}
}
3.3 自动填充抽象类
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity implements Serializable {
// 创建人
@CreatedBy
@Column(length = 50)
private String createdBy;
// 创建时间
// @CreatedDate
@CreationTimestamp
private Date createdTime;
// 更新人
@LastModifiedBy
@Column(length = 50)
private String updatedBy;
// 更新时间
// @LastModifiedDate
@UpdateTimestamp
private Date updatedTime;
}
四、枚举
枚举:用于避免拼写错误,一定程度上,加密数据,存储原则:
- 数据库:枚举的真实含义
- Json:枚举的 代表编号
4.1 注解方式
4.1.1 枚举类
@Getter // 数据库存储时
@AllArgsConstructor
public enum GradeEnum{
UNKNOWN("未知", 0),
SECONDARY("中学", 2),
PRIMARY("小学", 1),
HIGH("高中", 3);
private final String name;
@JsonValue // 转 JSON 时的字段
private final Integer value;
}
4.1.2 使用
@Data
@Entity
@Table(name = "t_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 枚举,数据库存储时,存的值为 UNKNOWN、PRIMARY、SECONDARY、HIGH
@Enumerated(value = EnumType.STRING)
private GradeEnum grade;
}
4.2 专用转换类
4.2.1 枚举类
@Getter
@AllArgsConstructor
public enum GradeEnum {
UNKNOWN("未知", 0),
SECONDARY("中学", 2),
PRIMARY("小学", 1),
HIGH("高中", 3);
private final String name;
@JsonValue
private final Integer value;
}
4.2.2 转换类
@Converter(autoApply = true)
public class EnumConverter implements AttributeConverter<GradeEnum, String> {
/**
* 此方法告诉jpa数据库里存的值
*/
@Override
public String convertToDatabaseColumn(GradeEnum gradeEnum) {
if (gradeEnum == null) {
return null;
}
return gradeEnum.getName();
}
/**
* 此方法告诉Java从数据库里取出的值对应的是哪个enum值
*/
@Override
public GradeEnum convertToEntityAttribute(String value) {
if (value == null) {
return null;
}
for (GradeEnum gradeEnum : GradeEnum.values()) {
if (gradeEnum.getName().equals(value)) {
return gradeEnum;
}
}
return GradeEnum.UNKNOWN;
}
}
4.2.3 使用
@Data
@Entity
@Table(name = "t_product")
public class Product extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private GradeEnum grade;
}
4.3 通用转换类
4.3.1 枚举类
-
接口 BaseEnum
// 枚举通用接口 public interface BaseEnum<V> { // 用于显示的枚举名称 String getName(); // 数据库存储数值 V getCode(); }
-
枚举类
@Getter @AllArgsConstructor public enum GradeEnum implements BaseEnum<Integer>{ UNKNOWN(0, "未知"), SECONDARY(2, "中学"), PRIMARY(1, "小学"), HIGH(3, "高中"); private final Integer code; @JsonValue private final String name; // 静态内部类,这里也可不重写方法 public static class Converter extends BaseEnumConverter<GradeEnum, Integer>{ @Override // 重写方法,补充 baseEnum 为空 时插入枚举类型 为 UNKNOWN(0, "未知") public Integer convertToDatabaseColumn(BaseEnum<Integer> baseEnum) { try { return super.convertToDatabaseColumn(baseEnum); }catch (Exception e){ return 0; } } @Override // 重写方法,补充将数据库中读取到的 无法转换的类型,默认转换为 UNKNOWN(0, "未知") public BaseEnum<Integer> convertToEntityAttribute(Integer value) { try { return super.convertToEntityAttribute(value); }catch (Exception e){ return GradeEnum.UNKNOWN; } } } }
4.3.2 转换类
// 抽象类,默认通用转换类
public abstract class BaseEnumConverter<X extends BaseEnum<V>,V> implements AttributeConverter<BaseEnum<V>, V> {
private Class<X> clazz;
private Method method;
@SuppressWarnings("unchecked")
public BaseEnumConverter() {
this.clazz = (Class<X>) (((ParameterizedType) this.getClass().getGenericSuperclass())
.getActualTypeArguments())[0];
try {
method = clazz.getMethod("values");
} catch (Exception e) {
throw new RuntimeException("can't get values method from " + clazz);
}
}
/**
* 数据库存储的值
* @param baseEnum 枚举对象
* @return 数据库中存放的值
*/
@SuppressWarnings("unchecked")
@Override
public V convertToDatabaseColumn(BaseEnum<V> baseEnum) {
return baseEnum.getCode();
}
/**
* 数据库中的值转换为 枚举对象
* @param value 数据库中的值
* @return 枚举对象
*/
@SuppressWarnings("unchecked")
@Override
public BaseEnum<V> convertToEntityAttribute(V value) {
try {
X[] enums = (X[]) method.invoke(null);
for (X x: enums){
if (x.getCode().equals(value)){
return x;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
throw new IllegalArgumentException("unknown db enum attribute: " + value);
}
}
4.3.3 使用
@Data
@EqualsAndHashCode(callSuper = false)
@Entity
@Table(name = "t_product")
public class Product extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@Convert(converter = GradeEnum.Converter.class)
private GradeEnum grade;
}
五、分页
5.1定义统一分页接口
public interface AppPage<T> {
/**
* 当前页页码
*/
long getPageNum();
/**
* 每页条数
*/
long getPageSize();
/**
* 总条数
*/
long getTotal();
/**
* 总页数
*/
long getTotalPages();
/**
* 分页对象记录
*/
List<T> getItems();
}
5.2 接口封装
@AllArgsConstructor
public class JpaPageImpl<T> implements AppPage<T> {
private Page<T> page;
@Override
public long getPageNum() {
if (page == null) return 0;
return page.getNumber() + 1;
}
@Override
public long getPageSize() {
if (page == null ) return 0;
return page.getSize();
}
@Override
public long getTotal() {
if (page == null ) return 0;
return page.getTotalElements();
}
@Override
public long getTotalPages() {
if (page == null ) return 0;
return page.getTotalPages();
}
@Override
public List<T> getItems() {
return page.getContent();
}
}
5.3 使用
@GetMapping("page")
public JpaPageImpl<Product> page (){
PageRequest pageRequest = PageRequest.of(0,2);
Page<Product> all = productRepo.findAll(pageRequest);
return new JpaPageImpl<>(all);
}
六、动态查询
6.1 Specification
6.1.1 前提条件:repository 必须实现 JpaSpecificationExecutor 接口
@Repository
public interface StudentRepo extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {}
// 或者,JpaRepositoryImplementation 接口就是将 以上两个接口整合到一起
@Repository
public interface StudentRepo extends JpaRepositoryImplementation<Student, Integer> {}
6.1.2 使用
@SpringBootTest
public class DynamicQueryTest {
@Autowired
private StudentRepo studentRepo;
@Test // 单条件
public void specification1 (){
// root.get("age") 中 age 为 Student 中 属性名,不是表中列名
Specification<Student> spec1 = (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("age"),2);
List<Student> students1 = studentRepo.findAll(spec1);
System.out.println(students1);
}
@Test // 多条件
public void specification2 (){
Specification<Student> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("grade"), 1)); // 不可使用枚举,必须使用数据存储的值
predicates.add(cb.in(root.get("age")).value(List.of(11,12,14))); // in 查询
predicates.add(cb.ge(root.get("age"), 2)); // >= 查询
predicates.add(cb.le(root.get("age"), 28)); // <= 查询
predicates.add(cb.like(root.get("name"), "t%")); // like 查询
Predicate[] ps = new Predicate[predicates.size()];
return cb.and(predicates.toArray(ps));
};
List<Student> students = studentRepo.findAll(spec);
System.out.println(students);
}
}
6.2 Example
@SpringBootTest
public class DynamicQueryTest {
@Autowired
private StudentRepo studentRepo;
@Test
public void example (){
// 查询条件
Student student = new Student();
student.setName("ts");
// 分页条件
PageRequest pageRequest = PageRequest.of(0,10);
// 匹配规则
ExampleMatcher matcher = ExampleMatcher.matchingAll()
.withStringMatcher(ExampleMatcher.StringMatcher.STARTING) // 所有非空 String 字段进行匹配
// .withMatcher("name", ExampleMatcher.GenericPropertyMatcher::startsWith) // 指定 字段 匹配
// .withMatcher("name", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.ENDING)) // 模糊查询,匹配结尾
// .withMatcher("name", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING)) // 模糊查询,包含
// .withMatcher("name", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.DEFAULT)) // 默认
// .withMatcher("name", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.EXACT)) // 精确查询
// .withMatcher("name", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.REGEX)) // 模糊查询,正则匹配
.withIgnoreNullValues();
Example<Student> example = Example.of(student, matcher);
Page<Student> page = studentRepo.findAll(example, pageRequest);
System.out.println(page.getTotalPages());
}
}
七、其他
7.1 @Transient:标记为非持久化字段
标记某字段为业务中的临时存放字段,数据库中不存在
7.2 @Enumerated: 枚举类持久化
- EnumType.ORDINAL:持久化为 枚举字段下标
- EnumType.STRING:持久化为 枚举字段 name
7.3 @Lob:持久化为大对象类型
一般要配合 @Basic(fetch=FetchType.LAZY) 延迟加载
- Clob:长字符串类型
- Blob:字节类型
7.4 @OrderBy:关联集合元素 的排序
// 下面将根据 sex 递增排序,salary 递减排序
@OneToMany(cascade = CascadeType.ALL)
@OrderBy("sex asc,salary desc")
private List<Student> studentList;