Spring Data JPA 初体验
一,JPA相关的概念
-
JPA概述
全称是:JavaPersistence API。是SUN公司推出的一套基于ORM的规范。
Hibernate框架中提供了JPA的实现。
JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
2.JPA优势
标准化:
JPA是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
容器级特性的支持:
JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
简单方便:
JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
查询能力:
JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是HibernateHQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
高级特性:
JPA中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
3.学习JPA要明确的
JPA是一套ORM规范,hibernate实现了JPA规范 hibernate中有自己的独立ORM操作数据库方式,也有JPA规范实现的操作数据库方式。 在数据库增删改查操作中,我们hibernate和JPA的操作都要会。
二, JPA入门
1.需求介绍
本章节我们实现基于JPA注解的对象关系映射,配置实体类和数据库表的对应关系. 并且使用JPA规范中的方法实现CRUD操作。
2.JPA环境搭建
2.1 拷贝jar包到工程中
2.2 编写实体类并使用注解配置
-
使用注解代替配置文件
@Entity @Table(name="t_user") public class User { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="uid") private Integer uid; @Column(name="uname") private String uname; @Column(name="uage") private int uage; public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } public int getUage() { return uage; } public void setUage(int uage) { this.uage = uage; } @Override public String toString() { return "User [uid=" + uid + ", uname=" + uname + ", uage=" + uage + "]"; }
}
2.3创建配置文件
-
要求在src下面的META-INF文件夹下面创建一个名称为persistence.xml的文件。
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <!--Name属性用于定义持久化单元的名字 (name必选,空值也合法); transaction-type 指定事务类型(可选) 取值: JTA:默认值 RESOURCE_LOCAL --> <persistence-unit name="myPersistUnit" transaction-type="RESOURCE_LOCAL"> <!-- 三引入映射 --> <class>com.itheima.bean.User</class> <properties> <!-- 一,连接数据库的基本项 --> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" /> <property name="hibernate.connection.url" value="jdbc:mysql:///jpa" /> <property name="hibernate.connection.username" value="root" /> <property name="hibernate.connection.password" value="123456" /> <!-- 二,选配 --> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit>
2.4编写工具类
-
用于获取JPA的操作数据库对象EntityManager
public final class JPAUtil { //JPA的实体管理器工厂:相当于Hibernate的SessionFactory private static EntityManagerFactory em; //使用静态代码块赋值 static { //注意:该方法参数必须和persistence.xml中persistence-unit标签name属性取值一致 em = Persistence.createEntityManagerFactory("myPersistUnit"); } /**
- 使用管理器工厂生产一个管理器对象
- @return
*/
public static EntityManager getEntityManager() {
return em.createEntityManager();
}
}
- @return
- 使用管理器工厂生产一个管理器对象
2.5编写单元测试
-
保存用户
@Test public void fun01(){ User user = new User(); user.setUname("张三"); user.setUage(18); EntityManager manager = JPAUtil.getEntityManager(); EntityTransaction transaction = manager.getTransaction(); transaction.begin(); manager.persist(user); transaction.commit(); manager.close(); }
3.常用注解说明
- @Entity
作用:指定当前类是实体类。写上此注解用于在创建SessionFactory/EntityManager时,加载映射配置。 - @Table
作用:指定实体类和表之间的对应关系。
属性:
name:指定数据库表的名称 - @Id
作用:指定当前字段是主键。 - @GeneratedValue
作用:指定主键的生成方式。JPA的主键生成方式详解见2.4小节的说明。
属性:
strategy :指定主键生成策略。JPA支持四种生成策略,具体介绍看4小节。 - @Column
作用:指定实体类属性和数据库表之间的对应关系
属性:
name:指定数据库表的列名称。
unique:是否唯一
nullable:是否可以为空
inserttable:是否可以插入
updateable:是否可以更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。
4.主键生成策略
4.1JPA提供的类型
通过annotation(注解)来映射hibernate实体的,基于annotation的hibernate主键标识为@Id, 其生成规则由 @GeneratedValue设定的.这里的@id和@GeneratedValue都是JPA的标准用法。JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO。具体说明如下:
- IDENTITY:主键由数据库自动生成(主要是自动增长型)
用法:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uid; -
SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。 (用在oracle)
用法:@Id @GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq") @SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment")
说明:
@SequenceGenerator源码中的定义
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface SequenceGenerator {
String name();
String sequenceName() default "";
int initialValue() default 0;
int allocationSize() default 50;
}
name:表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中。
sequenceName:属性表示生成策略用到的数据库序列名称。 -
AUTO:主键由程序控制. 自动创建一张表
@Id
用法:
@GeneratedValue(strategy = GenerationType.AUTO) -
TABLE:使用一个特定的数据库表格来保存主键(了解)
用法:@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator="payablemoney_gen")
@TableGenerator(name = "pk_gen",
table="tb_generator",
pkColumnName="gen_name",
valueColumnName="gen_value",
pkColumnValue="PAYABLEMOENY_PK",
allocationSize=1
)这里应用表tb_generator,定义为 :
CREATE TABLE tb_generator (
id NUMBER NOT NULL,
gen_name VARCHAR2(255) NOT NULL,
gen_value NUMBER NOT NULL,
PRIMARY KEY(id)
)插入纪录,供生成主键使用:
INSERT INTO tb_generator(id, gen_name, gen_value)VALUES (1,PAYABLEMOENY_PK', 1);
在主键生成后,这条纪录的value值,按allocationSize递增。@TableGenerator的定义:
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface TableGenerator {
String name();
String table() default "";
String catalog() default "";
String schema() default "";
String pkColumnName() default "";
String valueColumnName() default "";
String pkColumnValue() default "";
int initialValue() default 0;
int allocationSize() default 50;
UniqueConstraint[] uniqueConstraints() default {};
}
其中属性说明:
name:
表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中。
table:
表示表生成策略所持久化的表名,例如,这里表使用的是数据库中的“tb_generator”。
catalog和schema:
具体指定表所在的目录名或是数据库名。
pkColumnName:
属性的值表示在持久化表中,该主键生成策略所对应键值的名称。例如在“tb_generator”中将“gen_name”作为主键的键值
valueColumnName:
属性的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加。例如,在“tb_generator”中将“gen_value”作为主键的值
pkColumnValue:
属性的值表示在持久化表中,该生成策略所对应的主键。例如在“tb_generator”表中,将“gen_name”的值为“CUSTOMER_PK”。
initialValue:
表示主键初识值,默认为0。
allocationSize:
表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。
UniqueConstraint:
与@Table标记中的用法类似。
4.2自定义类型
JPA提供了四种类型,有的情况下是不能满足使用的. eg: 主键类型是字符串,需要uuid,这个时候就需要自定义类型.
5.JPA的CRUD操作
三,JPA一对多配置
1.注解详解
- OneToMany
作用:
建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除 - @ManyToOne
作用:
建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。 - @JoinColumn
作用:
用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
2.使用JPA配置一对多
-
一的一方实体(Category)
@Entity @Table(name="category") public class Category { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="cid") private Integer cid; @Column(name="cname") private String cname; //在一的一方,用一个集合表示和product的关系 @OneToMany(targetEntity=Product.class,mappedBy="category") private Set<Product> products = new HashSet<Product>(); public Integer getCid() { return cid; } public void setCid(Integer cid) { this.cid = cid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } public Set<Product> getProducts() { return products; } public void setProducts(Set<Product> products) { this.products = products; } }
-
多的一方实体(Product)
@Entity @Table(name="product") public class Product { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer pid; @Column(name="pname") private String pname; @Column(name="price") private double price; //用一个对象表示,当前商品属于哪个类别 @ManyToOne(targetEntity=Category.class) @JoinColumn(name="cid",referencedColumnName="cid") private Category category; public Integer getPid() { return pid; } public void setPid(Integer pid) { this.pid = pid; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } }
3.一对多关系的CRUD
四,JPA配置多对多
1,注解详解
- @ManyToMany
作用:
用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。 - @JoinTable
作用:
针对中间表的配置
属性:
name:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段 - @JoinColumn
作用:
用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
2,使用JPA配置多对多
-
Student.java
@Entity @Table(name = "student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "sid") private Integer sid; @Column(name = "sname") private String sname; // 中间表s_c_table字段关联sid表的主键字段sid,// 中间表s_c_table的字段cid关联表的主键cid @ManyToMany @JoinTable(name = "s_c_table", // 中间表的名称 joinColumns = { @JoinColumn(name = "sid", referencedColumnName = "sid") }, inverseJoinColumns = {@JoinColumn(name = "cid", referencedColumnName = "cid") }) private Set<Course> courses = new HashSet<Course>(); public Integer getSid() { return sid; } public void setSid(Integer sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public Set<Course> getCourses() { return courses; } public void setCourses(Set<Course> courses) { this.courses = courses; }
}
-
Course.java
@Entity @Table(name="course") public class Course { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="cid") private Integer cid; @Column(name="cname") private String cname; @ManyToMany(mappedBy="courses") private Set<Student> students = new HashSet<Student>(); public Integer getCid() { return cid; } public void setCid(Integer cid) { this.cid = cid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; }
}
五,Spring Data JPA
1.概述
1.1什么是SpringDataJPA
Spring提供的一个用于简化JPA开发的框架。可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。下面的示例代码即可完成数据保存的操作,而无需具体实现类
1.2SpringDataJPA的核心接口
- Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。
- CrudRepository :是Repository的子接口,提供CRUD的功能
- PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能
- JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等。
- JpaSpecificationExecutor:用来做负责查询的接口,类似条件(QBC)查询
- Specification:是Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可
2.SpringDataJPA入门
2.1创建Java项目,导入jar包
- spring相关的jar包
- hibernate相关jar包
- springDaataJpa相关的jar: spring-data-commons.jar 、 spring-data-jpa.jar 、 hibernate-entitymanager-5.0.7.Final.jar
2.2创建持久化类
-
BaseDict
package com.itheima.crm.bean; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.annotations.GenericGenerator; @Entity @Table(name="base_dict") public class BaseDict { @Id @GeneratedValue(generator="sysuuid") @GenericGenerator(name = "sysuuid", strategy = "uuid") @Column(name="dict_id") private String dict_id; @Column(length=20) private String dict_type_code; @Column(length=40) private String dict_type_name; @Column(length=40) private String dict_item_name; @Column(length=40) private String dict_item_code; @Column private int dict_sort; @Column(length=40) private String dict_enable; @Column(length=40) private String dict_memo; public String getDict_id() { return dict_id; } public void setDict_id(String dict_id) { this.dict_id = dict_id; } public String getDict_type_code() { return dict_type_code; } public void setDict_type_code(String dict_type_code) { this.dict_type_code = dict_type_code; } public String getDict_type_name() { return dict_type_name; } public void setDict_type_name(String dict_type_name) { this.dict_type_name = dict_type_name; } public String getDict_item_name() { return dict_item_name; } public void setDict_item_name(String dict_item_name) { this.dict_item_name = dict_item_name; } public String getDict_item_code() { return dict_item_code; } public void setDict_item_code(String dict_item_code) { this.dict_item_code = dict_item_code; } public int getDict_sort() { return dict_sort; } public void setDict_sort(int dict_sort) { this.dict_sort = dict_sort; } public String getDict_enable() { return dict_enable; } public void setDict_enable(String dict_enable) { this.dict_enable = dict_enable; } public String getDict_memo() { return dict_memo; } public void setDict_memo(String dict_memo) { this.dict_memo = dict_memo; } @Override public String toString() { return "BaseDict [dict_id=" + dict_id + ", dict_type_code=" + dict_type_code + ", dict_type_name=" + dict_type_name + ", dict_item_name=" + dict_item_name + ", dict_item_code=" + dict_item_code + ", dict_sort=" + dict_sort + ", dict_enable=" + dict_enable + ", dict_memo=" + dict_memo + "]"; } }
2.3创建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <!--引入jdbc.properties --> <context:property-placeholder location="classpath:jdbc.properties"/> <context:component-scan base-package="com.itheima"/> <!--配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置entityManagerFactory --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <!--1.,配置数据源(代替四个基本项) --> <property name="dataSource" ref="dataSource"></property> <!--2,配置选配项 --> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> </props> </property> <!--3配置包扫描路径 --> <property name="packagesToScan" value="com.itheima.crm.bean"></property> <!-- 4.,配置JPA适配 --> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"></property> </bean> <!--注册适配器 --> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> <!--配置事务管理器 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"></property> </bean> <!--配置注解事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!--配置JPADao,生成代理对象 --> <jpa:repositories base-package="com.itheima.crm.dao"></jpa:repositories> <!--配置数据字典模块 --> <bean id="baseDictService" class="com.itheima.crm.service.impl.BaseDictServiceImpl"> <property name="baseDictDao" ref="baseDictDao"></property> </bean> </beans>
2.4创建Dao
public interface BaseDictDao extends PagingAndSortingRepository<BaseDict, String>{ }
3.查询方法命名规则及JPQL语法生成
3.1概述
SpringDataJPA默认情况下, 提供了查询的相关的方法, 基本上能满足我们80%左右的需要. 但是还有一些是没有满足的,我们可以遵循它的命名规范来定义方法名. 如果没有满足命名规范的, 可以在方法上加@Query注解来写语句
3.2 JPA命令规范
关键字 方法命名 sql where字句 And findByNameAndPwd where name= ? and pwd =? Or findByNameOrSex where name= ? or sex=? Is,Equals findById,findByIdEquals where id= ? Between findByIdBetween where id between ? and ? LessThan findByIdLessThan where id < ? LessThanEquals findByIdLessThanEquals where id <= ? GreaterThan findByIdGreaterThan where id > ? GreaterThanEquals findByIdGreaterThanEquals where id > = ? After findByIdAfter where id > ? Before findByIdBefore where id < ? IsNull findByNameIsNull where name is null isNotNull,NotNull findByNameNotNull where name is not null Like findByNameLike where name like ? NotLike findByNameNotLike where name not like ? StartingWith findByNameStartingWith where name like '?%' EndingWith findByNameEndingWith where name like '%?' Containing findByNameContaining where name like '%?%' OrderBy findByIdOrderByXDesc where id=? order by x desc Not findByNameNot where name <> ? In findByIdIn(Collection<?> c) where id in (?) NotIn findByIdNotIn(Collection<?> c) where id not in (?) True findByAaaTue where aaa = true False findByAaaFalse where aaa = false IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)
-
eg
public interface StandardRepository extends JpaRepository<Standard, Long> { // JPA的命名规范 List<Standard> findByName(String name); // 自定义查询,没有遵循命名规范 @Query("from Standard where name = ?") Standard findByNamexxxx(String name); // 遵循命名规范,执行多条件查询 Standard findByNameAndMaxLength(String name, Integer maxLength); // 自定义多条件查询 @Query("from Standard where name = ?2 and maxLength = ?1") Standard findByNameAndMaxLengthxxx(Integer maxLength, String name); // 使用标准SQL查询 @Query(value = "select * from T_STANDARD where C_NAME = ? and C_MAX_LENGTH = ?", nativeQuery = true) Standard findByNameAndMaxLengthxx(String name, Integer maxLength); // 模糊查询 Standard findByNameLike(String name); @Modifying // 代表本操作是更新操作 @Transactional // 事务注解 @Query("delete from Standard where name = ?") void deleteByName(String name); @Modifying // 代表本操作是更新操作 @Transactional // 事务注解 @Query("update Standard set maxLength = ? where name = ?") void updateByName(Integer maxLength, String name); }
推荐阅读
-
Spring Data JPA实现分页Pageable的实例代码
-
详解spring boot jpa整合QueryDSL来简化复杂操作
-
spring data jpa使用详解(推荐)
-
Maven工程搭建spring boot+spring mvc+JPA的示例
-
Spring Data JPA例子代码[基于Spring Boot、Mysql]
-
Spring-Data-JPA整合MySQL和配置的方法
-
聊聊Spring Cloud Cli 初体验
-
在Spring Boot中使用Spring-data-jpa实现分页查询
-
spring boot + jpa + kotlin入门实例详解
-
Spring Boot JPA如何把ORM统一起来