Spring Data JPA系列
1、SpringBoot集成JPA及基本使用
2、Spring Data JPA Criteria查询、部分字段查询
3、Spring Data JPA数据批量插入、批量更新真的用对了吗
4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作
5、Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用
6、【源码】Spring Data JPA原理解析之Repository的自动注入(一)
7、【源码】Spring Data JPA原理解析之Repository的自动注入(二)
8、【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码
9、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)
10、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)
11、【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理
12、【源码】SpringBoot事务注册原理
13、【源码】Spring Data JPA原理解析之事务注册原理
14、【源码】Spring Data JPA原理解析之事务执行原理
15、【源码】SpringBoot编程式事务使用及执行原理
16、【源码】Spring事务之传播特性的详解
17、【源码】Spring事务之事务失效及原理
18、【源码】Spring Data JPA原理解析之Hibernate EventListener使用及原理
19、【源码】Spring Data JPA原理解析之Auditing执行原理
20、Spring Data JPA使用及实现原理总结
在Spring Data JPA系列的第一篇文章
SpringBoot集成JPA及基本使用-CSDN博客
中讲解了实体类的Id生成策略可以通过@GeneratedValue注解进行配置,该注解的strategy为GenerationType类型,GenerationType为枚举类,支持四种Id的生成策略,分别为TABLE、SEQUENCE、IDENTITY、AUTO,详细信息可以查看第一篇博文。
以上的四种Id生成策略并不能完全满足实际的项目需要,如在分布式系统中,为了实现Id的唯一性,可以采用雪花算法,此时可以使用自定义Id生成策略。
自定义Id生成策略,需要实现org.hibernate.id.IdentifierGenerator接口,重写generate()方法。此处以时间戳作为id为例,代码如下:
package com.jingai.jpa.util;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import java.io.Serializable;
public class GeneratePK implements IdentifierGenerator {
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
return System.currentTimeMillis();
}
}
自定义Id生成策略使用,代码如下:
package com.jingai.jpa.dao.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
@Data
@Entity
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
@Table(name = "tb_goods")
public class GoodsEntity2 {
@Id
// 指定id生成策略
@GenericGenerator(name = "generatePk", strategy = "com.jingai.jpa.util.GeneratePK")
// generator的值为@GenericGenerator的name
@GeneratedValue(generator = "generatePk")
private Long id;
private String name;
private String subtitle;
private Long classificationId;
private Date createTime;
@Transient
private String createTimeStr;
}
其他的代码同使用系统提供的Id生成策略一致。
有些表会存在多个id,如角色功能表。针对这种情况,Spring Data JPA中该如何配置呢?
在Spring Data JPA的实体类中并不支持简单的直接在多个属性中添加@Id注解。而是需要先创建一个复合主键类,然后在实体类中使用@IdClass注解将主键类附加在类中。
下面以会员统计表为例,建表语句:
CREATE TABLE `tb_member_statistics` (
`member_id` int(0) NOT NULL,
`type` int(0) NOT NULL,
`total_integral` int(0) NULL DEFAULT NULL,
PRIMARY KEY (`member_id`, `type`) USING BTREE
)
该表以member_id、type为复合主键。
复合主键类为主键字段。代码如下:
package com.jingai.jpa.dao.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class MemberStatisticsPk implements Serializable {
private long memberId;
private int type;
}
package com.jingai.jpa.dao.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Data
@Entity
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
// 附加主键类
@IdClass(MemberStatisticsPk.class)
@Table(name = "tb_member_statistics")
public class MemberStatisticsEntity {
@Id
private long memberId;
@Id
private int type;
private int totalIntegral;
}
Spring Data JPA的Repository接口格式为Repository
package com.jingai.jpa.dao;
import com.jingai.jpa.dao.entity.MemberStatisticsEntity;
import com.jingai.jpa.dao.entity.MemberStatisticsPk;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
import java.util.List;
public interface MemberStatisticsRepository extends JpaRepositoryImplementation {
@Query("from MemberStatisticsEntity where memberId = ?1")
List find(long memberId);
}
package com.jingai.jpa.service;
import com.jingai.jpa.dao.MemberStatisticsRepository;
import com.jingai.jpa.dao.entity.MemberStatisticsEntity;
import com.jingai.jpa.dao.entity.MemberStatisticsPk;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class MemberStatisticsService {
@Resource
private MemberStatisticsRepository memberStatisticsRepository;
public List find(long memberId) {
return memberStatisticsRepository.find(memberId);
}
/**
* 使用原生的ById接口时,需要传入复合主键类作为id
*/
public MemberStatisticsEntity find(MemberStatisticsPk pk) {
return memberStatisticsRepository.findById(pk).get();
}
}
package com.jingai.jpa.controller;
import com.jingai.jpa.dao.entity.MemberStatisticsPk;
import com.jingai.jpa.service.MemberStatisticsService;
import com.jingai.jpa.util.ResponseUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Map;
@RestController
@RequestMapping("memberstatistics")
public class MemberStatisticsController {
@Resource
private MemberStatisticsService memberStatisticsService;
@GetMapping("find")
public Map find(long memberId) {
return ResponseUtil.success(memberStatisticsService.find(memberId));
}
@GetMapping("get")
public Map findById(long memberId, int type) {
return ResponseUtil.success(memberStatisticsService.find(new MemberStatisticsPk(memberId, type)));
}
}
复合主键还可以通过@EmbeddedId和@Embeddable注解,采用嵌入式的方式实现,只是没有那么直观。感兴趣的可以自己百度了解一下。
Auditing翻译过来是审计和审核,在实际的业务中,经常需要记录某条数据的操作人及操作时间,以及记录操作日志,Spring Data JPA通过注解的方式提供了审计功能的架构实现。
Spring Data JPA提供了4个注解解决数据操作人及操作时间数据的维护。
1)@CreatedBy:创建用户
2)CreatedDate:创建时间
3)@LastModifiedBy:修改的用户
4)@LastModifiedDate:最后一次修改的时间
package com.jingai.jpa.dao.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
@Data
@Entity
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
// 添加AuditingEntityListener实体监听
@EntityListeners(AuditingEntityListener.class)
@Table(name = "tb_user")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String state;
// 添加审计的注解
@CreatedBy
private Long createBy;
@CreatedDate
private Date createTime;
@LastModifiedBy
private Long modifyBy;
@LastModifiedDate
private Date modifyTime;
}
添加AuditingEntityListener实体监听及审计的注解。
package com.jingai.jpa.config;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.Optional;
@Component
public class AppAuditorAware implements AuditorAware {
/**
* 返回当前的审计员,即要添加在@CreateBy和@LastModifiedBy注解中属性的信息。
* 此处通过Request中获取用户id。可根据实际项目进行修改,如通过jwt或者security等
*/
@Override
public Optional getCurrentAuditor() {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
Object userId = requestAttributes.getAttribute("userId", RequestAttributes.SCOPE_SESSION);
if(userId == null)
return Optional.empty();
return Optional.of((long)userId);
}
}
package com.jingai.jpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
// 指定扫描的表映射实体Entity的目录,如果不指定,会扫描全部目录
//@EntityScan("com.jingai.jpa.dao.entity")
// 指定扫描的表repository目录,如果不指定,会扫描全部目录
//@EnableJpaRepositories(basePackages = {"com.jingai.jpa.dao"})
// 可选,开启JPA auditing能力,可以自动赋值一些字段,比如创建时间、最后一次修改时间等等
@EnableJpaAuditing
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
}
Repository、Service类不需要任何改动。
package com.jingai.jpa.dao;
import com.jingai.jpa.dao.entity.UserEntity;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
public interface UserRepository extends JpaRepositoryImplementation {
}
package com.jingai.jpa.service;
import com.jingai.jpa.dao.UserRepository;
import com.jingai.jpa.dao.entity.UserEntity;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserRepository userRepository;
public UserEntity save(UserEntity entity) {
return userRepository.save(entity);
}
public UserEntity find(long id) {
return userRepository.findById(id).get();
}
}
package com.jingai.jpa.controller;
import com.jingai.jpa.dao.entity.UserEntity;
import com.jingai.jpa.service.UserService;
import com.jingai.jpa.util.ResponseUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.annotation.Resource;
import java.util.Map;
@RestController
@RequestMapping("user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("save")
public Map save(String name) {
// 模拟审计员
RequestContextHolder.getRequestAttributes().setAttribute("userId", 1000l, RequestAttributes.SCOPE_SESSION);
UserEntity entity = new UserEntity();
entity.setName(name);
entity.setState("1");
return ResponseUtil.success(userService.save(entity));
}
@PostMapping("update")
public Map update(long id, String name) {
// 模拟审计员
RequestContextHolder.getRequestAttributes().setAttribute("userId", 1001l, RequestAttributes.SCOPE_SESSION);
UserEntity entity = userService.find(id);
entity.setName(name);
return ResponseUtil.success(userService.save(entity));
}
}
在修改的时候需要特别注意,如果不先通过id获取原记录,那么修改后,createBy和createDate会被修改为null,因为修改时传入的实体类对象没有createBy和createDate的值。
访问上面的两个接口如下:
在项目中,可能会有很多的实体类需要记录操作人及操作时间,此时可以定义一个父类,专门记录操作人及操作信息。
package com.jingai.jpa.dao.entity;
import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 添加审计的注解
@CreatedBy
private Long createBy;
@CreatedDate
private Date createTime;
@LastModifiedBy
private Long modifyBy;
@LastModifiedDate
private Date modifyTime;
}
package com.jingai.jpa.dao.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Table;
@Data
@Entity
@JsonIgnoreProperties(value = {"hibernateLazyInitializer"})
@Table(name = "tb_user")
public class UserEntity extends AbstractAuditable {
private String name;
private String state;
}
限于篇幅,Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用就分享到这里。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧!