前言
在Spring Data JPA系列的第一篇文章
SpringBoot集成JPA及基本使用-CSDN博客
中讲解了实体类的Id生成策略可以通过@GeneratedValue注解进行配置,该注解的strategy为GenerationType类型,GenerationType为枚举类,支持四种Id的生成策略,分别为TABLE、SEQUENCE、IDENTITY、AUTO,详细信息可以查看第一篇博文。
以上的四种Id生成策略并不能完全满足实际的项目需要,如在分布式系统中,为了实现Id的唯一性,可以采用雪花算法,此时可以使用自定义Id生成策略。
自定义Id生成策略
1.1 自定义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();
}
}
1.2 引用自定义Id生成策略
自定义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为复合主键。
2.1 添加复合主键类
复合主键类为主键字段。代码如下:
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;
}
2.2 在实体类中使用@IdClass注解附加复合主键类
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;
}
2.3 Repository类
Spring Data JPA的Repository接口格式为Repository<T, ID>,其中T为实体类,ID为实体类中的Id。如果要使用Repository原生的ById接口,则必须传入正确的实体类Id。对于复合主键的实体类,此处传入的Id为复合主键类。代码如下:
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<MemberStatisticsEntity, MemberStatisticsPk> {
@Query("from MemberStatisticsEntity where memberId = ?1")
List<MemberStatisticsEntity> find(long memberId);
}
2.4 Service类
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<MemberStatisticsEntity> find(long memberId) {
return memberStatisticsRepository.find(memberId);
}
/**
* 使用原生的ById接口时,需要传入复合主键类作为id
*/
public MemberStatisticsEntity find(MemberStatisticsPk pk) {
return memberStatisticsRepository.findById(pk).get();
}
}
2.5 Controller类
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<String, Object> find(long memberId) {
return ResponseUtil.success(memberStatisticsService.find(memberId));
}
@GetMapping("get")
public Map<String, Object> findById(long memberId, int type) {
return ResponseUtil.success(memberStatisticsService.find(new MemberStatisticsPk(memberId, type)));
}
}
复合主键还可以通过@EmbeddedId和@Embeddable注解,采用嵌入式的方式实现,只是没有那么直观。感兴趣的可以自己百度了解一下。
Auditing使用
Auditing翻译过来是审计和审核,在实际的业务中,经常需要记录某条数据的操作人及操作时间,以及记录操作日志,Spring Data JPA通过注解的方式提供了审计功能的架构实现。
3.1 操作时间及操作人注解
Spring Data JPA提供了4个注解解决数据操作人及操作时间数据的维护。
1)@CreatedBy:创建用户
2)CreatedDate:创建时间
3)@LastModifiedBy:修改的用户
4)@LastModifiedDate:最后一次修改的时间
3.2 实体类Auditing的使用
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实体监听及审计的注解。
3.3 自定义AuditorAware
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<Long> {
/**
* 返回当前的审计员,即要添加在@CreateBy和@LastModifiedBy注解中属性的信息。
* 此处通过Request中获取用户id。可根据实际项目进行修改,如通过jwt或者security等
*/
@Override
public Optional<Long> getCurrentAuditor() {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
Object userId = requestAttributes.getAttribute("userId", RequestAttributes.SCOPE_SESSION);
if(userId == null)
return Optional.empty();
return Optional.of((long)userId);
}
}
3.4 在SpringBoot的启动类中添加@EnableJpaAuditing注解
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);
}
}
3.5 Repository、Service类
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<UserEntity, Long> {
}
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();
}
}
3.6 Controller类
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<String, Object> 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<String, Object> 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的值。
访问上面的两个接口如下:
3.7 @MappedSuperClass的使用
在项目中,可能会有很多的实体类需要记录操作人及操作时间,此时可以定义一个父类,专门记录操作人及操作信息。
3.7.1 创建公共的抽象类
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;
}
3.7.2 在实体类中继承抽象类
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使用就分享到这里。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧!