为了方便大家学习springData,所以花了些时间整理了一下,希望能够帮到大家。当然网上各种关于springData的博文都有,建议大家多多搜索,采纳更多的方式和意见,以达到更好的学习效果。
学习了spring jpa 的朋友,在学springData时就非常容易理解了!
主要从以下来探讨:
1.springData概述
2.使用SpringData JPA进行持久层开发 hello word
—Repository接口及其子接口的介绍
—自定义repository,进行方法扩展
—调用方法时方法名的书写格式
—使用关键字创建方法名
—使用@Query注解可以自定义hql查询
—设置@nativeQuery=true,原生sql查询
—普通分页,带条件查询的分页
—批量新增,批量删除
一、springData概述:
Spring Data :Spring 的一个子项目。用于简化数据库访问,支持NoSQL和关系数据存储。其主要目标是使数据库的访问变得方便快捷。SpringData项目所支持NoSQL存储:
MongoDB(文档数据库)
Neo4j(图形数据库)
Redis(键/值存储)
Hbase(列族数据库)
SpringData项目所支持的关系数据存储技术:
JDBC
JPA
JPA Spring Data:致力于减少数据访问层(DAO)的开发量.开发者唯一要做的,就只是声明持久层的接口,其他都交给SpringData JPA 来帮你完成!
框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的ID查询出满足条件的User 对象。SpringData JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
二、SpringData JPA HelloWorld
1.使用 Spring Data JPA 进行持久层开发需要的四个步骤:
(1).配置 Spring 整合 JPA
(2).在 Spring 配置文件中配置 Spring Data,让 Spring 为声明的接口创建代理对象。配置了 后,Spring 初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。
(3)声明持久层的接口,该接口继承 Repository,Repository 是一个标记型接口,它不包含任何方法,如必要,Spring Data 可实现 Repository 其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法。
(4)在接口中声明需要的方法。Spring Data 将根据给定的策略(具体策略稍后讲解)来为其生成实现代码。
(5)同时下载 Spring Data Commons 和 Spring Data JPA 两个发布包:
Commons 是 Spring Data 的基础包,并把相关的依赖 JAR 文件加入到 CLASSPATH 中
在 Spring 的配置文件中配置 Spring Data
创建application.xml:
<?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/context http://www.springframework.org/schema/context/spring-context-4.3.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.3.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.ctg.springdata.entity"></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="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<!-- 配置其他属性 -->
</bean>
<!-- 2.配置JPA的EntityManagerFactory -->
<bean id="entityManangerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- jpa适配器 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置扫描加entity注解的包 -->
<property name="packagesToScan" value="com.ctg.springdata.entity"></property>
<!-- hibernate配置文件 -->
<property name="jpaProperties">
<props>
<!-- 生成数据表的列映射策略 -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!-- hibernate 基本属性 -->
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 3.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManangerFactory"></property>
</bean>
<!-- 4.配置支持注解的事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 5. 配置SpringData-->
<!-- 加入JPA的命名空间 -->
<!-- base-package:用于扫描Repository Bean 所在的package -->
<jpa:repositories base-package="com.ctg.springdata.entity" entity-manager-factory-ref="entityManangerFactory"></jpa:repositories>
</beans>
创建两个类:Person 和 Address 他们之间存在多对一关联关系
Person:
package com.ctg.springdata.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name="JPA_PERSONS")
public class Person {
@Id
@GeneratedValue
private Integer id;
private String lastName;
private String email;
private Date birth;
@ManyToOne
@JoinColumn(name="ADDRESS_ID")
private Address address;
@Column(name="ADD_ID")
private Integer addressId;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Person [id=" + id + ", lastName=" + lastName + ", email=" + email + ", birth=" + birth + ", address="
+ address + ", addressId=" + addressId + "]";
}
}
Address:
package com.ctg.springdata.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="JPA_ADDRESS")
public class Address {
@Id
@GeneratedValue
private Integer id;
private String province;
private String city;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
由于此处只是建立一个demo,所以中间业务层在发生事务时创建,建立持久层personRepository之前,需要了解他所要继承的类及其子类之间的关系:
(1).Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类;
(2).CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法 ;
(3).PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法 ;
(4).JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法 ;
(5).自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。
(6).JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法 ;
自定义一个PersonDao,进行方法扩展:
package com.ctg.springdata.entity;
public interface PersonDao {
void test();
}
创建personDao的实现类:
package com.ctg.springdata.entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class PersonRepositoryImpl implements PersonDao {
@PersistenceContext
private EntityManager entityManager;
@Override
public void test() {
Person person = entityManager.find(Person.class, 11);
System.out.println("---->"+person);
}
}
所以,我们在建立PesonRepository时只需要继承JpaRepository,JpaSpecificationExecutor,就可以调用其父类中所有的方法了,同时需要方法扩展时可以继承自己创建的PersonDao:
package com.ctg.springdata.entity;
import java.util.Date;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.query.Param;
/**
* 1.Repository 是一个空接口,即是一个标记接口
* 2.若我们定义的接口继承了Repository,则该接口会被IOC容器识别为一个Repository Bean。纳入到IOC容器中,进而可以在该接口
* 中定义满足一定规范的方法。
* 3.实际上,也可以通过@RepositoryDenition 注解来替代继承Repository 接口
*
* 4.在Repository 子接口中声明方法:
* ①不是随便声明的,而需要符合一定的规范
* ②查询方法以find|read|get开头
* ③设计查询条件时,条件的属性用条件关键字连接。要注意的是:条件属性以首字母大写;
* ④支持属性级联查询,若当前类有符合条件的属性,则优先使用,而不使用级联属性。
* 若要使用级联属性,则属性之间使用‘_’连接
* @author asus
*
*/
//@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonRepository extends JpaRepository<Person, Integer>,JpaSpecificationExecutor<Person>,PersonDao {
//1.根据lastName来获取对应的Person:分别使用find|read|get开头
Person getByLastName(String lastName);
Person readByLastName(String lastName);
Person findByLastName(String lastName);
//2.涉及查询条件时,条件的属性用条件关键字连接,如下:StartingWith:以什么开始;And:并且;LessThan:小于
//WHERE lastName LIKE ?% AND id<?
List<Person> getByLastNameStartingWithAndIdLessThan(String lastName,Integer id);
//3.EndingWidth:以什么结束
//WHERE lastName LIKE %? AND id<?
List<Person> getByLastNameEndingWithAndIdLessThan(String lastName,Integer id);
//4.In:在..范围内
//WHERE email IN (?,?,?) or birth<?
List<Person> getByEmailInOrBirthLessThan(List<String> emails,Date birth);
//5.属性级联查询:GreaterThan:大于
//WHRER a.id > ?
List<Person> getByAddressIdGreaterThan(Integer id);
//级联属性使用‘_下划线连接’
List<Person> getByAddress_IdGreaterThan(Integer id);
//总结1:以上使用关键字连接进行查询,缺点:①:方法名复杂,较长;②:更复杂的条件查询无法使用:比如带有子查询就无法进行;
//JP_QUERY的使用===============================================
//6.查询id值最大的那个Person:使用@Query注解可以自定义jpql 语句以实现更灵活的查询
@Query("SELECT p FROM Person p WHERE p.id=(SELECT max(p2.id) FROM Person p2 )")
Person getMaxIdPerson();
//7.带参查询
//使用占位符
@Query("SELECT p FROM Person p where p.lastName=?1 and p.email=?2")
Person getBylastNameAndEmail1(String lastName,String email);
//使用命名参数的方式
@Query("SELECT p FROM Person p where p.lastName=:lastName and p.email=:email")
Person getBylastNameAndEmail2(@Param("email")String email,@Param("lastName")String lastName);
//8.like 模糊查询
//使用占位符:是否在sql中添加入%如过没有添加,在调用方法入参时需要加入
@Query("SELECT p FROM Person p where p.lastName like ?1 or p.email like ?2")
List<Person> testdQueryAnotationLikeParam1(String lastName,String email);
//也可以在占位符上添加%
@Query("SELECT p FROM Person p where p.lastName like %?1% or p.email like %?2%")
List<Person> testdQueryAnotationLikeParam2(String lastName,String email);
//使用命名参数:
@Query("SELECT p FROM Person p where p.lastName like %:lastName% or p.email like %:email%")
List<Person> testdQueryAnotationLikeParam3(@Param("lastName")String lastName,@Param("email")String email);
//本地sql查询==============================================
//9.设置nativeQuery=true,表明是原生sql查询
@Query(value="select count(id) from jpa_persons",nativeQuery=true)
long getTotalCount();
//可以通过自定义JPQL完成UPDATE和DELETE 操作,注意:JPQL不支持使用insert
//在@Query注解中编写JPQL语句,但必须使用@Modifying进行修饰,已通知SpringData,这是一个UPDATE或Delete操作,需要使用事务定义service层
//10.@Modifying注解:进行修改,删除====================================================
@Query("update Person p set p.email=:email where p.id = :id")
@Modifying
void updatePersonEmail(@Param("id")Integer id,@Param("email")String email);
}
创建一个测试类,测试类中分别对当PersonRepository继承Repository 及其子类时调用的方法进行测试:
package com.ctg.springdata.test;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.domain.Specification;
import com.ctg.springdata.entity.Person;
import com.ctg.springdata.entity.PersonRepository;
import com.ctg.springdata.entity.PersonService;
/**
* 单元测试类
* @author asus
*
*/
public class SpringDataTest {
private ApplicationContext ctx = null;
private PersonRepository personRepository = null;
private PersonService personService=null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
personRepository=ctx.getBean(PersonRepository.class);
personService = ctx.getBean(PersonService.class);
}
//==========================personRepository继承自定义的PersonDao测试=============
@Test
public void testPersonDao(){
personRepository.test();
}
//==========================personRepository继承JpaSpecificationExecutor 测试=============
/**
* 目标:实现带查询条件的分页,id>5
* 调用JpaSpecificationExecutor 的page<T> findAll(Specification<T> spec,Pageable pageable)
* Specification:封装了JPA Criteria 查询条件
* Pageable:封装了请求分页的信息:例如pageNo ,pageSize,Sort
*/
@Test
public void testJpaSpecificationExecutor(){
int pageNo = 3;
int pageSize = 5;
PageRequest pageable = new PageRequest(pageNo-1, pageSize);
//通常使用Specification的匿名内部类:
Specification<Person> specification = new Specification<Person>(){
/**
* root:代表查询的实体类
* query:可以从中得到Root对象,即告知JPA Criteria 查询要查询哪一个实体类。还可以来添加查询条件,还可以结合EntityManager
* 对象得到最终查询的TypeQuery 对象;
* cb:CriteriaBuilder 对象。用于创建Criteria相关对象的工厂,当然可以从中获取到Predicate对象
* return:Predicate类型,代表一个查询条件
*/
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path path = root.get("id");
Predicate predicate = cb.gt(path, 5);
return predicate;
}
};
Page<Person> page = personRepository.findAll(specification, 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());
}
//==========================personRepository2继承JpaRepository 测试=============
@Test
public void testJpaRepository(){
Person person = new Person();
person.setBirth(new Date());
person.setEmail("sdjfl@ctg.com");
person.setLastName("lskdj");
person.setId(28);
//saveAndFlush 相当于 hibernate中的merge()
Person person2 = personRepository.saveAndFlush(person);
System.out.println(person==person2);
}
//==========================personRepository2继承PagingAndSortingRepository 测试=============
/**
* 测试1 :分页
*/
@Test
public void testPagingAndSortingRepository1(){
int pageNo = 3;
int pageSize = 5;
//Pageable 接口通常使用的其PageRequest 实现类,其中封装了需要分页的信息;
Pageable pageable = new PageRequest(pageNo-1, pageSize);
Page<Person> page = personRepository.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());
}
/**
* 测试2:分页,排序
*/
@Test
public void testPagingAndSortingRepository2(){
int pageNo = 3;
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);
PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
Page<Person> page = personRepository.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());
}
//==========================personRepository2继承CrudRepository 测试=============
/**
* 测试1:批量新增
*/
@Test
public void testCrudRepository(){
List<Person> persons = new ArrayList<>();
for(int i='a';i<='z';i++){
Person person = new Person();
person.setAddressId(i+1);
person.setBirth(new Date());
person.setEmail((char)i+""+(char)i+"@ctg.com");
person.setLastName((char)i+""+(char)i);
persons.add(person);
}
personService.savePersons(persons);
}
//==========================personRepository继承Repository 测试=============
/**
* 测试1
*/
@Test
public void testHelloWorldSpringData(){
System.out.println("是一个代理对象:"+personRepository.getClass().getName());
Person person = personRepository.getByLastName("aa");
System.out.println(person);
}
/**
* 测试2
*/
@Test
public void testKeyWords2(){
List<Person> persons = personRepository.getByLastNameEndingWithAndIdLessThan("a", 4);
System.out.println("查询总数:"+persons.size());
}
/**
* 测试3
*/
@Test
public void testKeyWords1(){
List<Person> persons = personRepository.getByLastNameStartingWithAndIdLessThan("a", 4);
System.out.println("查询总数:"+persons.size());
}
/**
* 测试4
*/
@Test
public void testKeyWords3(){
List<Person> persons = personRepository.getByEmailInOrBirthLessThan(Arrays.asList("aa@ctg.com","bb@ctg.com","cc@ctg.com"), new Date());
System.out.println(persons.size());
}
/**
* 测试5
*/
@Test
public void testKeyWords4(){
List<Person> persons = personRepository.getByAddressIdGreaterThan(1);
System.out.println(persons.size());
}
/**
* 测试6
*/
@Test
public void testKeyWords5(){
Person person = personRepository.getMaxIdPerson();
System.out.println(person);
}
/**
* 测试7
*/
public void testKeyWords6(){
Person person = personRepository.getBylastNameAndEmail1("aa", "aa@ctg.com");
System.out.println(person);
}
/**
* 测试8
*/
public void testdQueryAnotationLikeParam(){
List<Person> person = personRepository.testdQueryAnotationLikeParam1("%a%", "%a%");
System.out.println(person);
}
/**
* 测试9
*/
@Test
public void getTotalCount(){
long count = personRepository.getTotalCount();
System.out.println(count);
}
/**
* 测试10
*/
@Test
public void updatePersonEmail(){
personService.updatePersonEmail("123@ctg.com", 1);
}
//=======================测试jpa配置=======================================
@Test
public void testJpa(){
}
//========================测试数据连接========================================
/**
* 测试数据库连接
* @throws SQLException
*/
@Test
public void testDataSource() throws SQLException {
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println( dataSource.getConnection());
}
}