Spring Data Jpa - 一篇入门Spring Data Jpa

主要内容:

1. Spring Data 简介

2. Spring Data JPA 整合

3. Repository 类继承体系

4. Repository 及其子类 API 和 方法介绍

5. 为 Repository 添加自定义方法


 

1. Spring Data Jpa 概述

Spring Data : Spring 的一个子项用于简化数据库访问,支持NoSQL 关系数据存储其主要目标是使数据库的访问变得方便快捷。

SpringData 目所支持 NoSQL 存储

     – MongoDB (文档数据库)

     –Neo4j(图形数据库)

     –Redis(键/值存储)

     –Hbase(列族数据库)

SpringData 项目所支持的关系数据存储技术

    –JDBC

    –JPA

JPA Spring Data : 力于减少数据访问(DAO) 开发. 开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成

框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById()  这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User  对象Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。

Spring Data Jpa HelloWorld 开发步骤

1. 配置 Spring 整合 JPA

2. 在Spring 配置文件中配置 Spring Data,让Spring 为声明的接口创建代理对象。配置后,Spring 初始化容器时将会扫描

3. 声明持久层接口,该接口继承 Repository,Repository 是一个标记性接口,他不包含任何方法,如必要,Spring Data 可实现 Repository 其他子接口,其中定义了一些常用的增删改查以及分页相关的方法。 

4. 在接口中声明需要的方法。Spring Data 将根据给定的策略来为其生成实现代码。 

applicationContext.xml 文件配置




	
	
	
		
		
		
		
	

	
	
		
		
		
		
			
		
		
		
		

		
			
				
				org.hibernate.cfg.ImprovedNamingStrategy
				org.hibernate.dialect.MySQL5InnoDBDialect
				true
				true
				update
			
		
		
	

	
	
		
	

	
	

	
	
	
	

 代码示例:

/**
 * 持久化类
 * @author Administrator
 */
@Table(name = "JPA_PERSONS")
@Entity
public class Person {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;

	private String lastName;

	private String email;

	private Date birth;
   // getter & setter ... 
}


/**
 * Repository Bean
 * 
 * Repository
 * 泛型:第一个是需要处理的实体类型;第二个是该实体的主键类型
 * @author Administrator
 */
public interface PersonRepository extends Repository {
	// 根据 lastName 来获取对应的 Person
	Person getByLastName(String lastName);
}

// =====================
public class SpringDataTest {

	private ApplicationContext ctx = null;
	
	{
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
	}
	
	@Test
	public void testHelloWordSpringData() throws SQLException {
		PersonRepository personRepository = ctx.getBean(PersonRepository.class);
		Person person = personRepository.getByLastName("chang");
		System.out.println(person);
	}
	
}

接口讲解:

Repository:

Spring Data Jpa - 一篇入门Spring Data Jpa_第1张图片

该接口是一个标记接口(java.io.Serializable也是一个标记接口),若我们定义的接口继承了 Repository,则该接口会被 IOC 容器识别为一个 Repository Bean,并将其实现类(Spring 帮我们创建)纳入到 IOC 容器,进而可以在该接口中定义满足一定规范的方法。

Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法 

    public interface RepositorySerializable> { } 

Spring Data可以让我们只定义接口,只要遵循 Spring Data的规范,就无需写实现类。 

继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass idClass 属性。如下两种方式是完全等价的

public interface PersonRepository extends Repository 

@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonRepository extends Repository

 Repository 的子接口

基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下: 

Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口

CrudRepository继承 Repository,实现了一CRUD 相关的方法 

PagingAndSortingRepository继承 CrudRepository,实现了一组分页排序相关的方法 

JpaRepository继承 PagingAndSortingRepository,实现一JPA 规范相关的方法 

自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。

JpaSpecificationExecutor属于Repository体系,实现一JPA Criteria 询相关的方法 

SpringData 方法定义规范

在 Repository 子接口中声明方法不是随便声明的,而需要符合一定的规范

1. 简单查询:查询某一个实体类或者集合,按照 Spring Data 的规范,查询方法以 find | read | get 开头,涉及条件查询时,条件的属性用条件关键字连接,需要注意的是:条件属性以首字母的大写。也支持属性的级联查询。

例如:定义一Entity 实体类 
class Person{ 
  private String firstName
  private String lastName
} 
使用And条件连接时,应这样写: 
findByLastNameAndFirstName(String lastName,String firstName); 
条件的属性名称与个数要与参数的位置与个数一一对应 

Spring Data 支持的关键字

直接在接口中定义查询方法,如果是符合规范的,可以不用写实现,目前支持的关键字写法如下:

Spring Data Jpa - 一篇入门Spring Data Jpa_第2张图片

Spring Data Jpa - 一篇入门Spring Data Jpa_第3张图片

 代码示例

// Reposiroty Bean
public interface PersonRepository extends Repository {
	// 根据 lastName 来获取对应的 Person
	Person getByLastName(String lastName);
	
	// HWERE lastName LIKE ?% AND id < ?
	List getByLastNameStartingWithAndIdLessThan(String lastName, Integer id);
	
	// HWERE lastName LIKE ?% AND id < ?
	List getByLastNameEndingWithAndIdLessThan(String lastName, Integer id);
	
	// WHERE email in (?,?,?) or birth < ?
	List getByEmailInOrBirthLessThan(List emails, Date birth);
}

// Main
public class SpringDataTest {

	private ApplicationContext ctx = null;
	private PersonRepository personRepository = null;
	
	{
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		personRepository = ctx.getBean(PersonRepository.class);
	}
	
	@Test
	public void testKeyWords() throws SQLException {
		List persons = personRepository.getByLastNameStartingWithAndIdLessThan("X", 26);
		System.out.println(persons);
		
		persons = personRepository.getByLastNameEndingWithAndIdLessThan("X", 26);
		System.out.println(persons);
		
		persons = personRepository.getByEmailInOrBirthLessThan(Arrays.asList("[email protected]","[email protected]"), new Date());
		System.out.println(persons);
	}
	
}

关键字查询优点:1. 简单,见名知意;缺点:1. 随着查询属性条件增多,方法名字过长,方法不灵活,不能实现复杂查询,例如max查询。

使用@Query注解进行查询

这种查询可以声明Repository 方法中,摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰,这Spring data 特有实现。

注意点:1. 参数传递的方式(命名参数和索引参数);2. 如何使用 @Query 进行本地SQL查询

 代码实例:

 

public interface PersonRepository extends Repository {
	// 根据 lastName 来获取对应的 Person
	Person getByLastName(String lastName);

	// HWERE lastName LIKE ?% AND id < ?
	List getByLastNameStartingWithAndIdLessThan(String lastName, Integer id);

	// HWERE lastName LIKE ?% AND id < ?
	List getByLastNameEndingWithAndIdLessThan(String lastName, Integer id);

	// WHERE email in (?,?,?) or birth < ?
	List getByEmailInOrBirthLessThan(List emails, Date birth);

	// WHERE a.id > ?
	List getByAddressIdGreaterThan(Integer id);

	// 查询 id 值最大的那个 Person
	// 使用 @Query 注解可以自定义 JPQL 语句,以实现更灵活的查询
	@Query("SELECT P FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")
	Person getMaxIdPerson();

	// 为 @Query 注解传递参数的方式一:使用占位符
	@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
	List testQueryAnnotationParams1(String lastName, String email);

	// 为 @Query 注解传递参数的方式一:使用命名参数的方式
	@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
	List testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);

	// @Query("SELECT p FROM Person p WHERE p.lastName LIKE ?1 AND p.email LIKE ?2")
	// SpringData 允许在占位符上添加 %
	@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% AND p.email LIKE %?2%")
	List testQueryAnnotationLikeParams(String email, String lastName);

	// SpringData 允许在使用命名参数的方式
	@Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% AND p.email LIKE %:email%")
	List testQueryAnnotationLikeParams2(@Param("email") String email, @Param("lastName") String lastName);

	// 使用本地 SQL 进行查询:设置 nativeQuery=true 即可
	@Query(value = "SELECT count(id) FROM jpa_persons", nativeQuery = true)
	long getCount();
}

// =====================
public class SpringDataTest {

	private ApplicationContext ctx = null;
	private PersonRepository personRepository = null;
	
	{
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		personRepository = ctx.getBean(PersonRepository.class);
	}
	
	@Test
	public void testnativeQuery() throws SQLException {
		long count = personRepository.getCount();
		System.out.println(count);
	}
	
	@Test
	public void testQueryAnnotationLikeParams() throws SQLException {
//		List persons = personRepository.testQueryAnnotationLikeParams("%a%", "%A%");
//		System.out.println(persons.size());
		
		List persons = personRepository.testQueryAnnotationLikeParams("a", "A");
		System.out.println(persons.size());
		
		personRepository.testQueryAnnotationLikeParams2("a", "A");
		System.out.println(persons.size());
	}
	
	@Test
	public void testQueryAnnotationParams2() throws SQLException {
		List persons = personRepository.testQueryAnnotationParams2("[email protected]", "AA");
		System.out.println(persons);
	}
	
	@Test
	public void testQueryAnnotationParams1() throws SQLException {
		List persons = personRepository.testQueryAnnotationParams1("AA", "[email protected]");
		System.out.println(persons);
	}
	
	@Test
	public void testQueryAnnotation() {
		Person person = personRepository.getMaxIdPerson();
		System.out.println(person);
	}
	
	/**
	 * 级联查询
	 * @throws SQLException
	 */
	@Test
	public void testKeyWords2() throws SQLException {
		List persons = personRepository.getByAddressIdGreaterThan(1);
		System.out.println(persons);
	}
	
	/**
	 *简单查询
	 * @throws SQLException
	 */
	@Test
	public void testKeyWords() throws SQLException {
		List persons = personRepository.getByLastNameStartingWithAndIdLessThan("X", 26);
		System.out.println(persons);
		
		persons = personRepository.getByLastNameEndingWithAndIdLessThan("X", 26);
		System.out.println(persons);
		
		persons = personRepository.getByEmailInOrBirthLessThan(Arrays.asList("[email protected]","[email protected]"), new Date());
		System.out.println(persons);
	}
	
	@Test
	public void testHelloWordSpringData() throws SQLException {
		Person person = personRepository.getByLastName("chang");
		System.out.println(person);
	}
	
	@Test
	public void testJpa() throws SQLException {
		
	}
	
	@Test
	public void testDataSource() throws SQLException {
		DataSource dataSource = ctx.getBean(DataSource.class);
		System.out.println(dataSource.getConnection());
	}
	
}

 @Modifying 注解和事务

@Query @Modifying annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下

注意:

方法的返回值应该int,表示更新语句所影响的行数

调用的地方必须加事务,没有事务不能正常执行

Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务

对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明

进行多Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要Service 实现对多Repository 调用,并在相应的方法上声明事务

// 可以通过制定会议的 HPQL 完成 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);


// Service 层

@Service
public class PersonService {
	@Autowired
	private PersonRepository personRepository;
	@Transactional
	public void updatePersonEmail(String email, Integer id) {
		personRepository.updatePersonEmail(id, email);
	}
}

// Main 方法
@Test
public void testModifying() throws SQLException {
	//personRepository.updatePersonEmail(26, "[email protected]");
	personService.updatePersonEmail("[email protected]", 26);
}

CrudRepository接口

该接口拥有的方法:主要为增删改查方法

Spring Data Jpa - 一篇入门Spring Data Jpa_第4张图片

 PagingAndSortingRepository接口

Spring Data Jpa - 一篇入门Spring Data Jpa_第5张图片

排序方法

继承 PagingAndSortingRepository 的 Repository Bean

public interface PersonRepository extends PagingAndSortingRepository
@Test
public void testPagingAndSortingRepository() throws SQLException {
	// pageNo 从 0 开始
	int pageNo = 3 - 1;
	int pageSize = 5;

	// 排序相关的 Sort 封装了排序的信息
	// Order 是针对某一个属性进行升序还是降序
	Order order1 = new Order(Direction.DESC, "id");
	Order order2 = new Order(Direction.ASC, "email");
	Sort sort = new Sort(order1, order2);
		
	// Pageable 接口通常使用的是其 PageRequest 实现类,其中封装了需要分页的信息(含排序)
	PageRequest pageRequest = new PageRequest(pageNo, pageSize, sort);
	Page page = personRepository.findAll(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());
}

JpaRepository接口 

Spring Data Jpa - 一篇入门Spring Data Jpa_第6张图片

 该接口提供了JPA的相关功能

    List findAll(); //查找所有实体

    List> findAll(Sort sort); //排序、查找所有实体

   List> save(Iterable entities);//保存集合

   void flush();//执行缓存与数据库同步

   T saveAndFlush(T entity);//强制执行持久化

   void deleteInBatch(Iterable entities);//删除一个实体集合

 

 JpaSpecificationExecutor接口

属于Repository体系,实现一组 JPA Criteria 查询相关的方法 

Spring Data Jpa - 一篇入门Spring Data Jpa_第7张图片

 Specification:封装  JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象

public interface PersonRepository extends PagingAndSortingRepository, JpaSpecificationExecutor

测试方法:

	/**
	 * 目标:实现带查询条件的分页。 id > 5
	 * 调用 JpaSpecificationExecutor 的 Page findAll(Specification spec, Pageable pageable)
	 * Specification:封装了 JPA Criteria 查询的查询条件
	 * Pageable : 封装了请求分页的信息
	 * @throws SQLException
	 */
	@Test
	public void testJpaSpecificationExecutor() throws SQLException {
		// pageNo 从 0 开始
		int pageNo = 3 - 1;
		int pageSize = 5;

		// Pageable 接口通常使用的是其 PageRequest 实现类,其中封装了需要分页的信息(含排序)
		PageRequest pageRequest = new PageRequest(pageNo, pageSize);
	
		// 通常使用其匿名内部类
		Specification specification = new Specification() {
			/**
			 * @param root : 代表查询的实体类
			 * @param query: 可以从中得到 root 对象,即告知 JPA Criteria 查询哪一个实体类,还可以来添加查询条件,
			 *  还可以结合 EntityManager 对象得到查询的 TypedQuery 对象
			 * @param *cb: CriteriaBuilder 对象,用于创建 Criteria 相关对象的工厂,当然从中可以获取 Predicate 对象
			 * 
			 * @return *Predicate 类型,代表一个查询条件
			 */
			@Override
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				Path path = root.get("id");
				Predicate predicate = cb.gt(path, 5);
				return predicate;
			}
		};
		Page page = personRepository.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());
	}

 

Spring Data Jpa - 一篇入门Spring Data Jpa_第8张图片

 

为某一个 Repository 上添加定义方法

步骤:

定义一个接口: 声明要添加的, 并自实现的方法

提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法

声明 Repository 接口, 并继承 1) 声明的接口

使用.

注意: 默认情况下, Spring Data 会在 base-package 中查找 "接口名Impl" 作为实现类. 可以通过 repository-impl-postfix 声明后缀.

Spring Data Jpa - 一篇入门Spring Data Jpa_第9张图片

// 自定义的接口
public interface PersonDao {
	void test();
}

// 自定义接口的实现
public class PersonRepositoryImpl implements PersonDao {
	@PersistenceContext
	private EntityManager em;
	@Override
	public void test() {
		Person person = em.find(Person.class, 1);
		System.out.println(person);
	}
}

// Repository Bean 实现 自定义的接口
public interface PersonRepository extends PagingAndSortingRepository, 
	JpaSpecificationExecutor, PersonDao

// 测试方法
/**
  * 测试为某个 Repository 添加自定义的方法
  * @throws SQLException
  */
@Test
public void testCustomerRepositoryMethod() throws SQLException {
	personRepository.test();
}

 

为所有的 Repository 都添加自实现的方

步骤:

声明一个接口, 在该接口中声明需要自定义的方法, 且该接口需要继承 Spring Data Repository.

提供 1) 所声明的接口的实现类. 继承 SimpleJpaRepository, 并提供方法的实现

定义 JpaRepositoryFactoryBean 的实现类, 使其生成 1) 定义的接口实现类的对象

修改 <jpa:repositories /> 节点的 factory-class 属性指向 3) 的全类名

注意: 全局的扩展实现类不要用 Imp 作为后缀名, 或为全局扩展接口添加 @NoRepositoryBean 注解告知  Spring Data: Spring Data 不把其作为 Repository

 

 

声明:以上文章整理自S硅谷,十分感谢!

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Spring,Data,Jpa)