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
每次启动时,对表的更改的策略,有以下几种:
- create
如果表已存在,则会删除原表,建新表 - create-drop
每次启动时都会,创建表,结束应用时,删除表 - update
如果表存在则会更新,不存在则会创建 - validate
会跟新表的属性,但不会增加表 - none
不进行操作
hibernate表命名策略
strategy: org.hibernate.cfg.ImprovedNamingStrategy
若没有@Table,@Column指定字段名,则会采用命名策略。
表的命名策略,有以下几种:
- org.hibernate.cfg.DefaultNamingStrategy
不做其他处理,直接用属性名作为字段名 - 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来为对应属性赋值。
启用步骤如下:
- 实体类上添加 @EntityListeners(AuditingEntityListener.class)
- 在需要的字段上加上 @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;
}
- 在配置类上添加@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 {
}
- 实现 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方法的方案。
- 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
-
原生sql查询,更新
类似于jql查询,只不过查的是表名,字段名 -
方法名查询
利于方法名中的关键查询,如下:
findByAddress(String address);
关键字有以下这些:
关键字 | 接口函数例子 | JPQL 片段 |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |