SpringData概述
- Spring Data : Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。
- SpringData 项目所支持 NoSQL 存储:
- MongoDB (文档数据库)
- Neo4j(图形数据库)
- Redis(键/值存储)
- Hbase(列族数据库)
- SpringData 项目所支持的关系数据存储技术:
- JDBC
- JPA
JPA Spring Data : 致力于减少数据访问层 (DAO) 的开发量. ==开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!==
框架怎么可能代替开发者实现业务逻辑呢?
Spring Data JPA 做的便是==规范方法==的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象
SpringData JPA进行持久化开发的4个步骤
- Spring整合JPA
Spring配置SpringData
- 让Spring为声明的接口创建代理对象
声明持久层的接口(继承Repository)
- Repository 是一个标记型接口,它不包含任何方法,如必要,Spring Data 可实现 Repository 其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法。
在接口中声明方法
- Spring Data 将根据给定的策略(具体策略稍后讲解)来为其生成实现代码。
框架搭建
导入jar包
配置配置文件
Spring整合JPA(完整版)
<?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.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.xiaoming.springdata"></context:component-scan>
<!-- 1. 配置数据源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<!-- 配置其他属性 -->
</bean>
<!-- 2. 配置 JPA 的 EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"></property>
<!-- 配置 JPA 提供商的适配器. 可以通过内部 bean 的方式来配置 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置实体类所在的包 -->
<property name="packagesToScan" value="com.xiaoming.springdata"></property>
<!-- 配置 JPA 的基本属性. 例如 JPA 实现产品的属性 -->
<property name="jpaProperties">
<props>
<!-- 二级缓存相关 -->
<!--
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop>
-->
<!-- 生成的数据表的列的映射策略 -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!-- hibernate 基本属性 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 3.配置JPA使用的事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 4. 配置支持注解的事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--上面的是Spring整合JPA-->
<!-- 5. 配置 SpringData -->
<!-- 加入 jpa 的命名空间 -->
<!--
base-package: 扫描 Repository Bean 所在的 package
为扫描的包中继承Repository或其子接口的接口创建代理对象,并将代理对象注册为SpringBean
-->
<jpa:repositories base-package="com.xiaoming.springdata" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
</beans>
在Spring配置文件中配置SpringData
<!-- 5. 配置 SpringData -->
<!-- 加入 jpa 的命名空间 -->
<!--
base-package: 扫描 Repository Bean 所在的 package
为扫描的包中继承Repository或其子接口的接口创建代理对象,并将代理对象注册为SpringBean
-->
<jpa:repositories base-package="com.xiaoming.springdata" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
SpringData的约束
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
Person类
@Table(name = "person")
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private Integer age;
.......
}
PersonRepository类
public interface PersonRepository extends Repository<Person,Long> {
//根据用户名获取person对象
Person getByUsername(String username);
}
代码测试(每完成一步测试一下)
package com.xiaoming.springdata.test;
import com.xiaoming.springdata.domain.Person;
import com.xiaoming.springdata.repsotory.PersonRepsotory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
/**
* 测试SpringData项目是否搭建成功
*/
public class InitTest {
private ApplicationContext applicationContext = null;
{
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void testHelloWorldSpringData() throws Exception{
PersonRepsotory personRepsotory = applicationContext.getBean(PersonRepsotory.class);
System.out.println(personRepsotory.getClass().getName());
Person person = personRepsotory.getByUsername("张三");
System.out.println(person);
}
@Test
public void testJpa(){
}
/**
* 测试数据库连接池是否整合成功
*/
@Test
public void testDataSource() throws Exception{
DataSource dataSource = applicationContext.getBean(DataSource.class);
System.out.println(dataSource.getConnection());
}
}
Repository接口详解
Repository接口的概述
- Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法
public interface Repository<T, ID extends Serializable> {}
Spring Data可以让我们只定义接口,只要遵循 Spring Data的规范,就无需写实现类。
与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的
//@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonRepository extends Repository<Person,Long> {
......
}
Repository的子接口
基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:
- Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类
- CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
- PagingAndSortingRepository: 继承 - CrudRepository,实现了一组分页排序相关的方法
- JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
- 自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。
- JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
SpringData方法定义规范
简单条件查询(查询某一个实体类或者集合)
- 简单条件查询: 查询某一个实体类或者集合
- ==按照 Spring Data 的规范,查询方法以 find | read | get 开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写==。
- 例如:定义一个 Entity 实体类
class User{
private String firstName;
private String lastName;
......
}
使用And条件连接时,应这样写:==findByLastNameAndFirstName(String lastName,String firstName)==;
==条件的属性名称与个数要与参数的位置与个数一一对应==
SpringData支持的关键字
==直接在接口中定义查询方法,如果是符合规范的,可以不用写实现==
测试代码
PersonRepository
public interface PersonRepository extends Repository<Person,Long> {
//根据用户名获取person对象
Person getByUsername(String username);
//WHERE username LIKE ?% AND id < ?
//这个方法like后面没有 自动添加 % 传的参数中需要手动添加
List<Person> getByUsernameLikeAndAgeGreaterThan(String username,Integer age);
//是否包含该字符串(jpql语句不会写emmm)
List<Person> getByUsernameContaining(String username);
//WHERE username IN (?, ?, ?) OR age < ?
List<Person> getByUsernameInOrAgeLessThanEqual(List<String> name,Integer age);
}
RepositoryTest(测试一部分)
public class SpringDataRepositoryTest {
private ApplicationContext applicationContext = null;
private PersonRepository personRepsotory = null;
{
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
personRepsotory= applicationContext.getBean(PersonRepository.class);
}
@Test
public void testKeyWords(){
//Like关键字后面只添加 ? 没有添加 % 手动添加
List<Person> personList = personRepsotory.getByUsernameLikeAndAgeGreaterThan("%a%", 45);
System.out.println(personList);
}
@Test
public void testKeyWords2(){
List<Person> personList = personRepsotory.getByUsernameContaining("a");
System.out.println(personList.size());
System.out.println(personList);
}
@Test
public void testKeyWords3(){
List<Person> personList = personRepsotory.getByUsernameInOrAgeLessThanEqual(Arrays.asList("aa","bb","cc"),47);
System.out.println(personList.size());
System.out.println(personList);
}
}
简单条件查询的弊端
- 需要满足规范方法名太长
- 灵活性较差
简单条件查询进行级联查询时出现的问题
- 简单条件查询是是支持级联查询的,默认使用左外连接
- 但是如果要查询的级联的属性的名称跟本类中的属性名称重复,那么优先使用本类的属性
- 如果想要使用级联属性那么属性之间应该用_进行连接
List<Person> getByAddress_IdGreaterThan(Integer id);
使用Query注解
这种查询可以声明在 Repository 方法中,==摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明==,结构更为清晰,这是 Spring data 的特有实现
//查询 id 值最大的那个 Person
//使用 @Query 注解可以自定义 JPQL 语句以实现更灵活的查询
@Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")
Person getMaxIdPerson();
动态参数赋值问题
索引参数
@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName, String email);
//SpringData 允许在占位符上添加 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List<Person> testQueryAnnotationLikeParam(String lastName, String email);
命名参数(推荐)
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
测试代码
- PersonRepository
//查询 id 值最大的那个 Person
//使用 @Query 注解可以自定义 JPQL 语句以实现更灵活的查询
@Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")
Person getMaxIdPerson();
//为 @Query 注解传递参数的方式1: 使用占位符.
@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName, String email);
//为 @Query 注解传递参数的方式1: 命名参数的方式.
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
//SpringData 允许在占位符上添加 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List<Person> testQueryAnnotationLikeParam(String lastName, String email);
//SpringData 允许在占位符上添加 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")
List<Person> testQueryAnnotationLikeParam2(@Param("email") String email, @Param("lastName") String lastName);
//设置 nativeQuery=true 即可以使用原生的 SQL 查询
@Query(value="SELECT count(id) FROM jpa_persons", nativeQuery=true)
long getTotalCount();
- SpringDataRepositoryTest
@Test
public void testQueryAnnotationParams1(){
List<Person> persons = personRepsotory.testQueryAnnotationParams1("AA", "[email protected]");
System.out.println(persons);
}
@Test
public void testQueryAnnotationParams2(){
List<Person> persons = personRepsotory.testQueryAnnotationParams2("[email protected]", "AA");
System.out.println(persons);
}
@Test
public void testQueryAnnotationLikeParam(){
// List<Person> persons = personRepsotory.testQueryAnnotationLikeParam("%A%", "%bb%");
// System.out.println(persons.size());
// List<Person> persons = personRepsotory.testQueryAnnotationLikeParam("A", "bb");
// System.out.println(persons.size());
List<Person> persons = personRepsotory.testQueryAnnotationLikeParam2("bb", "A");
System.out.println(persons.size());
}
@Test
public void testNativeQuery(){
long count = personRepsotory.getTotalCount();
System.out.println(count);
}
@Query 与 @Modifying 执行更新操作
因为Repository接口是顶层的接口是一个空的接口,作为标记,我们前面讲到的全是查询的方法,因为hql不支持insert所以我们只能用hql进行更新操作
想要执行更新操作我们有两种方法,第一种是继承Repository的子接口,里面声明了增删改的方法,还有一种就是在Repository接口的基础上使用@Query跟@Modifying注解来执行操作
//可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
//在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作
//UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作.
//默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!
@Modifying
@Query("UPDATE Person p SET p.email = :email WHERE id = :id")
void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);
事物
Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。
对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明
进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。
CrudRepository接口
CrudRepository 接口提供了最基本的对实体类的添删改查操作
- T save(T entity);//保存单个实体
- Iterable save(Iterable
@Test
public void testCrudRepository(){
List<Person> persons = new ArrayList<>();
Person p1 = new Person();
p1.setUsername("王五");
p1.setPassword("123");
p1.setAge(18);
Person p2 = new Person();
p1.setUsername("赵六");
p1.setPassword("123");
p1.setAge(18);
persons.add(p1 );
persons.add(p2 );
// personService.savePersons(persons);
personRepsotory.save(persons);
PagingAndSortingRepository接口
该接口提供了分页与排序功能
- Iterable findAll(Sort sort); //排序
- Page findAll(Pageable pageable); //分页查询(含排序功能)
- PagingAndSortingRepository接口中的方法
@Test
public void testPagingAndSortingRespository(){
//pageNo 从 0 开始.
int pageNo = 6 - 1;
int pageSize = 5;
//Pageable 接口通常使用的其 PageRequest 实现类. 其中封装了需要分页的信息
//排序相关的. Sort 封装了排序的信息
//Order 是具体针对于某一个属性进行升序还是降序.
Order order1 = new Order(Direction.DESC, "id");
Order order2 = new Order(Direction.ASC, "email");
Sort sort = new Sort(order1, order2);
PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
Page<Person> page = personRepsotory.findAll(pageable);
System.out.println("总记录数: " + page.getTotalElements());
System.out.println("当前第几页: " + (page.getNumber() + 1));
System.out.println("总页数: " + page.getTotalPages());
System.out.println("当前页面的 List: " + page.getContent());
System.out.println("当前页面的记录数: " + page.getNumberOfElements());
}
- Repository中简单查询
//根据用户名获取person对象
List<Person> getByUsernameContaining(String username, Pageable pageable);
@Test
public void testPagingAndSortingRepository(){
//SpringData实现分页要传入Pageable接口类型的参数-->实现类PageRequest
//PageRequest 需要参数 pageNumber,pageSize Sort(排序的看需求)
int pageNumber = 0; //当前页 第3页 从0开始
int pageSize = 9; //每页记录数5
//不排序的
// PageRequest pageRequest = new PageRequest(pageNumber,pageSize);
//排序
Sort.Order order = new Sort.Order(Sort.Direction.DESC,"age");
Sort sort = new Sort(order);
PageRequest pageRequest = new PageRequest(pageNumber,pageSize,sort);
List<Person> persons = personRepsotory.getByUsernameContaining("a", pageRequest);
System.out.println(persons.size());
System.out.println(persons);
}
JpaRepository接口
该接口提供了JPA的相关功能
- List findAll(); //查找所有实体
- List findAll(Sort sort); //排序、查找所有实体
- List save(Iterable
@Test
public void testJpaRepository(){
Person person = new Person();
person.setBirth(new Date());
person.setEmail("[email protected]");
person.setLastName("xyz");
person.setId(28);
Person person2 = personRepsotory.saveAndFlush(person);
System.out.println(person == person2);
}
JpaSpecificationExecutor接口
不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象
前面讲到的Repository体系中分页查询,添加条件不方便,这个外来的接口极大地方便了我们带条件的分页查询
我们可以写一个通用的带条件的分页查询
/**
* 目标: 实现带查询条件的分页. age <45 并按照年龄排序 的条件
* <p>
* 调用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
* Specification: 封装了 JPA Criteria 查询的查询条件
* Pageable: 封装了请求分页的信息: 例如 pageNo, pageSize, Sort
*/
@Test
public void testJpaSpecificationExecutor() {
//findAll(Specification<T> spec, Pageable pageable);
//1.先解决分页排序问题
Sort.Order order = new Sort.Order(Sort.Direction.DESC, "age");
Sort sort = new Sort(order);
PageRequest pageRequest = new PageRequest(0, 20, sort);
//2.Specification<T>
//通常使用 Specification 的匿名内部类创建对象
Specification<Person> specification = new Specification<Person>() {
/**
*
* @param root(掌握):代表查询的实体类(这里指的是Person)
* @param criteriaQuery(了解):可以从中能得到实体类;可以添加查询条件;
* 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
* @param criteriaBuilder(掌握):创建Criteria相关对象的工厂,可以从中获得目标对象Predicate
* @return Predicate(掌握): 代表一个查询条件_
*/
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//先通过root导航,导航到相关属性(条件)
Path<Integer> age = root.get("age");
//将导航到的属性添加到条件中
Predicate predicate = criteriaBuilder.lt(age, 45);
return predicate;
}
};
Page<Person> page = personRepsotory.findAll(specification, pageRequest);
System.out.println("总记录数: " + page.getTotalElements());
System.out.println("当前第几页: " + (page.getNumber() + 1));
System.out.println("总页数: " + page.getTotalPages());
System.out.println("当前页面的 List: " + page.getContent());
System.out.println("当前页面的记录数: " + page.getNumberOfElements());
}
root导航图
自定义 Repository 方法
我们通过继承PagingAndSortingRepository接口和继承JpaSpecificationExecutor能够实现大部分的增删改查以及分页查询(带条件)
当不满足我们的时候我们还可以通过@Modifying跟@Query注解来手写JPQL语句(insert不行)
当还不满足我们的时候我们可以自定义Repository方法
- ==为某一个 Repository 上添加自定义方法(重点)==
- 为所有的 Repository 都添加自实现的方法
步骤(某个Repository):
- 定义一个接口: 声明要添加的, 并自实现的方法
- 提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法
- 声明 Repository 接口, 并继承 1) 声明的接口
- 使用.
- 注意: 默认情况下, Spring Data 会在 base-package 中查找 “接口名Impl” 作为实现类. 也可以通过 repository-impl-postfix 声明后缀.
为某一个 Repository 上添加自定义方法
自己写一个接口PersonDao
public interface PersonDao {
void test();
}
自己写PersonDao的实现PersonRepsotoryImpl(固定的)
public class PersonRepsotoryImpl implements PersonDao {
@PersistenceContext
private EntityManager entityManager;
@Override
public void test() {
Person person = entityManager.find(Person.class, 11);
System.out.println("-->" + person);
}
}
PersonReposity继承我们写的PersonDao
public interface PersonRepsotory extends
JpaRepository<Person, Integer>,
JpaSpecificationExecutor<Person>, PersonDao{
......
}
测试
//调用的是我们自己实现的test方法
@Test
public void testCustomRepositoryMethod(){
personRepsotory.test();
}