SpringData入门

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包

image
image

配置配置文件

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支持的关键字

==直接在接口中定义查询方法,如果是符合规范的,可以不用写实现==

image

image

测试代码

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 查询相关的方法

image

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导航图
image

自定义 Repository 方法

我们通过继承PagingAndSortingRepository接口和继承JpaSpecificationExecutor能够实现大部分的增删改查以及分页查询(带条件)

当不满足我们的时候我们还可以通过@Modifying跟@Query注解来手写JPQL语句(insert不行)

当还不满足我们的时候我们可以自定义Repository方法

  • ==为某一个 Repository 上添加自定义方法(重点)==
  • 为所有的 Repository 都添加自实现的方法

步骤(某个Repository)
- 定义一个接口: 声明要添加的, 并自实现的方法
- 提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法
- 声明 Repository 接口, 并继承 1) 声明的接口
- 使用.
- 注意: 默认情况下, Spring Data 会在 base-package 中查找 “接口名Impl” 作为实现类. 也可以通过 repository-impl-postfix 声明后缀.

image

为某一个 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();
}

猜你喜欢

转载自blog.csdn.net/qq_36974281/article/details/81367808
今日推荐