前言:记录一下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 查询相关的方法
下面主要记录一下PagingAndSortingRepository
与JpaSpecificationExecutor
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)
个人比较偏向后面两种写法,感觉封装查询对象步骤太过繁琐。