jpa 默认生成sql语句_JPA & Spring Data JPA学习与使用小记

本文详细介绍了JPA(Java Persistence API)的原理和用途,包括它作为ORM规范的角色,与JDBC、ORM框架的关系,以及与Spring Data JPA的联系。内容涵盖JPA的元数据映射、Criteria API、Query语言,以及如何通过注解定义实体和主键。此外,还讨论了Spring Data JPA如何简化JPA的使用,提供基于方法名的查询以及Repository的自定义实现策略。
摘要由CSDN通过智能技术生成

什么是JPA

JPA(Java Persistence API)是Java标准中的一套ORM规范,借助JPA技术可以通过注解或者XML描述【对象-关系表】之间的映射关系,并将实体对象持久化到数据库中(即Object Model与Data Model间的映射)。

JPA之于ORM(持久层框架,如MyBatis、Hibernate等,用于管理应用层Object与数据库Data之间的映射)正如JDBC之于数据库驱动。

JDBC是Java语言定义的一套标准,规范了客户端程序访问关系数据库(如MySQL、Oracle、Postgres、SQLServer等)的应用程序接口,接口的具体实现(即数据库驱动)由各关系数据库自己实现。

随着业务系统的复杂,直接用JDBC访问数据库对开发者来说变得很繁琐,代码难以维护,为解决此问题,ORM(Object Relation Mapping)框架出现了,如MyBatis、Hibernate等,百花齐放。

爱大一统的Java又出手了,Java针对ORM提出了JPA,JPA 本质上是一种 ORM 规范,不是 ORM 框架,只是定制了一些规范,提供了一些编程的 API 接口,具体实现由 ORM 厂商实现,如Hiernate、Eclipselink等都是JAP的具体实现,主要有:

JPA was meant to unify the EJB 2 CMP, JDO, Hibernate, and TopLink APIs and products

It is a standard and part of EJB3 and Java EE.

JPA主要包括Statix Named Query、Criteria Query API两部分(Query包含select、update、delete、insert等)。分为静态查询和动态查询:

静态查询在编译期即确定查询逻辑,为Static Named Query,如getByName等。

动态查询运行时确定查询逻辑,主要是Criteria API。Spring的Specification Query API对Criteria API进行了简化封装,此外Spring还提供了Example动态查询(query by example (QBE))。

使用JPA Query时与SQL Query最大的区别在于前者是面向Object Model(即定义的Java Bean)而后者是面向Data Model(即数据库表)的。

JPQL allows the queries to be defined in terms of the object model, instead of the data model. Since developers are programming in Java using the object model, this is normally more intuitive. This also allows for data abstraction and database schema and database platform independence.

Spring data JPA与JPA的关系

如上面所述,JPA是Java标准中的一套规范。其为我们提供了:

ORM映射元数据:JPA支持通过XML和注解两种元数据形式描述对象和表间的映射关系,并持久化到数据库表中。如@Entity、@Table等

JPA的Criteria API:提供API来操作实体对象,执行CRUD操作,框架会自动将之转换为对应的SQL,使开发者从繁琐的JDBC、SQL中解放出来。

JPQL查询语言:提供面向Java对象而非面向数据库自动的查询语言,避免程序与SQL语句耦合

关系图:

Spring Data JPA是Spring提供的一套简化JPA开发的框架(Criteria API还是太复杂了),按照约定好的【方法命名规则】写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等。

关系图:

通过Repository来支持上述功能,默认提供的几种Repository已经满足了绝大多数需求:

JpaRepository( 为Repository的子接口:JpaRepository -> PagingAndSortingRepository -> CrudRepository -> Repository)

QueryByExampleExecutor

JpaSpecificationExecutor

后两者用于更复杂的查询,如动态查询、关联查询等;第一种用得最多,提供基于方法名(query method)的查询,用户可基于第一种继承创建自己的子接口(只要是Repository的子接口即可),并声明各种基于方法名的查询方法。

题外话:PagingAndSortingRepository及其继承的几个接口实际上不仅可用于Spring Data JPA,还可用于Spring Data MongoDB等,可见可复用性很好。

Spring Data JPA 其实并不依赖于 Spring 框架。

JPA注解

注解位置

通过JPA定义的Object至少需要@Entity、@Id注解,示例:

import javax.persistence.*;

...

@Entitypublic classEmployee {

@Idprivate longid;privateString firstName;privateString lastName;privateAddress address;private Listphones;privateEmployee manager;private ListmanagedEmployees;

...

...

}

View Code

这些注解的位置可以有两种(Access Type):Field(在变量上)上、Property(在变量的get方法)上。一个Ojbect内的JPA注解要么在Field上要么在Property上(当然可以在类上),不能两者同时有。详情可参阅:https://en.wikibooks.org/wiki/Java_Persistence/Mapping#Access_Type

Field:will be accessed directly to store and load the value from the database。It avoids any unwanted side-effect code that may occur in the application get/set methods.

Property:get and set methods will be used to store and load the value from the database. It allows the application to perform conversion of the database value when storing it in the object.

JPA 2.0开始允许通过@Acdess注解来指定默认access type并通过该注解来指定例外acess type,从而达到混合使用的效果。

注解使用

**@Entity**

@Entity 标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的关系数据库表。(类似的,使用@Document可以映射到mongodb)

应用了此注解后,将会自动将类名映射作为数据库表名、将类内的字段名映射为数据库表的列名。映射策略默认是按驼峰命名法拆分将类名或字段名拆分成多部分,然后以下划线连接,如StudentEntity -> student_entity、studentName -> student_name。若不按默认映射,则可通过@Table、@Column指定,见下面。

**@Table**

当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用

schema属性:指定数据库名

name属性:指定表名,不知道时表名为类名

**@id**

@Id 标注用于声明一个实体类的属性映射为数据库的一个主键列

@Id标注也可置于属性的getter方法之前。以下注解也一样可以标注于getter方法前。

若同时指定了下面的@GeneratedValue则存储时会自动生成主键值,否则在存入前用户需要手动为实体赋一个主键值。主键值类型可能是:

Primitive types: boolean, byte, short, char, int, long, float, double.

Equivalent wrapper classes from package java.lang:

Byte, Short, Character, Integer, Long, Float, Double.

java.math.BigInteger, java.math.BigDecimal.

java.lang.String.

java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp.

Any enum type.

Reference to an entity object.

composite of several keys above

**@IdClass**

修饰在实体类上,指定联合主键。如:@IdClass(StudentExperimentEntityPK.class),主键类StudentExperimentEntityPK需要满足:

实现Serializable接口

有默认的public无参数的构造方法

重写equals和hashCode方法。equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时,是根据equals的返回值来判断的。hashCode方法返回当前对象的哈希码

示例:

packagecom.sensetime.sensestudy.common.entity;importjava.io.Serializable;importjava.util.Objects;importjavax.persistence.Column;importjavax.persistence.Id;importcom.sensetime.sensestudy.common.entity.ddl.ColumnLengthConstrain;public class CustomerCourseEntityPK implementsSerializable {/****/

private static final long serialVersionUID = 1L;privateString customerId;privateString courseId;

@Id

@Column(name= "customer_id", length =ColumnLengthConstrain.LEN_ID_MAX)publicString getCustomerId() {returncustomerId;

}public voidsetCustomerId(String customerId) {this.customerId =customerId;

}

@Id

@Column(name= "course_id", length =ColumnLengthConstrain.LEN_ID_MAX)publicString getCourseId() {returncourseId;

}public voidsetCourseId(String courseId) {this.courseId =courseId;

}

@Overridepublic booleanequals(Object o) {if (this ==o)return true;if (o == null || getClass() !=o.getClass())return false;

CustomerCourseEntityPK that=(CustomerCourseEntityPK) o;return Objects.equals(customerId, that.customerId) &&Objects.equals(courseId, that.courseId);

}

@Overridepublic inthashCode() {returnObjects.hash(customerId, courseId);

}

}

CustomerCourseEntityPK

packagecom.sensetime.sensestudy.common.entity;importjava.sql.Timestamp;importjavax.persistence.Basic;importjavax.persistence.Column;importjavax.persistence.Entity;importjavax.persistence.Id;importjavax.persistence.IdClass;importjavax.persistence.JoinColumn;importjavax.persistence.ManyToOne;importjavax.persistence.Table;importcom.sensetime.sensestudy.common.entity.ddl.ColumnLengthConstrain;

@Entity

@Table(name= "customer_course", catalog = "")

@IdClass(CustomerCourseEntityPK.class)public classCustomerCourseEntity {privateString customerId;privateString courseId;privateTimestamp purchaseExpireTime;private Boolean isPurchaseExpire;//由触发器自动更新//TODO zmm 最大使用人数

privateInteger maxNumber;privateCourseEntity courseByCourseId;publicCustomerCourseEntity() {

}publicCustomerCourseEntity(String customerId, String courseId, Timestamp purchaseExpireTime) {this.customerId =customerId;this.courseId =courseId;this.purchaseExpireTime =purchaseExpireTime;this.isPurchaseExpire = false;

}publicCustomerCourseEntity(String customerId, String courseId, Timestamp purchaseExpireTime, Integer maxNumber) {this.customerId =customerId;this.courseId =courseId;this.purchaseExpireTime =purchaseExpireTime;this.maxNumber =maxNumber;this.isPurchaseExpire = false;

}

@Id

@Column(name= "customer_id", length =ColumnLengthConstrain.LEN_ID_MAX)publicString getCustomerId() {returncustomerId;

}public voidsetCustomerId(String customerId) {this.customerId =customerId;

}

@Id

@Column(name= "course_id", length =ColumnLengthConstrain.LEN_ID_MAX)publicString getCourseId() {returncourseId;

}public voidsetCourseId(String courseId) {this.courseId =courseId;

}

@Basic

@Column(name= "purchase_expire_time", nullable = false)publicTimestamp getPurchaseExpireTime() {returnpurchaseExpireTime;

}public voidsetPurchaseExpireTime(Timestamp purchaseExpireTime) {this.purchaseExpireTime =purchaseExpireTime;

}

@Basic

@Column(name= "is_purchase_expire", nullable = false)publicBoolean getIsPurchaseExpire() {returnisPurchaseExpire;

}public voidsetIsPurchaseExpire(Boolean isPurchaseExpire) {this.isPurchaseExpire =isPurchaseExpire;

}

@Basic

@Column(name= "max_number")publicInteger getMaxNumber() {returnmaxNumber;

}public voidsetMaxNumber(Integer maxNumber) {this.maxNumber =maxNumber;

}

@ManyToOne

@JoinColumn(name= "course_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)publicCourseEntity getCourseByCourseId() {returncourseByCourseId;

}public voidsetCourseByCourseId(CourseEntity courseByCourseId) {this.courseByCourseId =courseByCourseId;

}

}

CustomerCourseEntity

**@EmbeddedId**

功能与@IdClass一样用于指定联合主键。不同的在于其是修饰实体内的一个主键类变量,且主键类应该被@Embeddable修饰。

此外在主键类内指定的字段在实体类内可以不再指定,若再指定则需为@Column加上insertable = false, updatable = false属性

**@GeneratedValue**

@GeneratedValue 用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:SqlServer 对应 identity,MySQL 对应 auto increment

IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式

AUTO: JPA自动选择合适的策略,是默认选项

TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。

SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式

**@Basic**

表示一个简单的属性到数据表的字段的映射,对于没有任何标注的 getXxx() 方法,默认为 @Basic

fetch 表示属性的读取策略,有 EAGER 和 LAZY 两种,分别为立即加载和延迟加载

optional 表示该属性是否允许为 null,默认为 true

**@Column**

此注解不是必须的,无此字段也会将字段映射到表列。当实体的属性与其映射的数据库表的列不同名时需要使用 @Column 标注说明,其有属性 name、unique、nullable、length 等。

类的字段名在数据库中对应的字段名可以通过此注解的name属性指定,不指定则默认为将属性名按驼峰命名法拆分并以下划线连接,如createTime对应create_time。注意:即使name的值中包含大写字母,对应到db后也会转成小写,如@Column(name="create_Time")在数据库中字段名仍为create_time。

可通过SpringBoot配置参数 spring.jpa.hibernate.naming.physical-strategy 配置上述对应策略,如指定name值是什么数据库中就对应什么名字的列名。默认值为: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

**@Transient**

表示该属性并非一个到数据库表的字段的映射,ORM 框架将忽略该属性

如果一个属性并非数据库表的字段映射,就务必将其标识为 @Transient,否则ORM 框架默认为其注解 @Basic,例如工具方法不需要映射

**@Temporal**

在 JavaAPI 中没有定义 Date 类型的精度,而在数据库中表示 Date 类型的数据类型有 Date,Time,TimeStamp 三种精度(日期,时间,两者兼具),进行属性映射的时候可以使用 @Temporal 注解调整精度。目前此注解只能用于修饰java.util.Date、java.util.Calendar类型的变量,TemporalType取DATE、TIME、TIMESTAMP时在MySQL中分别对应的DATE、TIME、DATETIME类型。示例:

@Temporal(TemporalType.TIMESTAMP)

@CreationTimestamp //org.hibernate.annotations.CreationTimestamp,用于在JPA执行insert操作时自动更新该字段值

@Column(name = "create_time", updatable=false )//为防止手动set,可设false以免该字段被更新

private Date createTime;

@Temporal(TemporalType.TIMESTAMP)

@UpdateTimestamp //org.hibernate.annotations.UpdateTimestamp,用于在JPA执行update操作时自动更新该字段值

@Column(name= "update_time")private Date updateTime;

@CreationTimestamp、@UpdateTimestamp是Hibernate的注解,SpringData JPA也提供了类似功能(推荐用此):@CreatedDate、@LastModifiedDate、@CreatedBy、@LastModifiedBy,可参阅https://blog.csdn.net/tianyaleixiaowu/article/details/77931903

**@MappedSuperClass**

用来修饰一个类,类中声明了各Entity共有的字段,也即数据库中多表中共有的字段,如create_time、update_time、id等。

标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。

标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。

允许多级继承。

**@Inheritance**

用于表结构复用。指定被该注解修饰的类被子类继承后子类和父类的表结构的关系。通过strategy属性指定关系,有三种策略:

SINGLE_TABLE:适用于共同字段多独有字段少的关联关系定义。子类和父类对应同一个表且所有字段在一个表中,还会自动生成(也可通过@DiscriminatorColumn指定)一个字段 varchar 'dtype' 用来表示一条数据是属于哪个实体的。为默认值(未使用@Inheritance或使用了但没指定strategy属性时默认采用此策略)。

JOINED:子类和父类对应不同表,父类属性对应的列(除了主键)不会且无法再出现在子表中。子表自动产生与父表主键对应的外键与父表关联。同样地也可通过@DiscriminatorColumn为父类指定一个字段用于标识一条记录属于哪个子类。

TABLE_PER_CLASS:子类和父类对应不同表且各类自己的所有字段(包括继承的)分别都出现在各自的表中;表间没有任何外键关联。此策略最终效果与@MappedSuperClass等同。

总而言之,@Inheritance、@MappedSuperClass可用于定义Inheritance关系。详情可参阅:https://en.wikibooks.org/wiki/Java_Persistence/Inheritance。这些方式的一个缺点是子类中无法覆盖从父类继承的字段的定义(如父类中name是not null的但子类中允许为null)。

除了 @Inheritance、@MappedSuperClass外,还有一种Inheritance方法(此法可解决上述不足):先定义一个Java POJO(干净的POJO,没有任何对该类使用任何的ORM注解),然后不同子类继承该父类并分别在不同子类中进行ORM定义即可。此法下不同子类拥有父类的公共字段且该字段在不同子类中对应的数据库列定义可不同。

实践示例:

翻译表与主表关联方案设计

多语言表(翻译表)与原表(主表)关联方案设计,需求:字段(列)复用以免重复代码定义、同一个列的定义如是否为空在不同表中可不一样(如有些字段主表中非空但翻译表中可空),有如下方案:

无关联,重复定义。pass

有关联

通过@MappeSuperclass,不同子类可以完全继承父类列定义且分别对应不同表,表结构完全相同,但不能覆盖父类的定义。pass

通过@Inheritance,三种策略:

SINGLE_TABLE:父、子类对应同一张表。子类无法覆盖父类的字段定义;源课程和翻译课程id一样,违背主键唯一约束。pass

JOINED:父、子类对应不同表且子类自动加与父类主键一样的字段与父类主键关联,但父表中除主键之外的所有字段无法在子表中再出现。pass

TABLE_PER_CLASS:父、子类对应不同表且表定义完全相同,无外键,但子类无法覆盖父类的字段定义(即同一字段在不同表中字段定义无法不同)。pass

定义个普通父类,子类继承父类并分别进行@Column定义:不同子类对应不同表,不同表含有的字段及定义可不一样。selected

View Code

注解扫描

这里针对SpringBoot而言。在SpringBoot中:

默认情况下,当Entity类、Repository类与主类在同一个包下或在主类所在包的子类时,Entity类、Repository类会被自动扫描到并注册到Spring容器,此时使用者无需任何额外配置。

当不在同一包或不在子包下时,需要分别通过在主类上加注解  @EntityScan( basePackages = {"xxx.xxx"}) 、 @EnableJpaRepositories( basePackages = {"xxx.xxx"}) 注解来分别指定Entity、Repository类的位置。

可多处使用@EntityScan:它们的basePackages可有交集,但必须覆盖到所有被Resository使用到的Entity否则会报错。

可多处使用@EnableJpaRepositories:它们的basePackages不能有交集否则会报重复定义的错(除非配置允许覆盖定义),必须覆盖到所有被使用到的Resository。

JPA对象属性与数据库列的映射

(attribute map between object model and data model)

以下是基本类型的映射:

对于非基本类型的属性,其映射:

法1:

By default in JPA any Serializable attribute that is not a relationship or a basic type (String, Number, temporal, primitive), will be serialized to a BLOB field.

法2:JPA 2.1起可通过 @Convert 指定属性与数据库列间的映射逻辑,其可将任意对象映射到数据库的一个列(详见后文)。在这之前没有@Convert,可以通过get、set方法实现类似效果,示例:

@Entitypublic classEmployee {

...private booleanisActive;

...

@Transientpublic booleangetIsActive() {returnisActive;

}public void setIsActive(booleanisActive) {this.isActive =isActive;

}

@BasicprivateString getIsActiveValue() {if(isActive) {return "T";

}else{return "F";

}

}private voidsetIsActiveValue(String isActive) {this.isActive = "T".equals(isActive);

}

}

View Code

Spring Data JPA使用小记

用法模板

1 将 spring-data-jpa 包,数据库驱动包等添加为项目依赖;

2 配置文件定义相应的数据源;

3 定义业务领域实体类,比如通过@Entity注解;

4 定义自己业务相关的的JPA repository接口,这些接口都继承自JpaRepository或者JpaSpecificationExecutor,如StudentRepository;

5 为应用添加注解@EntityScan、@EnableJpaRepositories,此步不是必须的,原因见前面的注解扫描一节;

6 将上面自定义JPA repository接口注入到服务层并使用它们进行相应的增删改查;

经过上述编写或配置后,就可以使用StudentRepository Bean了,即使没有实现该接口。

指定对象与数据库字段映射时注解的位置

如@Id、@Column等注解指定Entity的字段与数据库字段对应关系时,注解的位置可以在Field(属性)或Property(属性的get方法上),两者统一用其中一种,不能两者均有。推荐用前者。

JPA命名查询的原理

基本用法:通过方法名来指定查询逻辑,而不需要自己实现查询的SQL逻辑,示例:List getByName(String name)

方法名解析原理:对方法名中除了保留字(findBy、top、within等)外的部分以and为分隔符提取出条件单词,然后解析条件获取各个单词并看是否和Entity中的属性对应(不区分大小写进行比较)。get/find 与 by之间的会被忽略,所以getNameById与getById是等价的,会根据id查出整个Entity而不会只查name字段。(指定部分字段的查询见后面条目)

查询条件解析原理:假设School和Student是一对多关系,Student中有个所属的School school字段、School有个String addressCode属性,以如下查询为例:

Studetn getByNameAndSchoolAd

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值