SpringData JPA的介绍以及使用

前言:记录一下SpringData-JPA的学习过程…

1.什么是SpringData:

SpringData是目的在于简化数据库的访问,减轻持久层开发的压力,属于spring的一个子项目。支持关系型数据库和NoSQL ,主要目标是使数据库的访问变得方便快捷。面对不同的数据库,springdata也便提出了对应的解决方案,自然也有了SpringData -XXX.

  • 如所支持 NoSQL 存储:SpringData-.MongoDB ;SpringData-Redis
  • SpringData 项目所支持的关系数据存储技术:SpringData -JDBC;SpringData -JPA
2.SpringData-JPA

它就是在jap规范上,spring提出的一套持久层解决方案,开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA


SpringData-JPA入门环境的搭建。

(1)导入相关依赖。

  <!--导入相关依赖-->
    <dependencies>
    	<!--spring主要的包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
		<!--spring事务控制的包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

        <!--spring-orm整合包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.3.14.RELEASE</version>
        </dependency>


        <!--导入相关依赖-->
        <!--引入hibernate依赖-->
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.3.6.Final</version>
        </dependency>
        <!--引入jpa依赖-->
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.0.Final</version>
        </dependency>

        <!--引入hibernate-jpa-->
        <!-- https://mvnrepository.com/artifact/org.hibernate.javax.persistence/hibernate-jpa-2.1-api -->
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.1-api</artifactId>
            <version>1.0.0.Final</version>
        </dependency>
        <!--数据库驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <!--测试相关包-->
        <!--测试包-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--导入springdata的包-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.6.0.RELEASE</version>
        </dependency>

        <!--导入slf4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

(2)编写配置文件ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-4.2.xsd
 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">

    <context:component-scan base-package="com.kuke"/>
    <!--配置数据源-->
    <bean id="dataSource"
          class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///jpa"></property>
        <!-- 配置其他属性 -->
    </bean>

    <!-- 配置 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.kuke.entity"></property>
        <!-- 配置 JPA 的基本属性. 例如 JPA 实现产品的属性 这里使用的hibernate实现产品,即:与配置hibernate相同-->
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

    <!-- 配置 JPA 使用的事务管理器 -->
    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"></property>
    </bean>

    <!-- 配置支持基于注解是事务配置 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

(3)创建实体类Student

package com.kuke.entity;


import javax.persistence.*;
import java.util.Date;

/**
 * @author hao
 * @create 2019-05-22 ${TIM}
 */
@Table(name = "jpa_spring_student")
@Entity
public class Student {

    private Integer id;
    private String  name;
    private Integer age;
    private String email;
    private Date birth;



    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    public Integer getId() {
        return id;
    }


    @Temporal( TemporalType.DATE)
    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "username")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", birth=" + birth ;
    }
}

(4)定义声明Repository接口
进入Repository源码当中看发现这个接口只有这点信息public interface Repository<T, ID extends Serializable> {},可以知道这是个标记接口,其中T代表这个接口作用的实体类,ID是这个实体类的主键。

public interface StudentRepository extends Repository<Student,Integer>{
	  //根据Name来获得Student,
     Student getByName(String name);
}

//在接口当中声明一个方法getByName,好了现在就可以开始测试了,是不是觉得很奇怪,都没有写sql语句,怎么能够查询,不要惊讶,这就是springdata-jpa的魅力所在,在后面会记录原因。
(5)开始测试:

public class demoTest {
    private ClassPathXmlApplicationContext context = null;
    private    StudentRepository studentRepository = null;
	{
	  context = new ClassPathXmlApplicationContext("classpath:ApplicationContext.xml");
      studentRepository = context.getBean(StudentRepository.class);
		}
		
	 @Test
    public void test2(){
        System.out.println(studentRepository);
        Student student = studentRepository.getByName("ee");
        System.out.println(student);
    }
}

看控制台:
在这里插入图片描述
打印了sql语句并且,也查询到了结果。


1.Repository<T, ID extends Serializable> 是一个空接口,也就是一个标记接口。
2.当某个类继承了这个接口之后,就会被ioc容器识别成repositiry bean ,加入到ioc容器当中,在接口的定义的方法必须符合一定规范。
按照 Spring Data 的规范,查询方法以 find | read | get 开头, 涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。
如我们上述方法声明:getByName 通过Student的name属性查找。下面就是常用的关键字查找
在这里插入图片描述
在这里插入图片描述
根据上面的规范表 就可以向下面这个例子样,根据sql语句 写出对应方法查询。

  • findByNameContainingAndAgeIsLessThan(String name,Integer age) 查询 where age like ‘%?%’ and age <?,

只要是能用方法定义的,我们都需要去写操作数据库的具体方法,全部交给了框架去处理,这样就大大减轻了开发者的压力,但是当我们需要定义更复杂的方法,比如子查询,嵌套查询等,这种规范关键字查询已经不能满足我们的需求了,需要自定义查询,这是时候,就可以使用@Query注解来帮助我们。

@Query注解

如:当我们要查询年龄最大的学生。
面对这中需求,关键字查询就满足不了我们的需求,我们就要使用@Query注解,通过JPQL语句,来完成复杂的查询:
在接口中添加一个方法

public interface StudentRepository extends Repository<Student,Integer>{
	  //根据Name来获得Student,
     Student getByName(String name);
	
	 @Query("SELECT s FROM  Student  s  where s.age=( select MAX (S2.age) FROM  Student  S2)")
    Student findMaxId();
}

这样就能查询啦。
当我们使用@Query注解 入参数该怎么传入,之前有过记录@Query注解条件查询,传入参数

那如果我们需要原生的sql查询改怎么实现呢?也是通过@Query注解,添加属性nativeQuery = true,默认是false,这样就开启了原生sql,然后再@Query中就可以书写原生sql语句,下面就是通过原生sql查询总的记录数。

@Query(value = "SELECT  COUNT(*)  from jsp_spring_student",nativeQuery = true)
Long findCount();

上面一直提到的都是查询操作,那么删除 和修改,springdata-jpa是否支持呢?答案是肯定的。这里就要必须得借助一个注解@Modifying

@Modifying 用来声明这是一个删除或者修改的操作。

除此之外还不够,因为Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务,所有我们需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。 通过@Transactional注解的方式比较简便。
下面记录一下更新操作:

	 @Modifying
    @Query(value = "update jsp_spring_student s set  s.email = :email where s.id =:id",nativeQuery = true)
    void updateBynativeQuery(@Param("email") String email ,@Param("id") Integer id);

然后需要在service层调用该方法,开启事务。

    @Transactional //开启事务
    public void updateStudentAgeById( Integer age,  Integer id){
        studentRepository.updateStudentAgeById(age,id);
    }
Repository 的子接口

打开Repository 的继承图我们可以发现它有多个子类,每个子类都有他们功能。
-在这里插入图片描述
础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:

  • Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类
  • CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
  • PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
  • JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
  • 自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。
  • JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

下面主要记录一下PagingAndSortingRepositoryJpaSpecificationExecutor
1.PagingAndSortingRepository看名字就知道,这个一个有关分页的接口。
在这里插入图片描述
他只有两个方法,主要是findAll(Pageable)
(1)定义继承接口

public interface StudentPagingAndSortingRepository  extends PagingAndSortingRepository<Student,Integer> {
}

(2)编写测试类

  @Test
    public void testPageQuery(){
        StudentPagingAndSortingRepository bean = context.getBean(StudentPagingAndSortingRepository.class);
        //page当前第几页是从0开始计算  size每页多少条记录
        PageRequest pageable = new PageRequest(2,3);
        Page<Student> studentPage = bean.findAll(pageable);

        System.out.println("==========输出分页信息============");
        System.out.println("总记录数:"+studentPage.getTotalElements());
        System.out.println("总共多少页:"+studentPage.getTotalPages());
        System.out.println("当前页多少条:"+studentPage.getSize());
        System.out.println("当前页信息:"+studentPage.getContent());
    }

当我们封装好pageable之后,直接交给findAll(pageable),之后的查询分页操作都会由框架自己完成。
在这里插入图片描述
可以看到完成了分页操作,是不是比以前那种传统的分页方式,简便很多。

但是如果们需要根据查询条件来进行分页的话,上面这种情况就无法满足我们的需求,这个时候我们就需要借助JpaSpecificationExecutor来完成其中有这么几个方法。这里记录Page<T> findAll(Specification<T> spec, Pageable pageable);
在这里插入图片描述
代码如下:
(1)声明接口

public interface StudentJpaSpecificationExecutor extends JpaSpecificationExecutor<Student>,JpaRepository<Student,Integer>{
}

(2)测试方法

   //自定义分页查询条件	Page<T> findAll(Specification<T> spec, Pageable pageable);
    @Test
    public void testJpaSpecificationExecutor(){
        StudentJpaSpecificationExecutor studentJpaSpecificationExecutor = context.getBean(StudentJpaSpecificationExecutor.class);

        //第3页 每页4条记录, 在查询条件的约束下进行分页。
        PageRequest pageable = new PageRequest(2,4);
        Specification<Student> specification = new Specification<Student>() {
            /**
             * @param root 代表查询的实体类
             * @param cb   用于创建criteria的工厂,当然也可以用来获得Predicate对象,封装了查询信息。
             * @return a {@link Predicate}, must not be {@literal null}.
             */
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                Path path = root.get("id");
                //
                Predicate gt = cb.gt(path, 4);
                return gt;
            }
        };
        //第四页开始 每页3条记录
        PageRequest pageRequest = new PageRequest(3,3);
        Page<Student> studentPage = studentJpaSpecificationExecutor.findAll(specification, pageRequest);
        System.out.println("==========输出分页信息============");
        System.out.println("总记录数:"+studentPage.getTotalElements());
        System.out.println("总共多少页:"+studentPage.getTotalPages());
        System.out.println("当前页多少条:"+studentPage.getSize());
        System.out.println("当前页信息:"+studentPage.getContent());

    }

其中Specification是个接口,上面采用匿名内部类。执行控制台结果如下:
在这里插入图片描述
可以看到,cb.gt(path, 4); 就是设置student0_.id>4这个条件。

还有一种分页的写法就是,不通过对象设置查询条件,可以在接口上定义分页查询方法,然后传入pageable
效果与封装Specification查询对象效果相同,

Page<Student> findByIdGraeterThen(Pageable pageable)

当然也可以自己书写原生的sql语句 然后使用pageable

//,nativeQuery = true 开启原生sql
@Query(value="select * from student where student.id > ?",,nativeQuery = true
Page<Student> findBy(String id,Pageable pageable)

个人比较偏向后面两种写法,感觉封装查询对象步骤太过繁琐。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值