JPA介绍及实践
0.概念梳理
- DAO: Data Access Object(数据访问对象),一种设计模式,通过映射应用程序对持久层的调用,DAO提供一些特定的数据操作,而无需暴露数据库细节。
- ORM:Object Relational Mapping(对象关系映射),一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
- JPA: Java Persistence Api(Java 持久化 API),
- Hibernate:Hibernate为Java提供了一个开源的对象关系映射框架。版本3.2及更高版本提供了JPA的实现。
- Mybatis:是一个Java持久化框架,与其他的对象关系映射框架不同,MyBatis并没有将Java对象与数据库表关联起来,而是将Java方法与SQL语句关联。
- Spring Data JPA:抽象存储库的实现是Java应用程序框架Spring的领域驱动设计的关键构建块。透明地支持所有可用的JPA实现,并支持CRUD操作以及方便地执行数据库查询。
1.Hibernate总体设计
- 高亮部分属于 JPA规范
- raw jpa example
EntityManagerFactory sessionFactory = Persistence.createEntityManagerFactory( "org.hibernate.tutorial.jpa" );
EntityManager entityManager = sessionFactory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist( new Event( "Our very first event!", new Date() ) );
entityManager.persist( new Event( "A follow up event", new Date() ) );
entityManager.getTransaction().commit();
entityManager.close();
persistence.xml
META-INF/persistence.xml
<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_2_0.xsd"
version="2.0">
<persistence-unit name="org.hibernate.tutorial.jpa">
...
</persistence-unit>
</persistence>
2.association
owning side & inverse side(拥有方和被拥有方)
简单理解,关系拥有方在数据库中拥有指向被拥有方的外键.(如一对多关系中,学生拥有class_id指向班级,学生为拥有方)
- why 为什么要区分?不区分有什么问题?
没有指定owning side,Hibernate不知道该由谁来维护这个关系,会创建关联表(即双方都不维护而由第三方关联表来管理).
- how
使用@mappedBy 如下例, mappedBy出现在inverse side
@OneToOne, @OneToMany, or @ManyToMany 拥有mappedBy属性(many-to-one为什么没有?)
public class Clazz {
private int id;
private String name;
@OneToMany(mappedBy="clazz")
private List<Student> students;
}
使用@joinColumn,@joinTable 如下例,出现在owning side
public class Student {
private int id;
private String name;
@ManyToOne
@JoinColumn(
name = "clazzId")
private Clazz clazz;
}
Unidirectional and Bidirectional (单向和双向)
单向例子
public class Student {
private int id;
private String name;
@ManyToOne
@JoinColumn(
name = "clazzId")
private Clazz clazz;
}
public class Class {
private int id;
private String name;
}
双向例子
public class Student {
private int id;
private String name;
@ManyToOne
@JoinColumn(
name = "clazzId")
private Clazz clazz;
}
public class Clazz {
private int id;
private String name;
@OneToMany(mappedBy="clazz")
private List<Student> students;
}
区别:
两个方向都能获取到另一边(student可以获取class,class可以获取student),从而可以使用cascading.
四种关系
- oneToMany (1...*)
public @interface OneToMany {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.LAZY;
String mappedBy() default "";
boolean orphanRemoval() default false;
}
- ManyToMany (*...*)
public @interface ManyToMany {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.LAZY;
String mappedBy() default "";
}
- oneToOne (1...1(0))
public @interface OneToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.EAGER;
boolean optional() default true;
String mappedBy() default "";
boolean orphanRemoval() default false;
}
- manyToOne(* ... 1 (0))
public @interface ManyToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.EAGER;
boolean optional() default true;
}
reference: https://www.slideshare.net/VladMihalcea/high-performance-hibernate-devoxx-france
3.DTO projections(DTO映射)
- why:query不想取整个entity
- how:
1.Class-Based Projections
@Query("select new package.DeadlineType(a.id, a.code) from ABDeadlineType a ")
List<DeadlineType> findAllSummarizedBy();
dto+jpql
limit: 不支持嵌套,属性名必须相同
2.Interface-Based Projections
public interface DelegationApplyQueryRepository extends JpaRepository<DelegationApplyEntity, Long> {
/**
* 根据代收付申请编号和商户ID查询
*
* @param outReqNo
* @param merchantId
* @return
*/
DelegationApplyView findByOutReqNoAndMerchantId(String outReqNo, Long merchantId);
}
public interface DelegationApplyView {
String getOutReqNo();
String getOrderNo();
String getTermNo();
}
spring-data-jpa
支持嵌套,属性名不同时可以使用@value修饰,但使用后无法优化,即使用@value后会查询出所有columns
3. dynamic projections
同一个方法可以返回不同的结果.
/**
* 根据代收付申请编号和商户ID查询
*
* @param outReqNo
* @param merchantId
* @param clazz
* @return
*/
<T> T findByOutReqNoAndMerchantId(String outReqNo, Long merchantId, Class<T> clazz);
TIPS:
1.queryRepository与apprepository分离
2.使用interface-based projections 将映射接口放到model文件夹
3.view中的方法名应当严格按照entity属性来取,如需转换在DTO做
4.常用的repository建议添加 dynamic projections
5.使用native query+ view可以完成大部分业务需求查询
//使用 nativequery update的 例子
@Modifying//非查询需要标记
@Query(value = "update carloan_delegation_apply a " +
"set a.status=:status,a.finish_time=:finishTime,a.remark=:content where a.out_req_no=:outReqNo", nativeQuery = true)
int updateDelegationApply(@Param("outReqNo") String outReqNo, @Param("status") Integer status, @Param("finishTime") Date finishTime, @Param("content") String content);
4.查询
参考 git clone https://github.com/tuzhenxian/Jpa-demo
查看测试文件夹下的test case
- jpa
jpql
criteria api(2.0)
- hibernate
hql
criteria api(deprecated)
- spring data
enhanced repository
JpaSpecificationExecutor 2.0 criteria api
@query //jpql
@query(nativequery=true,) //sql db-based
@Modifying
- querydsl
5.常见问题
- LazyInitializationException
造成原因:
fetchType为lazy时,不会直接获取相关属性,等到要获取的时候由于底层的session已经关闭导致异常
bad practice:
Open Session in View anti-pattern
hibernate.enable_lazy_load_no_trans
better way:
dto projection
- 在one-to-many和many-to-many的relation中,list增删更新时,发现数据库先执行全量删除再插入的操作.
错误的应用了relation,在one-to-many中需要指定index column,many-to-many使用set.