参考资源列表>> 1.官方文档:https : //docs.spring.io/spring-data/jpa/docs/2.1.5.RELEASE/reference/html/ > 2.“春数据JPA入门到精通”
前言
JPA
是Java持久性API '的简称,是'春天'在'休眠'的基础上进行的二次封装框架,为了更好更方便的融入'春天'大家庭,同时也提供了一些
休眠SpringData` 没有的特性,与其他ORM框架一起构成
,统一封装了ORM层,使开发人员使用起来更加方便快捷。
备注:本文所有代码都是基于SpringBoot 2.1.5版本
JPA的使用
基本单表操作的使用
对于操作单表来说,JPA提供了非常方便使用的封装,我们只需要按规范编写库接口同时继承JpaRepository
就可以接受JPA基本功能了代码如下:
用户实体:
java 包 com 。一竹。实体; 进口龙目岛。建造者; 进口龙目岛。数据; 进口龙目岛。ToString ; 导入 javax 。坚持不懈。* ; 导入 java 。io 。可序列化; 导入 java 。实用程序。清单; 导入 java 。实用程序。设置;
@Entity @Table(名称= “T_USER”)@Data @Builder公共类用户器具序列化{私人静态最终长的的的serialVersionUID = 1L ; @Id @GeneratedValue(策略= GenerationType.IDENTITY)私人龙ID;私有字符串名称;专有字符串密码;私有整体时代;私人的整数性别; } 储存库接口:。java的包COM一竹仓库;引入玉米一竹实体用户。;公共接口UserRepository扩展了JpaRepository <用户,龙> { }
下面看看JPA为我们提供了一些替代操作单表数据的方法。java的包有机弹簧框架。数据。jpa。仓库; @NoRepositoryBean公共接口JpaRepository
< T , ID > 扩展了PagingAndSortingRepository < T , ID > , QueryByExampleExecutor < T > { List < T > findAll (); //查询全表数据 列表< T > findAll
(对var1排序); //查询全表数据,支持排序 List < T > findAllById ( Iterable < ID > var1 ); //根据id细分查询所有匹配数据< S 扩展T >列表< S > saveAll ( Iterable < S > var1 ); //批量保存或更新数据void flush (); //刷新本地缓存到数据库< S 扩展T > S saveAndFlush
( S var1 ); //保存或更新单挑数据及刷新本地缓存到数据库空隙deleteInBatch (可迭代< Ť > VAR1 ); //批量删除数据无效deleteAllInBatch (); //批量删除全表数据 T getOne ( ID var1 ); //根据id查询一条匹配数据< S 扩展T > List < S > findAll (示例< S > ar1 );//示例方式的查询指定实体<
S 扩展 T > List < S > findAll (示例< S > var1 , Sort var2 ); //示例方式的查询指定实体并排序} ```
java 包 org 。弹簧框架。数据。仓库; @NoRepositoryBean 公共接口PagingAndSortingRepository < T , ID > 扩展CrudRepository < T , ID > { Iterable < T > findAll ( Sort var1 ); //根据排序查询全表,返回类型是任意集合 Page < T > findAll ( Pageable var1 ); //根据分页参数分页查询
} `“ java包org。弹簧框架。数据。仓库;@NoRepositoryBean公共接口CrudRepository < T,ID >扩展存储库< T,ID > { < S扩展T > S save(S var1);//保存或更新单条数据<S扩展T>可重复 saveAll( 可迭代< S > var1); //批量保存或更新可选 findById(ID var1); //根据id查询数据,,返回类型是可选的boolean existById(ID var1); //根据id判断数据是否存在Iterable findAll();//查询全表数据,返回类型为集合Iterable findAllById(Iterable var1);//根据id集合查询数据long count();//统计全表数据量void deleteById(ID var1);//根据ID删除数据无效删除(ŤVAR1);//删除单条数据void deleteAll(可迭代<?扩展T>
//删除指定集合数据无效的deleteAll();//删除全表数据} ```的java包有机弹簧框架数据仓库查询。。;公共接口QueryByExampleExecutor < Ť > { <小号扩展Ť >可选<小号> findOne(示例<小号> VAR1); //根据示例查询一条<S扩展T>可迭代<S> findAll(示例< S > var1);//根据示例查询所有数据<S扩展T>可重复<S> findAll(示例<S> var1,对var2排序); //根据示例查询所有数据,并排序<S扩展T>页面<S> findAll (示例<S> var1,可分页的var2);//根据示例分页查询<S 扩展T > long count(示例<
; ///根据示例统计<S扩展T>布尔值存在(示例 var1); //根据示例判断数据是否存在} 另外,jpa提供了一套新的生成sql的机制,非常方便好用,JPA根据存储库接口的方法中的关键字,实体替换及出入参,自动生成SQL,这种方式启动容器的时候就可以检查语法是否正确,简单使用示例如下:
的Java 包融为一体。一竹。仓库; 导入 com 。一竹。实体。用户; 导入组织。弹簧框架。数据。jpa 。仓库。实体图; 导入组织。弹簧框架。数据。jpa。仓库。JpaRepository
; 导入组织。弹簧框架。数据。jpa 。仓库。JpaSpecificationExecutor ; 导入组织。弹簧框架。数据。jpa 。仓库。查询; 导入组织。弹簧框架。数据。仓库。查询。参数; 导入 java 。
util.List;
public interface UserRepository extends JpaRepository<User, Long>{
/**
* 根据年龄查询用户信息
* @param age
* @return
*/
List<User> findAllByAge(Integer age);
/**
* 根据用户性别和所属组织名称查询用户信息
* @param userSex
* @param orgName
* @return
*/
List<User> findBySexAndOrg(@Param("sex") Integer sex, @Param("name") String name);
/**
*根据用户名模糊查询
* @return
* /
列出<用户> findAllByNameLike(@Param(“名称”)字符串名称);} ``除了`发现`,`由`,`和`之外,还有一些关键字,全部定义在`PartTree`,`部分'类,组装起来可以生成各种各样的SQL,下面截取部分代码,研究员的同学可以打开原始码去阅读```java package org 。弹簧框架。数据。仓库。查询。解析器; 公共类PartTree 实现Streamable < PartTree 。OrPart > { 私有静态最终字符串KEYWORD_TEMPLATE =
“(%s)(?=(\\ p {Lu} | \\ P {InBASIC_LATIN}))” ;私有静态最终字符串QUERY_PATTERN = “查找|读取|获取|查询|流” ; 私有静态最终字符串COUNT_PATTERN = “ count” ; 私有静态最终字符串EXISTS_PATTERN = “ exists” ; 私有静态最终字符串DELETE_PATTERN = “ delete | remove” ; 私有静态最终模式PREFIX_TEMPLATE = Pattern 。编译(
“ ^(查找|读取|获取|查询|流|计数|存在|删除|删除)(((\\ p {Lu}。*?))??通过” );私有最终 PartTree 。学科学科; 私有最终 PartTree 。谓词谓词; 。。。私有静态 String [ ] split ( String text , String关键字){ 模式模式=模式。编译(字符串。格式(“(%s)(?=(\\ p {Lu} | \\ P {InBASIC_LATIN}))” ,
关键字)); 返回模式。分割(文字); } 私人静态类谓词工具可流< PartTree 。OrPart > { 私有静态最终模式ALL_IGNORE_CASE =模式。编译(“ AllIgnor(ing | e)Case” ); 私有静态最终字符串ORDER_BY = “ OrderBy” ; 私人决赛
列出< PartTree 。OrPart >节点; 私有最终 OrderBySource orderBySource ; 私有boolean alwaysIgnoreCase ; 公共谓词(字符串谓词,类< ?> domainClass ){ 字符串[ ] parts = PartTree 。分裂(此。detectAndSetAllIgnoreCase (谓词),“排序依据” )
; 如果(部分。长度> 2){抛出新的IllegalArgumentException(“在方法名称中不能多次使用OrderBy!”));}其他{这个。例程=(列表)副本。流(PartTree。分割(份[0],“或”))。。过滤器(StringUtils的::
hasText).map((part) -> {
return new PartTree.OrPart(part, domainClass, this.alwaysIgnoreCase);
}).collect(Collectors.toList());
this.orderBySource =零件。length String > subject { 匹配器GRP = LIMITED_QUERY_TEMPLATE。匹配(它);返回可选< String > subject , Pattern pattern ){== 2 ? new OrderBySource(parts[1], Optional.of(domainClass)) : OrderBySource.EMPTY;
}
}
...
}
private static class Subject {
private static final String DISTINCT = "Distinct";
private static final Pattern COUNT_BY_TEMPLATE = Pattern.compile("^count(\\p{Lu}.*?)??By");
private static final Pattern EXISTS_BY_TEMPLATE = Pattern.compile("^(exists)(\\p{Lu}.*?)??By");
private static final Pattern DELETE_BY_TEMPLATE = Pattern.compile("^(delete|remove)(\\p{Lu}.*?)??By"); 私有静态最终字符串LIMITING_QUERY_PATTERN = “(First | Top)(\\ d *)?” ; 私有静态最终模式LIMITED_QUERY_TEMPLATE =模式。编译(“ ^(find | read | get | query | stream)(Distinct)?(First | Top)(\\ d *)?(\\ p {Lu}。*?)?? By” ); 私人最终布尔不同; 私有最终布尔计数; 私有最终布尔存在; 私有最终布尔删除;
private final可选的< Integer > maxResults ; 公共主题(可选< ){ this 。独特= (布尔)主题。映射((它)- > { 返回它。包含(“鲜明” ); } )。orElse (假); 这个。数=
this.matches(subject, COUNT_BY_TEMPLATE);
this.exists = this.matches(subject,EXISTS_BY_TEMPLATE);
this.delete = this.matches(subject,DELETE_BY_TEMPLATE);
这个。maxResults= this。returnMaxResultsIfFirstKSubjectOrNull(主题);} private任选<Integer> returnMaxResultsIfFirstKSubjectOrNull(任选<String> subject){返回主题。映射((它)->
!grp.find()?null:StringUtils.hasText(grp.group(4))吗?Integer.valueOf(grp.group(4)):1; }); } ... private布尔匹配(返回)。映射((它)-> {返回图案。匹配(它)。发现();})。否则容易(假); }}}```
java包org。弹簧框架。数据。仓库。查询。解析器; 公共类Part {专有静态最终模式IGNORE_CASE =模式。编译(“ Ignor(ing | e)情况”);私有最终PropertyPath propertyPath; 私人最后部分。类型类型; 公共静态枚举类型{BETWEEN(2,新的字符串[] {“ IsBetween”,“之间”}),IS_NOT_NULL(0,新的字符串[] {“ IsNotNull”,”。NotNull”}), IS_NULL(0,新的字符串[] {“ IsNull”,“ Null”}),LESS_THAN(新字符串[] {“ IsLessThan”,“一个不超过”)),LESS_THAN_EQUAL(新字符)串[] { “ IsLessThanEqual”,“ LessThanEqual”}),GREATER_THAN(新字符串[] {“ IsGreaterThan”,“大于”}),GREATER_THAN_EQUAL(新变量[] {“ IsGreaterThanEqual”,“ GreaterThanEqual”}),BEFORE(新字符)串[] {“ IsBefore”,“ Before”}),AFTER(新字符串[] {“ IsAfter”,“ After”}),LIKE(新字符串[] {“ IsLike”,“ Like”})) ,, STARTING_WITH(新字符串[] {“ IsStartingWith”,“ StartingWith”,“ StartsWith”}),ENDING_WITH(新字符串[] {“ IsEndingWith”,“ EndingWith”,“ EndsWith”}),IS_NOT_EMPTY(0,新的弦[] {“ I sNotEmpty”,“ NotEmpty”}),
IS_EMPTY(0,新的字符串[] {“ IsEmpty”,“ Empty”})),
NOT_CONTAINING(新字符串[] {“ IsNotContaining”,“ NotContaining”,“ NotContains”}},包含
(新的字符串[] {“ IsContaining”,“ Containing”,“包含”}),
NOT_IN(新的字符串[] {“ IsNotIn”,“ NotIn”}),
IN(新字符串[] {“ IsIn”,” In “}}),
NEAR(新字符串[] {“ IsNear”,“ Near”}”),
WITHIN(新字符串[] {“ IsWithin”,“ Inside”}),
REGEX(新字符串[] { “ MatchesRegex”,“ Matches”,“ Regex”}),
EXISTS(0,新字符串[] {“ Exists”}),
TRUE(0,新字符串[] {“ IsTrue”,“ True”}),
FALSE(0,新字符串[] {“ IsFalse”,“ False”}),
NEGATING_SIMPLE_PROPERTY(新字符串[] {“ IsNot”,“ Not”}),SIMPLE_PROPERTY(新字符串[] {“是”,'等于'});私人静态最终列表<部分类型> ALL =副本.asList( IS_NOT_NULL,IS_NULL,不到LESS_THAN,LESS_THAN_EQUAL,GREATER_THAN,GREATER_THAN_EQUAL
之前,之后,NOT_LIKE,LIKE,STARTING_MPITH,ENDING_EITH,TY_IST_T,NOT_IN,IN,NEAR ININ,TY_TY,TY,TY,PER,TY,PER,PER
长度> ALL_KEYWORDS ;私有最终列表<String>关键字; 私有final int numberOfArguments; 静态。List{String> allKeywords = new ArrayList();。。。。。继承器var1 = ALL。继承器(); 而(VAR1。具有下一个()){部分。型型=(部分。类型)VAR1。下一个();
allKeywords。addAll(类型。关键字);} ALL_KEYWORDS =集合。unmodifiableList(allKeywords);}} }```
可以看到单表操作的大部分需求JPA都为我们提供了现成的实现,但也支持我们使用@Query
注解自定义查询SQL,方便有SQL基础的同学使用,SQL可控性强。
java包com。一竹。仓库; 引入com。一竹。实体。用户; jpa。仓库。实体图;导入组织。弹簧框架。数据。jpa。仓库。JpaRepository;导入组织。弹簧框架。数据。导入组织。弹簧框架。数据
。jpa。仓库。JpaSpecificationExecutor;导入组织。弹簧框架。数据。jpa。仓库。查询;导入组织。弹簧框架。数据。仓库。查询。参数; 引入java。实用程序。清单; 公共接口UserRepository扩展了JpaRepository <User,
Long>{
/**
* 查询所有用户信息
* @return
*/
@Query(value = "from User u")
List<User> findAll();
/**
* 根据年龄查询用户信息
* @param age
* @return
*/
@Query(value = "select * from t_user u where u.user_age = ?1", nativeQuery = true)
List<User> findAllByAge(Integer age); / ** *根据用户性别和所属组织名称查询用户信息 * @param userSex * @param orgName * @return * / @Query ( value = “从用户u中选择u从u左加入u.org o其中u.userSex =: userSex和o.orgName =:orgName“ ) 列表<用户> findUsersBySexAndOrg (@Param (” userSex“ )整数userSex ,@Param (” orgName“ )字符串orgName ); } ```
多表关联
@OneToOne
,@OneToMany
,@ManyToOne
,@ManyToMany
长orgId; @ApiModelProperty(值=“用户信息”)@OneToOne @JoinColumn(名称=“ id”,可更新= false,可插入= false)私有UserInfo userInfo;@ApiModelProperty(值=“用户所属组织”)@ManyToOne @JoinColumn(名称=“ org_id”,可更新= false,可插入= false) 私人组织机构;@ApiModelProperty(值=“用户角色”)@OneToMany @JoinColumn(名称=“ USER_ID”,referencedColumnName =“ ID”,可插入=假,可更新=假)@NotFound(动作= NotFoundAction。IGNORE)私有集<角色>角色; @ApiModelProperty(值 =“用户工作”)@ManyToMany @JoinTable(名称=“ t_user_job”,joinColumns = @JoinColumn(名称=“ user_id”,referencedColumnName =“ id”),inverseJoinColumns = @JoinColumn(名称=“ job_id”), referencedColumnName =“ id“))@ NotFound(动作= NotFoundAction。。)IGNORE) 私人集合<Job>工作;```
>`private Set <角色>角色;和和私订<工作>的工作;“不能用同时使用”列表代替,会报错`org.hibernate.loader.MultipleBagFetchException:不能同时获取多个袋:[com.yizhu.entity.User.jobs,com.yizhu.entity.User.roles] ` ###动态查询```java 包 com 。一竹。仓库; 导入 com 。一竹。dto 。UserQueryDto ; 导入 com 。一竹。实体。组织; 导入 com 。一竹。实体。用户; 导入组织。
弹簧框架。数据。jpa 。域。规格; 导入 javax 。坚持不懈。标准。加盟; 导入 javax 。坚持不懈。标准。JoinType ; 导入 javax 。坚持不懈。标准。谓词; 导入 java 。实用程序。ArrayList ; 导入 java 。实用程序。
清单; 导入 java 。实用程序。可选的; 公共类UserSpecs { 公共静态规范<用户> listQuerySpec ( UserQueryDto userQueryDto ){ 返回(根,查询,构建器)- > { 列表<谓词>谓词= 新ArrayList < > ();
可选的。ofNullable ( userQueryDto 。的getId ())。ifPresent (我- >谓词。添加(助洗剂。等于(根。获得(“ID” ),我))); 可选的。ofNullable ( userQueryDto 。的getName ())。ifPresent ( ñ -
>谓词。添加(建设者。等于(根。获得(“名称” ), ñ )));
可选的。ofNullable ( userQueryDto 。getAge ())。ifPresent (一- >谓词。加(建设者。等于(根。获得(“时代” ), a)));
可选的。ofNullable ( userQueryDto 。getOrgId ())。ifPresent ( OI - >谓词。添加(助洗剂。等于(根。获得(“ORGID” ), OI ))); 可选的。ofNullable ( userQueryDto 。getOrgName ())。
ifPresent(on -> {
Join<User, Organization> userJoin = root.join(root.getModel().getSingularAttribute("org", Organization.class), JoinType.LEFT);
predicates.add(builder.equal(userJoin.get("orgName"), on));
});
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
};
}
}
package com.yizhu.service;
import com.yizhu.dto.UserQueryDto;
import com.yizhu.entity.User;
import com.yizhu.repository.UserRepository;
import com.yizhu.repository.UserSpecs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> findUsersDynamic(UserQueryDto userQueryDto){
return userRepository.findAll(UserSpecs.listQuerySpec(userQueryDto));
}
}
审计功能使用
在启动类添加@EnableJpaAuditing
注解表示开启jpa审计功能
package com.yizhu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
在需要使用审计功能的实体类添加@EntityListeners(AuditingEntityListener.class)
注释解
package com.yizhu.entity;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name = "t_role")
@Data
@Builder
@EntityListeners(AuditingEntityListener.class)
public class Role implements Serializable {
private static final long serialVersionUID=1L;
@ApiModelProperty(hidden = true)
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String roleName;
@CreatedDate
private Date createTime;
@CreatedBy
private Long createId;
@LastModifiedDate
private Date updateTime;
@LastModifiedBy
private Long updateId;
}
实现AuditorAware接口,告诉容器当前登录人id
package com.yizhu.configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Optional;
public class UserAuditorAwareImpl implements AuditorAware<Long> {
@Override
public Optional<Long> getCurrentAuditor() {
// 从session中获取登录人id
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Long userId = (Long)servletRequestAttributes.getRequest().getSession().getAttribute("userId");
return Optional.of(userId);
}
}
ok,然后jpa将会根据ID
和Version
判断当前操作是更新还是添加数据,添加时会注入当前登录人id到标有@CreateBy
注解的转移上,当前时间注入到标有@CreateTime
注解分段上;更新时则注入到@LastModifiedBy
和对应@LastModifiedDate
的基线上。想详细了解的可查看org.springframework.data.jpa.domain.support.AuditingEntityListener
原始代码。
常见的坑
- N + 1问题,当使用
@ManyToMany
,@ManyToOne
,@OneToMany
,@OneToOne
关联关系的时候,FetchType怎么配置LAZY或者EAGER.SQL执行真正的时候的英文由一条主表查询和Ñ条子表查询组成的。这种查询效率一般比较低下,某些子对象有N个就会执行N + 1条SQL。使用JPA 2.1启动的@ EntityGraph,@ NamedEntityGraph可以解决该问题。如下。
serialVersionUID = 1L ; @ApiModelProperty (隐藏= 真)@Id @GeneratedValue (策略= GenerationType 。 IDENTITY )私人龙ID ; //省略其他属性} ```
```java
package com.yizhu.repository;
import com.yizhu.entity.User;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
/**
* 根据id查询用户信息
* @param id
* @return
*/
@EntityGraph(value = "User.findUsers", type = EntityGraph.EntityGraphType.FETCH)
User findAllById(Long id);
/**
* 根据name查询用户信息
* @param name
* @return
*/
@EntityGraph(value = "User.findUsers", type = EntityGraph.EntityGraphType.FETCH)
@Query(value = "select * from t_user where user_name = :name", nativeQuery = true)
List<User> findAllByUserName(@Param("name") String name);
}
- 所有的注解要么全配置在字段上,要么全配置在获得方法上,不能混用,混用就会启动不起来,但是语法配置没有问题。- 所有的关联都是支持单向关联和双向关联的,- 在所有的关联查询中,表一般是不需要建立外键索引的。。在特定情况下,视听特定业务场景而定。JSON序列化的时间使用双向注解会产生死循环,需要人为手动转换一次,或者使用@JsonIgnore 。 @mappedBy的使用需要注意。- 级联删除比较危险,建议考虑清楚,或者完全掌握。- 不同的关联关系的配置,@ JoinClumn里面的名字,referencedColumnName代表的意思是不一样的,很容易弄混,可以根据打印出来的SQL做调整。- 当配置这些关联关系的时候建议大家直接在表上面,把外键建好,然后通过后面我们介绍的开发工具直接生成,这样可以减少自己调试的时间。
JPA常用注解
摘自《 Spring Data JPA从入门到精通》
![1554619508054](http://img3.sycdn.imooc.com/5dd38a980001e64909160828.jpg)
![1554619555135](http://img1.sycdn.imooc.com/5dd38a9e00017a5808980852.jpg)
![1554619583326](http://img1.sycdn.imooc.com/5dd38aa00001f81808390276.jpg)
![1554619600777](http://img3.sycdn.imooc.com/5dd38aa40001f30a08990565.jpg)
![1554619615592](http://img2.sycdn.imooc.com/5dd38aa700010ead09290574.jpg )
![1554619631450](http://img1.sycdn.imooc.com/5dd38aab0001a22709330901.jpg )
![1554620031094](http://img1.sycdn.imooc.com/5dd38aaf0001548009410705.jpg )
![1554620213902](http://img3.sycdn.imooc.com/5dd38ab400019c5309081128.jpg )
![1554620262086](http://img4.sycdn.imooc.com/5dd38ab80001d6fa09060403.jpg )
更多信息可以关注我的个人博客:[逸竹小站]