JPA Spring Data : 致力于减少数据访问层 (DAO) 的开发量. ==开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!==
框架怎么可能代替开发者实现业务逻辑呢?
Spring Data JPA 做的便是==规范方法==的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象
Spring配置SpringData
声明持久层的接口(继承Repository)
在接口中声明方法
<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>
<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>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">bean>
property>
<property name="packagesToScan" value="com.xiaoming.springdata">property>
<property name="jpaProperties">
<props>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategyprop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialectprop>
<prop key="hibernate.show_sql">trueprop>
<prop key="hibernate.format_sql">trueprop>
<prop key="hibernate.hbm2ddl.auto">updateprop>
props>
property>
bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory">property>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<jpa:repositories base-package="com.xiaoming.springdata" entity-manager-factory-ref="entityManagerFactory">jpa:repositories>
beans>
<jpa:repositories base-package="com.xiaoming.springdata" entity-manager-factory-ref="entityManagerFactory">jpa:repositories>
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
@Table(name = "person")
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private Integer age;
.......
}
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());
}
}
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 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:
class User{
private String firstName;
private String lastName;
......
}
使用And条件连接时,应这样写:==findByLastNameAndFirstName(String lastName,String firstName)==;
==条件的属性名称与个数要与参数的位置与个数一一对应==
==直接在接口中定义查询方法,如果是符合规范的,可以不用写实现==
public interface PersonRepository extends Repository<Person,Long> {
//根据用户名获取person对象
Person getByUsername(String username);
//WHERE username LIKE ?% AND id < ?
//这个方法like后面没有 自动添加 % 传的参数中需要手动添加
List getByUsernameLikeAndAgeGreaterThan(String username,Integer age);
//是否包含该字符串(jpql语句不会写emmm)
List getByUsernameContaining(String username);
//WHERE username IN (?, ?, ?) OR age < ?
List getByUsernameInOrAgeLessThanEqual(List name,Integer age);
}
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 personList = personRepsotory.getByUsernameLikeAndAgeGreaterThan("%a%", 45);
System.out.println(personList);
}
@Test
public void testKeyWords2(){
List personList = personRepsotory.getByUsernameContaining("a");
System.out.println(personList.size());
System.out.println(personList);
}
@Test
public void testKeyWords3(){
List personList = personRepsotory.getByUsernameInOrAgeLessThanEqual(Arrays.asList("aa","bb","cc"),47);
System.out.println(personList.size());
System.out.println(personList);
}
}
简单条件查询进行级联查询时出现的问题
- 简单条件查询是是支持级联查询的,默认使用左外连接
- 但是如果要查询的级联的属性的名称跟本类中的属性名称重复,那么优先使用本类的属性
- 如果想要使用级联属性那么属性之间应该用_进行连接
List getByAddress_IdGreaterThan(Integer id);
这种查询可以声明在 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 testQueryAnnotationParams1(String lastName, String email);
//SpringData 允许在占位符上添加 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List testQueryAnnotationLikeParam(String lastName, String email);
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
//查询 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 testQueryAnnotationParams1(String lastName, String email);
//为 @Query 注解传递参数的方式1: 命名参数的方式.
@Query(" SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List 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 testQueryAnnotationLikeParam(String lastName, String email);
//SpringData 允许在占位符上添加 %%.
@Query(" SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")
List testQueryAnnotationLikeParam2(@Param(" email") String email, @Param("lastName") String lastName);
//设置 nativeQuery=true 即可以使用原生的 SQL 查询
@Query(value="SELECT count(id) FROM jpa_persons", nativeQuery=true)
long getTotalCount();
@Test
public void testQueryAnnotationParams1(){
List persons = personRepsotory.testQueryAnnotationParams1("AA", "[email protected]");
System.out.println(persons);
}
@Test
public void testQueryAnnotationParams2(){
List persons = personRepsotory.testQueryAnnotationParams2("[email protected]", "AA");
System.out.println(persons);
}
@Test
public void testQueryAnnotationLikeParam(){
// List persons = personRepsotory.testQueryAnnotationLikeParam("%A%", "%bb%");
// System.out.println(persons.size());
// List persons = personRepsotory.testQueryAnnotationLikeParam("A", "bb");
// System.out.println(persons.size());
List persons = personRepsotory.testQueryAnnotationLikeParam2("bb", "A");
System.out.println(persons.size());
}
@Test
public void testNativeQuery(){
long count = personRepsotory.getTotalCount();
System.out.println(count);
}
因为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 接口提供了最基本的对实体类的添删改查操作
- T save(T entity);//保存单个实体
- Iterable save(Iterable
@Test
public void testCrudRepository(){
List 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);
该接口提供了分页与排序功能
- Iterable findAll(Sort sort); //排序
- Page findAll(Pageable pageable); //分页查询(含排序功能)
@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 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());
}
//根据用户名获取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 persons = personRepsotory.getByUsernameContaining("a", pageRequest);
System.out.println(persons.size());
System.out.println(persons);
}
该接口提供了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);
}
不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象
前面讲到的Repository体系中分页查询,添加条件不方便,这个外来的接口极大地方便了我们带条件的分页查询
我们可以写一个通用的带条件的分页查询
/**
* 目标: 实现带查询条件的分页. age <45 并按照年龄排序 的条件
*
* 调用 JpaSpecificationExecutor 的 Page findAll(Specification spec, Pageable pageable);
* Specification: 封装了 JPA Criteria 查询的查询条件
* Pageable: 封装了请求分页的信息: 例如 pageNo, pageSize, Sort
*/
@Test
public void testJpaSpecificationExecutor() {
//findAll(Specification 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
//通常使用 Specification 的匿名内部类创建对象
Specification specification = new Specification() {
/**
*
* @param root(掌握):代表查询的实体类(这里指的是Person)
* @param criteriaQuery(了解):可以从中能得到实体类;可以添加查询条件;
* 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
* @param criteriaBuilder(掌握):创建Criteria相关对象的工厂,可以从中获得目标对象Predicate
* @return Predicate(掌握): 代表一个查询条件_
*/
@Override
public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//先通过root导航,导航到相关属性(条件)
Path age = root.get("age");
//将导航到的属性添加到条件中
Predicate predicate = criteriaBuilder.lt(age, 45);
return predicate;
}
};
Page 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());
}
我们通过继承PagingAndSortingRepository接口和继承JpaSpecificationExecutor能够实现大部分的增删改查以及分页查询(带条件)
当不满足我们的时候我们还可以通过@Modifying跟@Query注解来手写JPQL语句(insert不行)
当还不满足我们的时候我们可以自定义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();
}