Spring 的一个子项目,Spring Data 项目的目的是为了简化构建基于Spring 框架应用的数据访问技术,包括非关系数据库、Map-Reduce 框架、云数据服务等等。另外也包含对关系数据库的访问支持。其主要目标是使数据库的访问变得方便快捷。
SpringData 项目所支持 NoSQL 存储:
SpringData 项目所支持的关系数据存储技术:
JPA Spring Data : 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!
① JPA是什么
JPA是sun提出的一个对象持久化规范,各JavaEE应用服务器自主选择具体实现。主要实现有Hibernate、TopLink和OpenJPA等。
JPA的底层实现是一些流行的开源ORM(对象关系映射)框架,因此JPA其实也就是java实体对象和关系型数据库建立起映射关系,通过面向对象编程的思想操作关系型数据库的规范。
② JPA和Hibernate的关系
JPA 是 hibernate 的一个抽象(就像JDBC和JDBC驱动的关系)。
JPA 是规范:JPA 本质上就是一种 ORM 规范,不是ORM 框架 —— 因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现。
Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现。
③ SpringData 、hibernate以及JPA的关系
JPA是一种规范,而hibernate是实现这种规范的底层实现,spring data 对持久化接口JPA再抽象一层,针对持久层业务再进一步简化。
这样开发者就连持久层的业务逻辑也不用写了,只要按照spring data的命名规范,写好接口继承即可。
④ JPA的优势
标准化
提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
简单易用,集成方便
JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java 类一样简单,只需要使用 javax.persistence.Entity 进行注释。JPA 的框架和接口也都非常简单,
可媲美JDBC的查询能力
JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
支持面向对象的高级特性
JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,最大限度的使用面向对象的模型
⑤ JPA相关技术
JPA 包括 3方面的技术:ORM 映射元数据,JPA 的 API,查询语言(JPQL)。
ORM 映射元数据:JPA 支持 XML 和 JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。
JPA 的 API:用来操作实体对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的 JDBC和 SQL代码中解脱出来。
查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的 SQL 紧密耦合。
① Repository
根接口,其他接口继承该接口。源码如下:
/**
* Central repository marker interface. Captures the domain type to manage as well as the domain type's id type. General
* purpose is to hold type information as well as being able to discover interfaces that extend this one during
* classpath scanning for easy Spring bean creation.
*
* Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the
* same signature as those declared in {@link CrudRepository}.
*
* @see CrudRepository
* @param the domain type the repository manages
* @param the type of the id of the entity the repository manages
* @author Oliver Gierke
*/
public interface Repository {
}
基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。
② CrudRepository
基本的增删改查接口,提供了最基本的对实体类的添删改查操作,其源码如下:
/** Interface for generic CRUD operations on a repository for a specific type.*/
@NoRepositoryBean
public interface CrudRepository
extends Repository {
/* 保存单个实体:实体存在时进行update操作;实体不存在时进行insert操作 */
S save(S entity);
/* 保存实体集合*/
Iterable save(Iterable entities);
/* 根据id查找实体.*/
T findOne(ID id);
/** 根据id判断实体是否存在*/
boolean exists(ID id);
/* 查询所有实体,不用或慎用! */
Iterable findAll();
/* 根据id集合查找实体集合*/
Iterable findAll(Iterable ids);
/* 查询实体数量*/
long count();
/** 根据id删除实体 */
void delete(ID id);
/** 删除给定的实体*/
void delete(T entity);
/** 删除给定的实体集合*/
void delete(Iterable extends T> entities);
/* 删除所有实体,不用或慎用! */
void deleteAll();
}
继承 Repository,实现了一组 CRUD 相关的方法 。
③ PagingAndSortingRepository
增加了分页和排序操作,源码示例如下:
/**
* Extension of {@link CrudRepository} to provide additional methods to retrieve entities using the pagination and
* sorting abstraction.
*/
@NoRepositoryBean
public interface PagingAndSortingRepository extends CrudRepository {
/* 根据Sort返回所有实体排序过后的集合*/
Iterable findAll(Sort sort);
/* 根据Pageable返回当前页的数据信息*/
//通常会使用PageRequest
Page findAll(Pageable pageable);
}
继承 CrudRepository,实现了一组分页排序相关的方法,不过没法实现带查询条件的分页 。
PageRequest类继承示意图如下(idea ctrl+alt+shift+u快捷键查看):
具体参考:SpringDataJPA分页排序
④ JpaRepository
添加了批量操作,并从写了了父接口一些方法的返回类型,源码示例如下:
@NoRepositoryBean
public interface JpaRepository
extends PagingAndSortingRepository, QueryByExampleExecutor {
/*
*查询所有实体
*/
List findAll();
/*
* 根据sort查找所有实体---返回的实体集合是排序过的
*/
List findAll(Sort sort);
/*
*根据id集合查找对应的实体集合
*/
List findAll(Iterable ids);
/*
* 保存实体集合
*/
List save(Iterable entities);
/**
* 刷新缓存,与数据库同步
*/
void flush();
/* 保存实体并立即刷新缓存,即强制执行持久化*/
S saveAndFlush(S entity);
/**
* 删除一个实体集合
*/
void deleteInBatch(Iterable entities);
/**
* 删除所有的实体,禁用或慎用!
*/
void deleteAllInBatch();
/*根据id获取一个实体
*/
T getOne(ID id);
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
*/
@Override
List findAll(Example example);
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
*/
@Override
List findAll(Example example, Sort sort);
}
继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法 。
⑤ JpaSpecificationExecutor
用来做动态查询,可以实现带查询条件的分页,源码示例如下:
/*Interface to allow execution of {@link Specification}s based on the JPA criteria API.
*/
public interface JpaSpecificationExecutor {
/*根据查询条件返回一个实体*/
T findOne(Specification spec);
/* 根据查询条件返回多个实体.*/
List findAll(Specification spec);
/*根据查询条件和分页参数,返回当前页的实体信息.*/
Page findAll(Specification spec, Pageable pageable);
/*根据查询条件和排序规则,返回一个排序好的实体集合. */
List findAll(Specification spec, Sort sort);
/**
*根据查询条件统计实体的数量 */
long count(Specification spec);
}
不属于Repository体系,实现一组 JPA Criteria 查询相关的方法 。
⑥ Specification
Specification封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象。
Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可。
源码示例如下:
public interface Specification {
/**
* Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given
* {@link Root} and {@link CriteriaQuery}.
*
* @param root
* @param query
* @return a {@link Predicate}, may be {@literal null}.
*/
Predicate toPredicate(Root root, CriteriaQuery> query, CriteriaBuilder cb);
}
具体使用参考:SpringDataJPA查询方法那些事
① persistence.xml
根据JPA规范要求,配置persistence.xml,并存在于类路径下的 META-INF 目录中,这里我们配置如下:
org.hibernate.ejb.HibernatePersistence
可以把数据库的一些配置放到application.properties的配置文件中:
hibernate.dialect=org.hibernate.dialect.MySQLDialect
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/xxx
jdbc.username=root
jdbc.password=root
② 配置spring的applicationContext.xml文件,配置数据源,事务管理器,以及spring data jpa 的扫描目录
③ 编写我们的Repository
在Spring Data JPA扫描目录下,也就是持久层下新建一个接口继承于JpaRepository:
public interface TaskDao extends JpaRepository{
}
在父接口中,Spring Data JPA已经帮我定义好一些crud和分页操作接口,我们直接用就可以了。
若我们定义的接口继承了Repository,则该接口会被IOC容器识别为一个Repository Bean。并纳入到IOC容器中,进而可以在该接口中定义满足一定规则的方法。
与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。
如下两种方式是完全等价的 :
// ①
@RepositoryDefinition(domainClass=Task.class,idClass=Long.class)
public interface TaskDao {
}
// ②
public interface TaskDao extends Repository{
}
④ service
SpringBoot开启事务很简单,只需要一个注解@Transactional 就可以了。因为在SpringBoot中已经默认对jpa、jdbc、mybatis开启了事务,引入它们依赖的时候,事务就默认开启。
在service层新建TaskService,并把TaskDao 注入到该bean中:
@Service
public class TaskService {
@Autowired
TaskDao taskDao ;
/**
* 保存更新实体类
* @param task
* @return
*/
@Transactional
public Task saveOrUpdateTask(Task task){
if(task.getId() > 0){ //更新操作
task.setTaskName("newTaskName");
}else{ //保存操作
task.setCreateTime(new Date());
}
return this.taskDao.save(task); //save会根据实体类是否有id进行保存或者更新操作
}
/**
* 根据id删除一个实体
* @param id
*/
@Transactional
public void deleteTask(Long id){
this.taskDao.delete(id);
}
/**
* 获取分页数据
* @param page
* @param size
* @return
*/
public Page findPageTask(int page ,int size){
//直接分页不排序
Page list1 = this.taskDao.findAll(new PageRequest(page, size));
//如果需要分页和排序
Sort sort = new Sort(new Order("createTime"));
Page list2 = this.taskDao.findAll(new PageRequest(page, size,sort));
return list1 ;
}
/**
* 根据id获取一个实体
* @param id
* @return
*/
public Task findOne(Long id){
return this.taskDao.findOne(id);
}
}
⑤ Dao中添加自定义方法
示例如下:
public interface TaskDao extends JpaRepository{
/**
* 根据任务名获取任务列表
* @param taskName
* @return
*/
List findByTaskName(String taskName);
}
只需在service调用这个方法就可以,当然你还可以利用@Query直接在方法名上自定义查询。
public interface TaskDao extends JpaRepository{
/**
* 根据任务名获取任务列表
* @param taskName
* @return
*/
@Query("select * from Task t where t.taskName = ?")
List findByTaskName(String taskName);
}
还可以写原生的sql语句:
public interface TaskDao extends JpaRepository{
/**
* 根据任务名获取任务列表
* @param taskName
* @return
*/
@Query(value = "select * from tb_task t where t.task_name = ?",nativeQuery = true)
List findByTaskName(String taskName);
}
也可以不使用persistence.xml,将applicationContext.xml中EntityManagerFactory 修改如下:
org.hibernate.cfg.ImprovedNamingStrategy
update
true
true
org.hibernate.dialect.MySQL5InnoDBDialect
true
org.hibernate.cache.ehcache.EhCacheRegionFactory
true
spring提供三种方法集成JPA:
① LocalEntityManagerFactoryBean
适用于那些仅使用 JPA 进行数据访问的项目,该 FactoryBean 将根据JPA PersistenceProvider 自动检测配置文件进行工作,一般从“META-INF/persistence.xml”读取配置信息,这种方式最简单,但不能设置 Spring 中定义的DataSource,且不支持 Spring 管理的全局事务。
② 从JNDI中获取
用于从 Java EE 服务器获取指定的EntityManagerFactory,这种方式在进行 Spring 事务管理时一般要使用 JTA 事务管理。
在标准的java EE 启动过程中,Java EE服务器自动检测持久化单元(例如应用程序文件包中的META-INF/persistence.xml) ,以及java ee部署描述符中定义给那些持久化单元命名上下文位置的环境的persistence-unit-ref项(例如web.xml)。
在这种情况下,整个持久化单元部署,包括持久化类的织入(字码码转换)都取决于Java EE服务器。
JDBC DataSource 通过在META-INF/persistence.xml 文件中的JNDI位置进行定义,EntityManager事务与服务器的JTA子系统整合。
Spring仅仅用获得的 EntityManagerFactory, 通过依赖注入将它传递给应用程序对象,并为它管理事务(一般通过JtaTransactionManager)。
注意,如果在同一个应用程序中使用了多个持久化单元,JNDI获取的这种持久化单元的bean名称 应该与应用程序用来引用它们的持久化单元名称相符(例如@PersistenceUnit和 @PersistenceContext注解)。
③ LocalContainerEntityManagerFactoryBean
适用于所有环境的 FactoryBean,能全面控制 EntityManagerFactory 配置,如指定 Spring 定义的 DataSource 等等。
该bean有以下属性:
DefaultPersistenceUnitManager
用于解决多配置文件情况。DB2,DERBY,H2,HSQL,INFORMIX,MySQL,Oracle,
POSTGRESQL,SQL_SERVER,SYBASE