Jpa知识梳理

本文详细介绍Spring Boot中使用JPA进行数据库操作的配置及实践,包括依赖引入、配置文件详解、命名策略、审计功能、实体类定义、一对多与多对多关系处理以及Repository的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mavan包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

jpa配置文件

spring:
  application:
    name: jpa
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.x.xx:xxxx/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: xxx
    password: xxx
  jpa:
    database: MYSQL
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update
      naming:
        strategy: org.hibernate.cfg.ImprovedNamingStrategy
    show-sql: true

ddl-auto

ddl-auto: update

每次启动时,对表的更改的策略,有以下几种:

  1. create
    如果表已存在,则会删除原表,建新表
  2. create-drop
    每次启动时都会,创建表,结束应用时,删除表
  3. update
    如果表存在则会更新,不存在则会创建
  4. validate
    会跟新表的属性,但不会增加表
  5. none
    不进行操作

hibernate表命名策略

strategy: org.hibernate.cfg.ImprovedNamingStrategy

若没有@Table,@Column指定字段名,则会采用命名策略。

表的命名策略,有以下几种:

  1. org.hibernate.cfg.DefaultNamingStrategy
    不做其他处理,直接用属性名作为字段名
  2. org.hibernate.cfg.ImprovedNamingStrategy
    遇到大写字母是会转化为’_'小写字母

配置类

package com.example.jpa.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories("com.example.jpa.repository")
@EnableJpaAuditing
public class AppConfig {
}

@EnableJpaAuditing

启用jpa审计功能。

@EnableJpaRepositories(“com.example.jpa.repository”)

设置扫描repository的包

jpa审计功能

审计功能也即,通过在实体类中添加@CreateDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy来为对应属性赋值。

启用步骤如下:

  1. 实体类上添加 @EntityListeners(AuditingEntityListener.class)
  2. 在需要的字段上加上 @CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy 等注解。
package com.example.jpa.entities;

import lombok.*;
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.HashSet;
import java.util.Set;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Table(name = "jpa_user")
@EqualsAndHashCode(exclude = {"roles"})
public class User {

    @Id
    @GeneratedValue
    private Long userId;
    private String username;
    private Integer age;
    
    @CreatedBy
    private Long createBy;

    @CreatedDate
    private Long createTime;

    @LastModifiedBy
    private Long updateBy;

    @LastModifiedDate
    private Long updateTime;
}

  1. 在配置类上添加@EnableJpaAuditing
```java
package com.example.jpa.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories("com.example.jpa.repository")
@EnableJpaAuditing
public class AppConfig {
}
  1. 实现 AuditorAware 接口来返回你需要插入的值。
package com.example.jpa.context;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
@Slf4j
public class UserAuditor implements AuditorAware<Long> {

    @Override
    public Optional<Long> getCurrentAuditor() {
        // TODO: 2020/8/15 返回当前用户的id
        return Optional.empty();
    }
}

实体类定义

一对多情况

例子:客户,联系人,客户人为一,联系人为多。

Customer类

package com.example.jpa.entities;

import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.Set;

@Entity
@Data
@Builder
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(exclude = {"linkMans"})
@ToString(exclude = {"linkMans"})
public class Customer {

    @Id
    @GeneratedValue
    private Long id;

    private String address;
    private String name;

    /**
     * JoinColumn name是外键名称,referencedColumnName是主表的主键
     * JoinColumn 在主从表都配置的话,是双向,反之则是单向,
     */
//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "customerId", referencedColumnName = "id")
    /**
     * 放弃外键维护,添加mappedBy,指定子对象的customer属性来维护外键。
     * 如果主表不放弃外键维护,那么删除从表时,会将从表外键设为null,然后删除主表记录。
     * 但当放弃外键维护后,那么删除主表会报有外键引用的错误。
     */
    @OneToMany(mappedBy = "customer", cascade = {CascadeType.ALL})
    private Set<LinkMan> linkMans;
}

LinkMan类

package com.example.jpa.entities;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class LinkMan {

    @GeneratedValue
    @Id
    private Long id;

    private String name;
    /**
     * JoinColumn name是外键名称,referencedColumnName是主表的主键
     */
    @ManyToOne(targetEntity = Customer.class)
    @JoinColumn(name = "customerId", referencedColumnName = "id")
    private Customer customer;
}

@EqualsAndHashCode(exclude = {“linkMans”})
@ToString(exclude = {“linkMans”})

这两个注解属于lombok包下,用来排除linkMans属性的的ToString和EqualsAndHashCode方法的实现,因为当互相引用时,ToString和EqualsAndHashCode的lombok实现可能会出现内存溢出的情况。

@JoinColumn(name = “customerId”, referencedColumnName = “id”)

JoinColumn是用来指定外键对应关系,name是外键字段名,在这个例子中也就是ling_man表中的customer_id字段(这里写驼峰式属性名,也会转成_分隔的字段名,因为表的命名策略),referencedColumnName是外键对应主表中的那个字段,也就是customer表中的id。

注意的是加上JoinColumn就会指定哪一个类来维护外键关系。主表可以维护外键关系,从表也可以维护外键关系。

维护关键关系也即操作中操纵哪个类。
如果是Customer的话,那保存时,需要在Customer的linkMans集合属性中加入对应linkman对象。
如果是LinkMan的话,保存时,需要在对应customer属性上设为对应customer对象。

@OneToMany(mappedBy = “customer”, cascade = {CascadeType.ALL})

主表可以放弃外键维护,添加mappedBy,指定子对象的customer属性来维护外键。
如果主表不放弃外键维护,那么删除从表时,会将从表外键设为null,然后删除主表记录。
但当放弃外键维护后,那么删除主表会报有外键引用的错误。

多对多情况

例子:用户,角色。

User类

package com.example.jpa.entities;

import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Table(name = "jpa_user")
@EqualsAndHashCode(exclude = {"roles"})
public class User {

    @Id
    @GeneratedValue
    private Long userId;
    private String username;
    private Integer age;
    /**
     * 中间表用JoinTable
     */
    @ManyToMany(targetEntity = Role.class, cascade = CascadeType.ALL)
    @JoinTable(name = "jpa_user_role",
            /*joinColumns是当前表对应中间表的外键*/
            joinColumns = {@JoinColumn(name = "jpaUserId", referencedColumnName = "userId")},
            /*inverseJoinColumns是对方表对应中间表的外键*/
            inverseJoinColumns = {@JoinColumn(name = "jpaRoleId", referencedColumnName = "roleId")})
    private Set<Role> roles = new HashSet<>();
}

Role类

package com.example.jpa.entities;

import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Table(name = "jpa_role")
@EqualsAndHashCode(exclude = {"users"})
public class Role {

    @Id
    @GeneratedValue
    private Long roleId;
    private String roleName;

    /**
     * @ManyToMany(targetEntity = User.class)
     * @JoinTable(name = "jpa_user_role",
     * joinColumns = {@JoinColumn(name = "jpaRoleId", referencedColumnName = "roleId")},
     * inverseJoinColumns = {@JoinColumn(name = "jpaUserId", referencedColumnName = "userId")})
     */
    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();

}

@JoinTable(name = “jpa_user_role”,
/joinColumns是当前表对应中间表的外键/
joinColumns = {@JoinColumn(name = “jpaUserId”, referencedColumnName = “userId”)},
/inverseJoinColumns是对方表对应中间表的外键/
inverseJoinColumns = {@JoinColumn(name = “jpaRoleId”, referencedColumnName = “roleId”)})

JoinTable 用来定义多对多中间表。name属性表示中间表名称。

joinColumns 是当前表对应中间表的外键。

inverseJoinColumns 是对方表对应中间表的外键。

@ManyToMany(mappedBy = “roles”)

多对多与一对多相同也可以双向维护,一般 我们让操作的类进行维护。比如,User类中在roles属性上添加JoinTable,Role类中则是放弃外键维护,通过mappedBy ,将外键交给对方roles属性的维护。

repository

repository是用来和数据库交互的。

repository实现JpaRepository和JpaSpecificationExecutor两个接口,添加@Repository,那么repository就会具备常用的增删改查方法和动态查询的方法。

可以自己创建一个接口(BaseRepository)继承JpaRepository和JpaSpecificationExecutor,子类继承该接口(BaseRepository)。

BaseRepository接口

package com.example.jpa.repository.common;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface BaseRepository<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
}

@NoRepositoryBean
一般用作父类的repository,有这个注解,spring不会去实例化该repository。

CustomerRepository

package com.example.jpa.repository;

import com.example.jpa.entities.Customer;
import com.example.jpa.repository.common.BaseRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import javax.transaction.Transactional;
import java.util.Optional;

@Repository
public interface CustomerRepository extends BaseRepository<Customer, Long> {
    /**
     * @Query("from Customer where address = :address")
     */
    public Optional<Customer> findByAddress(String address);

    @Transactional
    @Modifying
    @Query("update Customer set address = :address where id = :id")
    public void updateAddress(Long id, String address);
}

除常用的增删改查外,jpa还提供了几种实现Repository方法的方案。

  1. jql语句查询,更新
    jql类似于sql只不过查的不是表名,字段名而是类名,属性名。如下:

@Query(“from Customer where address = :address”)
public Optional findByAddress(String address);

查询语句可以不写select *,直接from开始。

@Transactional
@Modifying
@Query(“update Customer set address = :address where id = :id”)
public void updateAddress(Long id, String address);

update语句或者insert,delete,需要加上@Modifying

  1. 原生sql查询,更新
    类似于jql查询,只不过查的是表名,字段名

  2. 方法名查询
    利于方法名中的关键查询,如下:

findByAddress(String address);

关键字有以下这些:

关键字接口函数例子JPQL 片段
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值