Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用

  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生成策略

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使用就分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧!

  • 43
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值