public interface EmployeeRepository extends JpaRepository<Employee, Long>, JpaSpecificationExecutor {
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
">
<tx:annotation-driven/>
<context:component-scan base-package="com.heyang.service"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="150" />
<property name="minIdle" value="5" />
<property name="maxIdle" value="20" />
<property name="initialSize" value="30" />
<property name="logAbandoned" value="true" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="10" />
<property name="maxWait" value="1000" />
<property name="timeBetweenEvictionRunsMillis" value="10000" />
<property name="numTestsPerEvictionRun" value="10" />
<property name="minEvictableIdleTimeMillis" value="10000" />
<property name="validationQuery" value="SELECT NOW() FROM DUAL" />
bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.heyang.domain"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="false" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
bean>
property>
bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
bean>
<jpa:repositories base-package="com.heyang.repository" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory" />
beans>
jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///数据库名字
jdbc.username=root
jdbc.password=12345678
顺带一提,对于不同的Spring Data子项目Spring提供了不同的xml命名空间,如对于Spring Data MongoDB可以将上面的jpa改为mongodb
当然,使用Spring Boot这一步基本可以省略,我们需要做的就是在application.properties或application.xml文件中配置几个属性即可
public interface EmployeeRepository extends JpaRepository<Employee, Long>, JpaSpecificationExecutor {
Employee findByUsername(String username);
//根据命名规则查询
Employee findAllByUsernameLikeAndAgeIs(String username, Integer age);
//使用规则直接查询
List<Employee> findAllByUsernameLike(String username);
//用jpql语句查询
@Query("select o from Employee o where username like ?1")
List<Employee> findByUsernameJpql(String username);
//用原生sql查询
@Query(nativeQuery = true, value = "SELECT * from employee")
List<Employee> findallNative();
}
package com.heyang.query;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
public abstract class BaseQuery {
//当前页
private int currentPage = 1;
//每页显示的数据
private int pageSize = 10;
//排序根据的列
private String orderName;
//排序类型
private String orderType = "ASC";
public Sort creatSort() {
if (StringUtils.isNotBlank(orderName)) {
return new Sort(Sort.Direction.valueOf(orderType), orderName);
}
return null;
}
//设置一个抽象方法,公共的固定名字,统一规范
public abstract Specification createSpecifictiong();
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
//修改当前页从0开始的情况,如果是已有封装好的代码,建议新建一个方法,进行扩展
public int getPageCurrentPage() {
return currentPage - 1;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public String getOrderType() {
return orderType;
}
public void setOrderType(String orderType) {
this.orderType = orderType;
}
}
package com.heyang.query;
import com.github.wenhao.jpa.Specifications;
import com.heyang.domain.Employee;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.domain.Range;
import org.springframework.data.jpa.domain.Specification;
//查询类高级查询
public class EmployeeQuery extends BaseQuery {
private String username;
private String email;
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
//高级查询
@Override
public Specification createSpecifictiong() {
Range range = new Range(20, 30);
Specification<Employee> build = Specifications.<Employee>and().like(StringUtils.isNotBlank(username), "username", "%" + username + "%")
.like(StringUtils.isNotBlank(email), "email", "%" + email + "%")
.gt(age != null, "age", age).build();
return build;
}
}
package com.heyang;
import com.heyang.domain.Employee;
import com.heyang.query.EmployeeQuery;
import com.heyang.repository.EmployeeRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
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.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.Specifications;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.persistence.criteria.*;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SSSDJTest {
@Autowired
private EmployeeRepository employeeRepository;
/**
* 测试查询所有
* @throws Exception
*/
@Test
public void test() throws Exception {
employeeRepository.findAll().forEach(e-> System.out.println(e));
}
/**
* 查询一条,根据规则查询,根据jpql查询
* @throws Exception
*/
@Test
public void test1() throws Exception {
Employee one = employeeRepository.findOne(23L);
Employee admin = employeeRepository.findByUsername("admin");
Employee allByUsernameLikeAndAgeIs = employeeRepository.findAllByUsernameLikeAndAgeIs("%admin%", 34);
List<Employee> allByUsernameLike = employeeRepository.findAllByUsernameLike("%admin%");
// allByUsernameLike.forEach(e-> System.out.println(e));
List<Employee> byUsername111 = employeeRepository.findByUsernameJpql("%admin%");
byUsername111.forEach(e-> System.out.println(e));
}
/**
* 使用原生语句查询
* @throws Exception
*/
@Test
public void test2() throws Exception{
employeeRepository.findallNative().forEach(e-> System.out.println(e));
}
/**
* 测试排序,分页
* @throws Exception
*/
@Test
public void test03() throws Exception{
Sort agesort = new Sort(Sort.Direction.DESC, "age");
PageRequest page = new PageRequest(0, 10, agesort);
PageRequest pageAndSort = new PageRequest(0, 10, agesort);
employeeRepository.findAll(agesort).forEach(e-> System.out.println(e));
System.out.println("====================================================");
employeeRepository.findAll(page).forEach(e-> System.out.println(e));
System.out.println("====================================================");
employeeRepository.findAll(pageAndSort).forEach(e-> System.out.println(e));
}
/**
* 测试高级查询
* @throws Exception
*/
@Test
public void test04() throws Exception{
/*
Root 获取字段名字
criteriaQuery
criteriaBuilder
*/
employeeRepository.findAll(((root, cq, cb) -> {
Path username = root.get("username");
Path emal = root.get("email");
Path age = root.get("age");
Predicate p1 = cb.like(username, "%admin%");
Predicate p2 = cb.like(emal, "%qq.com%");
Predicate p3 = cb.gt(age, 20);
Predicate pAll = cb.and(p1, p2, p3);
return pAll;
})).forEach(e-> System.out.println(e));
}
/**
* 查询 加入sort和分页
* @throws Exception
*/
@Test
public void test05() throws Exception{
/*
Root 获取字段名字
criteriaQuery
criteriaBuilder
*/
Pageable age1 = new PageRequest(0, 10, new Sort(Sort.Direction.DESC, "age"));
Page all = employeeRepository.findAll(((root, cq, cb) -> {
Path username = root.get("username");
Path emal = root.get("email");
Path age = root.get("age");
Predicate p1 = cb.like(username, "%admin%");
Predicate p2 = cb.like(emal, "%2%");
Predicate p3 = cb.gt(age, 20);
Predicate pAll = cb.and(p1, p2, p3);
return pAll;
}), age1);
all.forEach(e-> System.out.println(e));
}
@Test
public void test06() throws Exception{
//模拟前台数据传输
EmployeeQuery employeeQuery = new EmployeeQuery();
employeeQuery.setEmail("2");
employeeQuery.setUsername("admin");
employeeQuery.setAge(20);
employeeQuery.setOrderName("age");
employeeQuery.setOrderType("DESC");
//实际代码
Specification specifictiong = employeeQuery.createSpecifictiong();
Sort orders = employeeQuery.creatSort();
Pageable pageRequest = new PageRequest(employeeQuery.getPageCurrentPage(), employeeQuery.getPageSize(),orders);
employeeRepository.findAll(specifictiong,pageRequest).forEach(e-> System.out.println(e));
}
}
具体Spring Data Jpa对方法名的解析规则可参看官方文档4.4.3. Property Expressions
User findFirstByUsernameOrderByUsernameAsc(String username);
List<User> findTop10ByUsername(String username, Sort sort);
List<User> findTop10ByUsername(String username, Pageable pageable);
//@Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JPQL 查询语句即可
@Query("select u from User u where u.email = ?1")
User getByEmail(String eamil);
@Query("select u from User u where u.username = ?1 and u.password = ?2")
User getByUsernameAndPassword(String username, String password);
@Query("select u from User u where u.username like %?1%")
List<User> getByUsernameLike(String username);
@Query("select u from User u where u.id = :id")
User getById(@Param("id") String userId);
@Query("select u from User u where u.username = :username or u.email = :email")
User getByUsernameOrEmail(@Param("username") String username, @Param("email") String email);
@Query("select u from #{#entityName} u where u.email = ?1")
User getByEmail(String eamil);
@Query(value = "select * from tb_user u where u.email = ?1", nativeQuery = true)
User queryByEmail(String email);
@Query(value = "select * from tb_user u where u.email = :email", nativeQuery = true)
User queryByEmail(@Param("email") String email);
@Query(nativeQuery = true,value = "select * from tb_user u where u.username like %?1%",
countQuery = "select count(1) from tb_user u where u.username = %?1%")
Page<User> queryByUsernameLike(String username, Pageable pageable);
@Query("select u from User u where u.username like %?1%")
Page<User> findByUsernameLike(String username, Pageable pageable);
@Query("select u from User u where u.username like %?1%")
List<User> findByUsernameAndSort(String username, Sort sort);
那调用repository方法时传入什么参数呢?
new PageRequest(0, 5);
new PageRequest(0, 5, Sort.Direction.ASC, "username");
new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "username"));
PageRequest.of(0, 5);
PageRequest.of(0, 5, Sort.Direction.ASC, "username");
PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC, "username"));
@Transactional()
@Modifying
@Query("update User u set u.password = ?2 where u.username = ?1")
int updatePasswordByUsername(String username, String password);
@Transactional()
@Modifying
@Query("delete from User where username = ?1")
void deleteByUsername(String username);
这里使用级联查询进行多表的关联查询
多对多
package com.example.springbootjpa.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
import java.util.Set;
import java.util.UUID;
@Entity
@Table(name = "tb_user")
@Data
public class User {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "username", unique = true, nullable = false, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 64)
private String password;
@Column(name = "email", unique = true, length = 64)
private String email;
@ManyToMany(targetEntity = Role.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinTable(name = "tb_user_role", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles;
}
package com.example.springbootjpa.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@Entity
@Table(name = "tb_role")
@Data
public class Role {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "role_name", unique = true, nullable = false, length = 64)
private String roleName;
}
测试
@Test
public void findByIdTest() {
Optional<User> optional = userRepository.findById("40289f0c65674a930165674d54940000");
Set<Role> roles = optional.get().getRoles();
System.out.println(optional.get());
}
不出意外会报Hibernate懒加载异常,无法初始化代理类,No Session:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.springbootjpa.entity.User.roles, could not initialize proxy - no Session
原因:Spring Boot整合JPA后Hibernate的Session就交付给Spring去管理。每次数据库操作后,会关闭Session,当我们想要用懒加载方式去获得数据的时候,原来的Session已经关闭,不能获取数据,所以会抛出这样的异常。
在application.yml中做如下配置:
spring:
jpa:
open-in-view: true
properties:
hibernate:
enable_lazy_load_no_trans: true
package com.example.springbootjpa.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Set;
@Entity
@Table(name = "tb_dept")
@Data
public class Department {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "dept_name", unique = true, nullable = false, length = 64)
private String deptName;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Employee> employees;
}
package com.example.springbootjpa.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
@Entity
@Table(name = "tb_emp")
@Data
public class Employee {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "emp_name", nullable = false, length = 64)
private String empName;
@Column(name = "emp_job", length = 64)
private String empJob;
@Column(name = "dept_id", insertable = false, updatable = false)
private String deptId;
@ManyToOne(targetEntity = Department.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id")
private Department department;
}
测试
@Test
public void findByIdTest() {
Optional<Employee> optional = employeeRepository.findById("93fce66c1ef340fa866d5bd389de3d79");
System.out.println(optional.get());
}
结果报错了...
java.lang.StackOverflowError
通过日志看sql的输出,发现了sql重复执行了好多次。以下我截取了前10条sql记录。
Hibernate: select employee0_.id as id1_1_0_, employee0_.dept_id as dept_id2_1_0_, employee0_.emp_job as emp_job3_1_0_, employee0_.emp_name as emp_name4_1_0_ from tb_emp employee0_ where employee0_.id=?
Hibernate: select department0_.id as id1_0_0_, department0_.dept_name as dept_nam2_0_0_ from tb_dept department0_ where department0_.id=?
Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id=?
Hibernate: select department0_.id as id1_0_0_, department0_.dept_name as dept_nam2_0_0_ from tb_dept department0_ where department0_.id=?
Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id=?
Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id=?
Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id=?
Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id=?
Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id=?
Hibernate: select employees0_.dept_id as dept_id2_1_0_, employees0_.id as id1_1_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id2_1_1_, employees0_.emp_job as emp_job3_1_1_, employees0_.emp_name as emp_name4_1_1_ from tb_emp employees0_ where employees0_.dept_id=?
通过观察发现,第一条sql是执行查询Employee的sql,第二条sql是执行查询Department的sql,第三条sql是执行Department里面所有员工的sql,第四条sql是执行查询Department的sql,后面所有的sql都是执行查询Department里面所有员工的sql。
很明显发生了循环依赖的情况。这是Lombok的@Data注解的锅。Lombok的@Data注解相当于@Getter、@Setter、@RequiredArgsConstructor、@ToString、@EqualsAndHashCode这几个注解。
我们可以通过反编译看一下Lombok生成的toString()方法
// Employee
public String toString() {
return "Employee(id=" + getId() + ", empName=" + getEmpName() + ", empJob=" + getEmpJob() + ", deptId=" + getDeptId() + ", department=" + getDepartment() + ")";
}
// Department
public String toString() {
return "Department(id=" + getId() + ", deptName=" + getDeptName() + ", employees=" + getEmployees() + ")";
}
可以发现Lombok为我们生成的toString()方法覆盖了整个类的所有属性
现在将@Data注解去掉,替换为@Setter、@Getter、@EqualsAndHashCode,重写toString()方法
// Department
@Override
public String toString() {
return "Department{" +
"id='" + id + '\'' +
", deptName='" + deptName + '\'' +
'}';
}
// Employee
@Override
public String toString() {
return "Employee{" +
"id='" + id + '\'' +
", empName='" + empName + '\'' +
", empJob='" + empJob + '\'' +
", deptId='" + deptId + '\'' +
", department=" + department +
'}';
}
再次运行测试用例,测试通过,以上Employee toString()方法打印的department会触发懒加载,最终日志输出的sql如下:
Hibernate: select employee0_.id as id1_1_0_, employee0_.dept_id as dept_id2_1_0_, employee0_.emp_job as emp_job3_1_0_, employee0_.emp_name as emp_name4_1_0_ from tb_emp employee0_ where employee0_.id=?
Hibernate: select department0_.id as id1_0_0_, department0_.dept_name as dept_nam2_0_0_ from tb_dept department0_ where department0_.id=?
Employee{id='93fce66c1ef340fa866d5bd389de3d79', empName='jack', empJob='hr', deptId='0a4fe7234fff42afad34f6a06a8e1821', department=Department{id='0a4fe7234fff42afad34f6a06a8e1821', deptName='人事部'}}
再来测试查询Department
@Test
public void findByIdTest() {
Optional<Department> optional = departmentRepository.findById("0a4fe7234fff42afad34f6a06a8e1821");
Set<Employee> employees = optional.get().getEmployees();
Assert.assertNotEquals(0, employees.size());
}
同样还是报了堆栈溢出,错误定位在Department和Employee的hashCode()方法上
java.lang.StackOverflowError
依旧是Lombok的锅,@EqualsAndHashCode为我们生成的equals()和hashCode()方法会使用所有属性,注意,Department中employees是Set集合,当我们调用department.getEmployees()时,Employee的hashCode()方法会被调用,Employee中的hashCode()又依赖于Department的HashCode()方法,这样又形成了循环引用…
// Department
public int hashCode() {
int i = 43;
String $id = getId();
int result = ($id == null ? 43 : $id.hashCode()) + 59;
String $deptName = getDeptName();
result = (result * 59) + ($deptName == null ? 43 : $deptName.hashCode());
Set $employees = getEmployees();
int i2 = result * 59;
if ($employees != null) {
i = $employees.hashCode();
}
return i2 + i;
}
// Employee
public int hashCode() {
int i = 43;
String $id = getId();
int result = ($id == null ? 43 : $id.hashCode()) + 59;
String $empName = getEmpName();
result = (result * 59) + ($empName == null ? 43 : $empName.hashCode());
String $empJob = getEmpJob();
result = (result * 59) + ($empJob == null ? 43 : $empJob.hashCode());
String $deptId = getDeptId();
result = (result * 59) + ($deptId == null ? 43 : $deptId.hashCode());
Department $department = getDepartment();
int i2 = result * 59;
if ($department != null) {
i = $department.hashCode();
}
return i2 + i;
}
自己动手重写equals()和hashCode()方法,去掉@EqualsAndHashCode注解
// Department
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Department that = (Department) o;
return Objects.equals(id, that.id) &&
Objects.equals(deptName, that.deptName);
}
@Override
public int hashCode() {
return Objects.hash(id, deptName);
}
// Employee
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(id, employee.id) &&
Objects.equals(empName, employee.empName) &&
Objects.equals(empJob, employee.empJob) &&
Objects.equals(deptId, employee.deptId);
}
@Override
public int hashCode() {
return Objects.hash(id, empName, empJob, deptId);
}
再次运行测试用例,测试通过
审计Auditing
参考自官方文档5.9Auditing
一般数据库表在设计时都会添加4个审计字段,Spring Data Jpa同样支持审计功能。Spring Data提供了@CreatedBy,@LastModifiedBy,@CreatedDate,@LastModifiedDate4个注解来记录表中记录的创建及修改信息。
实体类
package com.example.springbootjpa.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
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;
import java.util.Set;
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "tb_user")
@Data
public class User {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "username", unique = true, nullable = false, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 64)
private String password;
@Column(name = "email", unique = true, length = 64)
private String email;
@ManyToMany(targetEntity = Role.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "tb_user_role", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles;
@CreatedDate
@Column(name = "created_date", updatable = false)
private Date createdDate;
@CreatedBy
@Column(name = "created_by", updatable = false, length = 64)
private String createdBy;
@LastModifiedDate
@Column(name = "updated_date")
private Date updatedDate;
@LastModifiedBy
@Column(name = "updated_by", length = 64)
private String updatedBy;
}
实体类上还添加了@EntityListeners(AuditingEntityListener.class),而AuditingEntityListener是由Spring Data Jpa提供的
实现AuditorAware接口
光添加了4个审计注解还不够,得告诉程序到底是谁在创建和修改表记录
package com.example.springbootjpa.auditing;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.of("admin");
}
}
这里简单的返回了一个"admin"字符串来代表当前用户名
启用Jpa审计功能
在Spring Boot启动类上添加@EnableJpaAuditing注解用于启用Jpa的审计功能
package com.example.springbootjpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class SpringBootJpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJpaApplication.class, args);
}
}