JPA之Spring Data JPA
JPA是Java Persistence API的简称,也称为Java持久层API,已成为目前比较流行的一种ORM技术之一,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。目录1.背景2.优势2.1 标准化2.2 对容器级特性的支持2.3 简单易用,集成方便2.4 可媲美JDBC的查询能力2.5 支持面向对象的高级特性...
JPA是Java Persistence API的简称,也称为Java持久层API,已成为目前比较流行的一种ORM技术之一,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
目录
1.背景
Sun引入新的JPA ORM规范出于两个原因:
- 简化现有Java EE和Java SE应用开发工作
- Sun希望整合ORM技术,实现天下归一
2.优势
2.1 标准化
JPA 是 JCP 组织发布的JavaEE标准之一,提供相同的访问 API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
2.2 对容器级特性的支持
JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
2.3 简单易用,集成方便
JPA的主要目标之一就是提供更加简单的编程模型:
在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注解;
JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。
JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
2.4 可媲美JDBC的查询能力
JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
2.5 支持面向对象的高级特性
JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
3.JPA规约
3.1 核心对象
JPA维护一个Persistence Context(持久化上下文),在持久化上下文中维护实体的生命周期。主要包含三个方面的内容:
- ORM元数据:JPA支持annotion或xml两种形式描述对象-关系映射
- 实体操作API:实现对实体对象的CRUD操作
- 查询语言:约定了面向对象的查询语言JPQL(Java Persistence Query Language)
JPA的主要API都定义在javax.persistence包中。如果你熟悉Hibernate,可以很容易做出对应:
org.hibernate | javax.persistence | 说明 |
cfg.Configuration | Persistence | 读取配置信息 |
SessionFactory | EntityManagerFactory | 用于创建会话/实体管理器的工厂类 |
Session | EntityManager | 提供实体操作API,管理事务,创建查询 |
Transaction | EntityTransaction | 管理事务 |
Query | Query | 执行查询 |
3.2 实体生命周期
- New,新创建的实体对象,没有主键(identity)值
- Managed,对象处于Persistence Context(持久化上下文)中,被EntityManager管理
- Detached,对象已经游离到Persistence Context之外,进入Application Domain
- Removed, 实体对象被删除
EntityManager提供一系列的方法管理实体对象的生命周期,包括:
- persist, 将新创建的或已删除的实体转变为Managed状态,数据存入数据库。
- remove,删除受控实体
- merge,将游离实体转变为Managed状态,数据存入数据库。
3.3 实体关系映射
3.3.1 基本映射
对象端 | 数据库端 | annotion |
Class | Table | @Entity @Table(name = "users") |
property | column | @Column(name = "user_code", nullable = false, length=32) .name:字段名 .unique:是否唯一 .nullable:是否可以为空 .inserttable:是否可以插入 .updateable:是否可以更新 .columnDefinition: 定义建表时创建此列的DDL .secondaryTable: 从表名(默认建在主表)。如果此列不建在主表上,该属性定义该列所在从表的名字。 |
property | primary key | @Id @GeneratedValue(strategy=GenerationType.IDENTITY) |
property | NONE | @Transient 瞬时字段(不需要与数据库映射的字段) |
|
| @OrderBy(name = "group_name ASC, name DESC") 在加载数据的时候可以为其指定顺序 |
3.3.2 ID生成策略
JPA提供了以下几种ID生成策略
- GeneratorType.AUTO ,由JPA自动生成
- GenerationType.IDENTITY,使用数据库的自增长字段,需要数据库的支持(如SQL Server、MySQL、DB2、Derby等)
- GenerationType.SEQUENCE,使用数据库的序列号,需要数据库的支持(如Oracle)
- GenerationType.TABLE,使用指定的数据库表记录ID的增长 需要定义一个TableGenerator,在@GeneratedValue中引用。例如:
@TableGenerator(name="myGenerator",table="GENERATORTABLE",pkColumnName="ENTITYNAME", pkColumnValue="MyEntity", valueColumnName = "PKVALUE", allocationSize=1 )
@GeneratedValue(strategy = GenerationType.TABLE,generator="myGenerator")
3.3.3 关联关系
JPA定义了one-to-one、one-to-many、many-to-one、many-to-many 4种关系。
对于数据库来说,通常在一个表中记录对另一个表的外键关联;对应到实体对象,持有关联数据的一方称为owning-side,另一方称为inverse-side。为了编程的方便,我们经常会希望在inverse-side也能引用到owning-side的对象,此时就构建了双向关联关系。 在双向关联中,需要在inverse-side定义mappedBy属性,以指明在owning-side是哪一个属性持有的关联数据。
关系类型 | Owning-Side | Inverse-Side |
one-to-one | @OneToOne | @OneToOne(mappedBy="othersideName") |
one-to-many / many-to-one | @ManyToOne | @OneToMany(mappedBy="xxx") |
many-to-many | @ManyToMany | @ManyToMany(mappedBy ="xxx") |
其中 many-to-many关系的owning-side可以使用@JoinTable声明自定义关联表,比如Book和Author之间的关联表:
@JoinTable(name="BOOKAUTHOR",joinColumns ={ @JoinColumn(name = "BOOKID", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "AUTHORID", referencedColumnName = "id") })
关联关系还可以定制延迟加载和级联操作的行为(owning-side和inverse-side可以分别设置):
通过设置fetch=FetchType.LAZY 或 fetch=FetchType.EAGER来决定关联对象是延迟加载或立即加载。
通过设置cascade={options}可以设置级联操作的行为,其中options可以是以下组合:
- CascadeType.MERGE 级联更新
- CascadeType.PERSIST 级联保存
- CascadeType.REFRESH 级联刷新
- CascadeType.REMOVE 级联删除
- CascadeType.ALL 级联上述4种操作
3.3.4 继承关系
JPA通过在父类增加@Inheritance(strategy=InheritanceType.xxx)来声明继承关系
- 单表继承(InheritanceType.SINGLETABLE),所有继承树上的类共用一张表,在父类指定(@DiscriminatorColumn)声明并在每个类指定@DiscriminatorValue来区分类型。
- 类表继承(InheritanceType.JOINED),父子类共同的部分公用一张表,其余部分保存到各自的表,通过join进行关联。
- 具体表继承(InheritanceType.TABLEPERCLASS),每个具体类映射到自己的表。
其中1和2能够支持多态,但是1需要允许字段为NULL,2需要多个JOIN关系;3最适合关系数据库,对多态支持不好。具体应用时根据需要取舍。
3.4 事件及监听
通过在实体的方法上标注@PrePersist,@PostPersist等声明即可在事件发生时触发这些方法。
3.5 查询语言-JPQL
它是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语句绑定在一起,使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。
JPA提供两种查询方式,一种是根据主键查询,使用EntityManager的find方法:T find(Class entityClass, Object primaryKey),这种比较简单,不做过多的介绍。主要是介绍另一种JPQL查询语言(完全面向对象的,具备继承、多态和关联等特性,和hibernate HQL很相似):Query createQuery(String qlString)
3.5.1 使用参数
命令参数
Query query = em.createQuery("select p from Person p where p.personid=:Id"); query.setParameter("Id",new Integer(1));
位置参数
Query query = em.createQuery("select p from Person p where p.personid=?1"); query.setParameter(1,new Integer(1));
3.5.2 命名查询
如果某个JPQL语句需要在多个地方使用,还可以使用@NamedQuery 或者 @NamedQueries在实体对象上预定义命名查询。
@NamedQuery(name="getPerson", query= "FROM Person WHERE personid=?1")
@NamedQueries({@NamedQuery(name="getPerson1",query="FROM Person WHERE personid=?1"), @NamedQuery(name="getPersonList", query= "FROM Person WHERE age>?1") })
Query query = em.createNamedQuery("getPerson");
3.5.3 排序
Query query = em.createQuery("select p from Person p order by p.age, p.birthday desc");
3.5.3 聚合查询
JPQL支持AVG、SUM、COUNT、MAX、MIN五个聚合函数。例如:
Query query = em.createQuery("select max(p.age) from Person p"); Object result = query.getSingleResult(); String maxAge = result.toString();
3.5.3 更新和删除
JPQL不仅用于查询,还可以用于批量更新和删除。
Query query = em.createQuery("update Order as o set o.amount=o.amount+10"); //update 的记录数 int result = query.executeUpdate();
Query query = em.createQuery("delete from OrderItem item where item.order in(from Order as o where o.amount<100)"); query.executeUpdate();
query = em.createQuery("delete from Order as o where o.amount<100"); query.executeUpdate();//delete的记录数
3.5.3 更多
与SQL类似,JPQL还涉及到更多的语法,可以参考:官方文档
3.6 事务管理
JPA支持本地事务管理(RESOURCELOCAL)和容器事务管理(JTA),容器事务管理只能用在EJB/Web容器环境中,事务管理的类型可以在persistence.xml文件中的“transaction-type”元素配置。
JPA中通过EntityManager的getTransaction()方法获取事务的实例(EntityTransaction),之后可以调用事务的begin()、commit()、rollback()方法。
4.Spring Data JPA
4.1 概述
它是Spring基于ORM框架、JPA规范封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作,它提供了包括增删改查等在内的常用功能,且易于扩展,可以极大的提高开发效率。
4.2 版本说明
4.3 接口规约
Repository:最顶层的接口,是一个空接口,目的是为了统一所有的Repository的类型,且能让组件扫描的时候自动识别;
CrudRepository:Repository的子接口,提供增删改查CRUD的功能;
PagingAndSortingRepository:CrudRepository的子接口,添加分页排序的功能;
JpaRepository:PagingAndSortingRepository的子接口,增加批量操作等功能;
JpaSpeccificationExecutor:用来做复杂查询的接口。
4.4 快速开始
step1:pom添加依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.15.RELEASE</version>
</dependency>
step2:添加配置
如果是spring-boot
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
}
}
如果spring项目,
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="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.xsd">
<repositories base-package="com.acme.repositories" />
</beans:beans>
4.5 Spring Data JPA JPQL查询语言
简单查询就是根据方法名来自动生成SQL
public interface UserRepository extends JpaRepository<User, Long> {
User findByUserName(String userName);//语法是findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy后面跟属性名称
User findByUserNameOrEmail(String username, String email);
Long deleteById(Long id); //修改、删除、统计也是类似语法
}
//下面使用默认方法
@Test
public void testBaseQuery() throws Exception {
User user=new User();
userRepository.findAll();
userRepository.findOne(1l);
userRepository.save(user);
userRepository.delete(user);
userRepository.count();
userRepository.exists(1l);
}
4.5.1 定义查询-关键字
基本语法规则:JPQL从实体中查询实体属性名,而SQL从表中查询字段名,【:+属性名】表示参数。
- 算术运算符:+、—、*、/;
- 比较运算符:=、>、<、>=、<=、<>;
- 逻辑运算符:between,like,in,is null判断普通属性为空,is empty判断类型为集合的属性为空,member of,not(表示否定),and,or。
内置函数
- 字符串函数:concat,substring,trim(去掉空格或者前缀后缀),upper,lower,length,locate;
- 日期时间函数:CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP;
- 算术函数:ABS,SQRT,MOD,SIZE(集合中的元素个数,作为函数只用在where条件中,或者【集合属性.size】的用法)。
Keyword | Sample | JPQL snippet |
---|---|---|
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) |
4.5.2 使用@Query
- 基于位置或名称的参数绑定:【?序号】或者【:名称】,设置参数时setParameter中传递序号或者名称字符串。
- 查询中使用构造器,实现返回实体或者属性列表;
- 本地化查询:见后续说明。
//? 展示位置参数绑定
@Query("from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
//: 展示名字参数绑定
@Query(value = "from User u where u.name=:name and u.email=:email")
User findByNameAndEmail(@Param("name")String name, @Param("email")String email);
//展示like模糊查询
@Query(value = "from User u where u.name like %:nameLike%")
List<User> findByNameLike(@Param("nameLike")String nameLike);
//展示时间间隔查询
@Query(value = "from User u where u.createDate between :start and :end")
List<User> findByCreateDateBetween(@Param("start")Date start, @Param("end")Date end);
//展示传入集合参数查询
@Query(value = "from User u where u.name in :nameList")
List<User> findByNameIn(@Param("nameList")Collection<String> nameList);
//展示传入Bean进行查询(SPEL表达式查询)
@Query(value = "from User u where u.name=:#{#usr.name} and u.password=:#{#usr.password}")
User findByNameAndPassword(@Param("usr")User usr);
//展示使用Spring自带分页查询
@Query("from User u")
Page<User> findAllPage(Pageable pageable);
//展示带有条件的分页查询
@Query(value = "from User u where u.email like %:emailLike%")
Page<User> findByEmailLike(Pageable pageable, @Param("emailLike")String emailLike);
4.5.3 排序+分页+分组 +table join
通过EntityManager对象的createQuery方法创建Query对象实例
排序:关键字(order by)属性名(desc|asc)
分页:原理调用Query对象的setFirstResult和setMaxResulet实现分页,从0开始计数。Pageable 是spring封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则,当查询中有多个参数的时候Pageable建议做为最后一个参数传入。
分组:语法【select 实体属性 聚合函数(实体属性)from实体 where条件 group by 实体属性 having条件】。常用聚合函数avg(distinct...),sum(distinct...),count(distinct...),min,max。where和having的区别:执行顺序不同,having后面可以接聚合函数,而where不能接聚合函数。
表关联:包含inner join、left join这里不再赘述
//userRepository类
Page<User> findALL(Pageable pageable);//分页
Page<User> findByUserName(String userName,Pageable pageable);//过滤分页
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);//过滤分页
User findFirstByOrderByLastnameAsc();//排序
User findTopByOrderByAgeDesc();//排序
List<User> findFirst10ByLastname(String lastname, Sort sort); //排序
List<User> findTop10ByLastname(String lastname, Pageable pageable);
@Test
public void testPageQuery() throws Exception {
int page=1,size=10;
Sort sort = new Sort(Direction.DESC, "id");
Pageable pageable = new PageRequest(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByUserName("testName", pageable);
userRepository.findFirst10ByLastname("lannister", new Sort("firstname"));
}
public interface HotelSummary {
City getCity();
String getName();
Double getAverageRating();
default Integer getAverageRatingRounded() {
return getAverageRating() == null ? null : (int) Math.round(getAverageRating());
}
}
@Query("select h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r group by h")
Page<HotelSummary> findByCity(Pageable pageable);
Page<HotelSummary> hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name"));
for(HotelSummary summay:hotels){
System.out.println("Name" +summay.getName());
}
4.5.4 支持SpEL
@Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
4.5.5 修改查询(增删改)
使用@Modifying自定义sql来完成增删改操作,更多
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
@Transactional(timeout = 10) //添加事务支持
@Modifying
@Query("delete from User u where user.role.id = ?1")
void deleteInBulkByRoleId(long roleId);
//等价于deleteInBulkByRoleId
void deleteByRoleId(long roleId);
4.5.6 使用Query Hints
@QueryHints(value = { @QueryHint(name = "name", value = "value")},forCounting = false)
Page<User> findByLastname(String lastname, Pageable pageable);
4.6 原生SQL查询
有些时候,JPQL使用不当会导致转化成的sql并不如理想或者特定场合需要sql优化,还是得用到原生SQL查询的。EntityManager对象的createNativeQuery方法,可以实现十分复杂的查询,但是需要对查询结果进行实体映射处理,并且不能跨数据库。
@Query注解,此时nativeQuery参数应该设置为true
@Query(value = "select * from user u where u.id=:id", nativeQuery = true)
User findByIdNative(@Param("id")Long id);
@Query(value = "select * from user", nativeQuery = true)
List<User> findAllNative();
//分页查询
@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
4.7 存储过程
@Entity
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
public class User {}
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);
4.8 事务
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
}
4.9 审计
class Customer {
@CreatedBy
private User user;
@CreatedDate
private DateTime createdDate;
}
4.10 拓展篇-JpaContext
//自定义Repository实现
class UserRepositoryImpl implements UserRepositoryCustom {
private final EntityManager em;
@Autowired
public UserRepositoryImpl(JpaContext context) {
this.em = context.getEntityManagerByManagedType(User.class);
}
}
todo
更多推荐
所有评论(0)