1. Spring Data环境搭建
Spring
Data提供了一套统一的基于Spring的数据访问模型,它可以轻松的实现数据库访问,包括各种关系型、非关系型数据库、Map-Reduce框架、云数据服务等。Spring Data 包含多个子项目: • Commons - 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化
• Hadoop - 基于 Spring 的 Hadoop 作业配置和一个 POJO 编程模型的 MapReduce 作业
• Key-Value - 集成了 Redis 和 Riak ,提供多个常用场景下的简单封装
• Document -集成文档数据库:CouchDB 和 MongoDB 并提供基本的配置映射和资料库支持
• Graph - 集成 Neo4j 提供强大的基于POJO 的编程模型
• Graph Roo AddOn - Roo support for Neo4j
• JDBC Extensions- 支持 Oracle RAD、高级队列和高级数据类型
• JPA - 简化创建 JPA 数据访问层和跨存储的持久层功能
• Mapping - 基于 Grails 的提供对象映射框架,支持不同的数据库
• Examples - 示例程序、文档和图数据库
• Guidance - 高级文档
1.1 创建工程
1.2 添加jar包
在pom.xml中加入如下配置:
<dependencies>
<!--Spring Data JPA依赖包-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.7.RELEASE</version>
</dependency>
<!--使用hibernate作为orm实现-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.10.Final</version>
</dependency>
<!--junit依赖包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--mysql依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
</dependencies>
1.3 编写数据库配置文件
在resources目录下创建db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8
jdbc.username=你的用户名
jdbc.password=你的密码
需要注意配置文件的编码要与工程的整体编码一致,建议都设置成utf-8。如果从其它地方复制文件,建议先将内容粘贴到记事本中去除编码格式,再粘贴到工程中。
1.4 编写Spring配置文件
在resources目录下创建spring-config.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:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:context="http://www.springframework.org/schema/context"
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.xsd">
<!--通过spring加载properties文件-->
<context:property-placeholder location="classpath:db.properties"
ignore-unresolvable="true"/>
<!--配置数据源,${属性名}是从配置文件中读取属性值-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--指定实现JPA的适配器-->
<bean id="hibernateJpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="MYSQL"/>
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
</bean>
<!--EntityManager与持久化上下文相关的,用于操作实体类的实例对象,对实体类对象进行增删改查-->
<!--EntityManagerFactory用于管理EntityManager,-->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
<property name="packagesToScan" value="pojo"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
<!-- Jpa 事务配置 -->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--jpa扫描包中使用Repository子类,并生成代理对象-->
<jpa:repositories base-package="dao"
entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"/>
</beans>
1.5 创建实体类
在pojo包下创建实体类Dept.java和Employee.java:
package pojo;
import javax.persistence.*;
import java.sql.Date;
@Entity
@Table(name = "EMPLOYEE")//表名
public class Employee {
//必须声明一个主键
@Id
@Column(name = "ID")
//主键生成策略
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "NAME")//列名
private String name;
@Column(name = "DEPT_ID")
private Integer deptId;
@Column(name = "JOB")
private String job;
@Column(name = "SALARY")
private Float salary;
@Column(name = "HIRE_DATE")
private Date hireDate;
//getter/setter方法略
}
package pojo;
import javax.persistence.*;
@Entity
//表名如果与类名一样,可以不写@Table
public class Dept {
@Id
@Column
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column//属性名与列名一样,可以不写@Column的name
private String name;
//getter/setter方法略
}
1.6 编写DAO层
在dao包下创建DeptRepository接口:
package dao;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import pojo.Dept;
/**
* XxxRepository相当于传统项目中的XxxDAO
* 它不需要定义任何方法,因为CrudRepository中有定义常用增删改查方法
* CrudRepository<要操作的实体类, 主键类型>
*/
public interface DeptRepository extends CrudRepository<Dept, Integer> {
}
Repository的作用相当于从前写的DAO接口,但是Repository接口类中暂时不必添加任何方法,因为父接口CrudRepository中已经定义了基本的增删改查方法。
1.7 测试
@Test
public void testSelectAll() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
/**
* 获取Repository对象,虽然配置文件中没有声明id=deptRepository的bean,
* 但是配置了<jpa:repositories>的base-package,不指定bean的id,
* 默认生成的bead的id是类名首字母小写
*/
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
//findAll()是全查,执行的是select * from 表
Iterable<Dept> depts = repository.findAll();
for (Dept dept : depts) {
System.out.println(dept.getName());
}
}
执行测试方法,可以看到控制台上输出SQL语句:
Hibernate:
select
dept0_.id as id1_0_,
dept0_.name as name2_0_
from
Dept dept0_代表环境配置成功。
2. 使用JPA操作数据库
SpringData的核心接口是Repository,Repository是一个标记接口(如Spring自动扫描的时候,扫描的都是Repository类型,其子类也可以被捕捉到),所有进行数据操作的接口都可以继承Repository并进行拓展。CrudRepository是Repository的子类,提供了常用的增删改查方法。除此之外还有JpaRepository、MongoRepository等,他们都继承了CrudRepository并进行了拓展。
2.1 CrudRepository
CrudRepository接口中定义了常用的增删改查方法,如:
save(S entity):添加/修改数据
findOne(ID primaryKey):根据主键查询
findAll():查询表中所有数据
count():查询表中有多少条记录 delete(T
entity):删除一条记录
exists(ID primaryKey):判断指定主键的数据是否存在
此外还有一些重载的方法,在此不做介绍。
使用CrudRepository操作数据库,需要创建一个接口并继承CrudRepository。语法如下:
public interface DeptRepository extends CrudRepository<Dept, Integer> { }
CrudRepository<类名,主键类型> 设定类名后,会根据实体类中的注解,找到与此类对应的表进行操作。
2.1.1 添加和修改数据
@Test
public void testSave(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
Dept dept=new Dept();//新创建一个对象,没有主键
dept.setName("jpa测试");
repository.save(dept);//通过save方法保存数据,执行insert
System.out.println(dept.getId());//添加数据后,会看到主键有值了
}
控制台输出语句: Hibernate:
insert
into
Dept
(name)
values
(?)
@Test
public void testSaveUpdate(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
Dept dept=new Dept();//新创建一个对象
dept.setId(32);//设置主键(数据库中有对应的数据)
dept.setName("jpa测试1");
repository.save(dept);//通过save方法保存数据,执行update
}
在给实体类设置了主键且数据库中有对应该主键的数据时,控制台输出如下语句: Hibernate:
select
dept0_.id as id1_0_0_,
dept0_.name as name2_0_0_
from
Dept dept0_
where
dept0_.id=? Hibernate:
update
Dept
set
name=?
where
id=?如果把id设置成一个数据库中不存在的值,那么第二条语句会是insert。
可见当主键有值的时候,会先进行查询,根据查询的结果判断是要insert还是update 使用save方法时,分如下几种情况:
- 实体类没有主键值:执行insert新增数据
- 实体类有主键,且主键值在数据库中存在,执行update修改数据
- 实体类有主键,但主键值在数据库中不存在,执行insert插入数据 save有两个重载的方法,除了传入一个对象以外,还可以传入集合,批量插入数据:
@Test
public void testSaveList(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
Dept dept1=new Dept();//新创建一个对象,没有主键
dept1.setName("jpa测试1");
Dept dept2=new Dept();//新创建一个对象,没有主键
dept2.setName("jpa测试2");
List<Dept> deptList=new ArrayList<>();//创建集合,将新对象放入集合中
deptList.add(dept1);
deptList.add(dept2);
repository.save(deptList);//传入集合,批量插入数据
}
执行测试方法,会看到控制台输出两条insert。
2.1.2 查询数据
CrudRepository中查询的方法有5个:
findOne(id):根据主键查询一个对象
findAll():查询表中所有数据
findAll(Iterator):传入一个主键的集合,使用in子句查询多条数据
exists(id):根据主键判断该条数据是否存在
count():查询数据库中数据的总条数
@Test
public void testFindOne(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
Dept dept=repository.findOne(30);//根据主键查询数据
System.out.println(dept.getId());
}
执行的语句如下: Hibernate:
select
dept0_.id as id1_0_0_,
dept0_.name as name2_0_0_
from
Dept dept0_
where
dept0_.id=?
@Test
public void testFindAll(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
List<Integer> ids=new ArrayList<>();//创建一个集合作为查询条件
ids.add(29);
ids.add(30);
ids.add(31);
//根据条件查询数据,会执行in子句
Iterable<Dept> depts = repository.findAll(ids);
for (Dept dept : depts) {
System.out.println(dept.getName());
}
}
执行的语句:
Hibernate:
select
dept0_.id as id1_0_,
dept0_.name as name2_0_
from
Dept dept0_
where
dept0_.id in (
? , ? , ?
)
@Test
public void testExists(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
//根据主键判断数据是否存在
boolean isExists=repository.exists(30);
System.out.println(isExists);
}
执行的语句: Hibernate:
select
count(*) as col_0_0_
from
Dept dept0_
where
dept0_.id=? 与findOne方法不同的是exists方法查询的是count。
@Test
public void testCount() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
long count=repository.count();//查询表中一共有多少条数据
System.out.println(count);
}
执行的语句: Hibernate:
select
count(*) as col_0_0_
from
Dept dept0_
2.1.3 删除数据
CrudRepository删除数据有四个方法:
delete(ID):根据主键删除
delete(T):传入对象删除
delete(Iterator):传入一个集合,数据库中与集合元素对应的数据
deleteAll():删除所有数据
@Test
public void testDelete() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
repository.delete(38);//根据主键删除数据
}
执行的SQL: Hibernate:
select
dept0_.id as id1_0_0_,
dept0_.name as name2_0_0_
from
Dept dept0_
where
dept0_.id=? Hibernate:
delete
from
Dept
where
id=?可以看到调用delete方法的时候先执行了查询,后执行删除。当查询出该主键对应的数据不存在时,会抛出如下异常:
org.springframework.dao.EmptyResultDataAccessException: No class pojo.Dept entity with id 38 exists!
@Test
public void testDeleteEntity() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
Dept dept = new Dept();//创建要删除的对象
dept.setId(37);
dept.setName("del");
repository.delete(dept);//删除数据
}
执行的SQL语句:
Hibernate:
select
dept0_.id as id1_0_0_,
dept0_.name as name2_0_0_
from
Dept dept0_
where
dept0_.id=? Hibernate:
delete
from
Dept
where
id=?
若所指定的id在数据库中不存在对应数据,则会执行如下语句:
Hibernate:
select
dept0_.id as id1_0_0_,
dept0_.name as name2_0_0_
from
Dept dept0_
where
dept0_.id=? Hibernate:
insert
into
Dept
(name)
values
(?) Hibernate:
delete
from
Dept
where
id=?
可以看到当指定的id不存在的时候,先执行了insert又执行了delete。如果Dept的属性设置不符合数据库约束,如不设置name,在insert的时候,name为空就会报错。
@Test
public void testDeleteIterator() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
List<Dept> deptList=new ArrayList<>();//创建集合
Dept dept1=new Dept();
dept1.setId(34);
Dept dept2=new Dept();
dept2.setId(35);
//将元素加入集合
deptList.add(dept1);
deptList.add(dept2);
repository.delete(deptList);//删除数据库中与集合对应的数据
}
执行的SQL语句: Hibernate:
select
dept0_.id as id1_0_0_,
dept0_.name as name2_0_0_
from
Dept dept0_
where
dept0_.id=? Hibernate:
select
dept0_.id as id1_0_0_,
dept0_.name as name2_0_0_
from
Dept dept0_
where
dept0_.id=? Hibernate:
delete
from
Dept
where
id=? Hibernate:
delete
from
Dept
where
id=?当delete方法传入的是集合的时候,也是先进行查询,再执行删除。元素的id在数据库中没有对应数据的时候,也是先执行insert再执行delete。
@Test
public void testDeleteAll() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
repository.deleteAll();//删除所有数据
}
执行的SQL: Hibernate:
select
dept0_.id as id1_0_,
dept0_.name as name2_0_
from
Dept dept0_ Hibernate:
delete
from
Dept
where
id=? Hibernate:
delete
from
Dept
where
id=?deleteAll()方法先查询所有数据,再根据主键一条一条的删。如果表中没有数据,就不执行delete语句了。
2.2 PagingAndSortingRepository
PagingAndSortingRepository是CrudRepository的一个子类,它增加了一些方法,主要实现分页查询和排序。
以Employee为例,实现对Employee表的查询,要先创建EmployeeRepository:
package dao;
import org.springframework.data.repository.PagingAndSortingRepository;
import pojo.Employee;
//继承PagingAndSortingRepository可以实现分页和排序查询
public interface EmployeeRepository
extends PagingAndSortingRepository<Employee, Integer> {
}
2.2.1 分页查询
创建测试类:
package test;
import dao.EmployeeRepository;
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 pojo.Employee;
import java.util.List;
public class EmployeeTest {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
EmployeeRepository repository =
(EmployeeRepository) ac.getBean("employeeRepository");
@Test
public void testPage1() {
//分页请求,当前请求第1页(索引从0开始),每页5条
PageRequest request = new PageRequest(0, 5);
Page<Employee> pages = repository.findAll(request);//分页查询
System.out.println("当前页:" + pages.getNumber() + 1);
System.out.println("是否有上一页:" + pages.hasPrevious());
System.out.println("是否有下一页:" + pages.hasNext());
System.out.println("是否是第一页:" + pages.isFirst());
System.out.println("是否是最后一页:" + pages.isLast());
System.out.println("记录总条数:" + pages.getTotalElements());
System.out.println("总页数:" + pages.getTotalPages());
List<Employee> empList = pages.getContent();//查询出的数据集合
for (Employee employee : empList) {
System.out.println(employee.getName());
}
}
}
可以看到控制台上输出的SQL:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.DEPT_ID as DEPT_ID2_1_,
employee0_.HIRE_DATE as HIRE_DAT3_1_,
employee0_.JOB as JOB4_1_,
employee0_.NAME as NAME5_1_,
employee0_.SALARY as SALARY6_1_
from
EMPLOYEE employee0_ limit ? Hibernate:
select
count(employee0_.ID) as col_0_0_
from
EMPLOYEE employee0_查询的时候分页用了limit,并且使用count查询了记录的总条数。注意获得查询的时候,页码索引是从0开始的。
2.2.2 排序
查询列表时需要排序,将排序条件封装到Sort中:
@Test
public void testSort() {
//创建排序条件,new Sort(排序的方向,排序的字段)
//按id的降序(DESC)排列,升序用ASC
Sort sort = new Sort(Sort.Direction.DESC, "id");
//查询所有数据,并按id降序排列
Iterable<Employee> employees = repository.findAll(sort);
for (Employee employee : employees) {
System.out.println(employee.getId() + ":" + employee.getName());
}
}
执行的SQL如下:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.DEPT_ID as DEPT_ID2_1_,
employee0_.HIRE_DATE as HIRE_DAT3_1_,
employee0_.JOB as JOB4_1_,
employee0_.NAME as NAME5_1_,
employee0_.SALARY as SALARY6_1_
from
EMPLOYEE employee0_
order by
employee0_.ID desc 可以看到SQL中包含了order by 子句。 分页与排序结合时,将Sort对象封装到PageRequest中:
@Test
public void testPagerSort() {
//按id降序排列
Sort sort = new Sort(Sort.Direction.DESC, "id");
//查询第2页(注意页码的索引从0开始,所以传1),每页5条
PageRequest request = new PageRequest(1, 5, sort);
Iterable<Employee> employees = repository.findAll(request);
for (Employee employee : employees) {
System.out.println(employee.getId() + ":" + employee.getName());
}
}
执行的SQL:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.DEPT_ID as DEPT_ID2_1_,
employee0_.HIRE_DATE as HIRE_DAT3_1_,
employee0_.JOB as JOB4_1_,
employee0_.NAME as NAME5_1_,
employee0_.SALARY as SALARY6_1_
from
EMPLOYEE employee0_
order by
employee0_.ID desc limit ?,
? Hibernate:
select
count(employee0_.ID) as col_0_0_
from
EMPLOYEE employee0_当有多个条件排序时,需要使用到Sort的Order类。
@Test
public void testPagerSortOrder() {
//按salary降序
Sort.Order salaryOrder = new Sort.Order(Sort.Direction.DESC, "salary");
//按id升序
Sort.Order idOrder = new Sort.Order(Sort.Direction.ASC, "id");
List<Sort.Order> orders = new ArrayList<>();//将排序条件添加到集合里
//注意order放入List的顺序,决定排序的结果
//先salary后id,代表先按salary排序,当salary相同时再按id排序
orders.add(salaryOrder);
orders.add(idOrder);
//多个条件排序封装
Sort sort = new Sort(orders);
PageRequest request = new PageRequest(0, 10, sort);
Iterable<Employee> employees = repository.findAll(request);
for (Employee employee : employees) {
System.out.println(employee.getId() + ":" + employee.getSalary());
}
}
执行的SQL语句:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.DEPT_ID as DEPT_ID2_1_,
employee0_.HIRE_DATE as HIRE_DAT3_1_,
employee0_.JOB as JOB4_1_,
employee0_.NAME as NAME5_1_,
employee0_.SALARY as SALARY6_1_
from
EMPLOYEE employee0_
order by
employee0_.SALARY desc,
employee0_.ID asc limit ? Hibernate:
select
count(employee0_.ID) as col_0_0_
from
EMPLOYEE employee0_一定要注意Order放入List的先后顺序,决定了排序的首要条件和次要条件。 Sort提供了多种构造方法:
- Sort(Order… orders):可以传入多个Order对象
- Sort(List orders):将多个Order封装到List中
- Sort(String… properties):传入多个排序的属性,按照默认的ASC进行排序
- Sort(Direction direction, String…
properties):根据direction,对后面的多个属性进行排序,多个属性都按照同一个direction排序- Sort(Direction direction, List properties):将要排序的属性封装到List中,这些属性都按照direction排序 此外还有一个and(Sort sort)方法,可以在当前排序的Sort上追加排序条件。
2.3 Repository
Spring Data JPA支持自定义查询机制,它的查询机制中,方法使用find…By, read…By, query…By,
count…By, get…By等前缀定义查询方法,方法名中的第一个By代表查询条件的开始。自定义查询方法的接口要继承Repository接口。
package dao;
import org.springframework.data.repository.Repository;
import pojo.Employee;
/**
* 自定义查询条件
*/
public interface EmpRepository extends Repository<Employee, Integer> {
}
2.3.1 查询方法示例
在接口类中写入固定的前缀时,IDEA会提示出一些常用的查询条件。它会自动将实体类中所有的属性可能生成的查询条件列出来,如图:
为了看到参数绑定情况,需要在pom.xml中加入日志相关jar包:
<!--日志包-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
在resources中创建log4j.properties文件:
# 日志级别设置成WARN可以过滤掉spring中大部分日志
log4j.rootLogger=WARN, stdout,
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
#为了显示参数
log4j.logger.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
全局修饰: Distinct, Top, First
关键词: Null, NotNull, Like, NotLike, Containing, Contains,In, NotIn,
IgnoreCase, Between, Equals, LessThan, GreaterThan, After, Before…
注:关键词前可以加Is。加Not代表相反的条件。
排序方向: Asc, Desc
连接词: And, Or
Keyword Sample
And findByIdAndName(paramId, paramName)
按照id和name两个字段查询,关系是and
… where ID = ? and NAME = ?
Or findByIdOrName(paramId, paramName)
按照id和name两个字段查询,关系是and
… where ID = ? or NAME = ?
Is,Equals findById(param),findByIdIs(param),
findByIdEquals(param)这三个方法是等价的
… where ID = ?
Between findByIdBetween(paramMin, paramMax)
id值在paramMin和paramMax之间
… where ID between ? and ?
LessThan,
Before findByAgeLessThan(param)
小于
… where AGE < ?
LessThanEqual findByAgeLessThanEqual(param)
小于等于
… where AGE <= ?
GreaterThan,
After findByAgeGreaterThan(param)
大于
… where AGE > ?
GreaterThanEqual findByAgeGreaterThanEqual(param)
大于等于
… where AGE >= ?
Null findByAgeNull()
为空
… where AGE is null
NotNull findByAgeNotNull()
不为空
… where AGE not null
Like findByNameLike(name)
like 但是不会自动拼接%%
… where NAME like ?
NotLike findByNameNotLike(param)
not like 但是不会自动拼接%%
… where NAME not like ?
StartingWith findByNameStartingWith(param)
以…开头的,会在参数值后自动拼接%
… where NAME like ?
EndingWith findByNameEndingWith(param)
以…结尾的,会在参数值前自动拼接%
… where NAME like ?
Containing,
Contains findByNameContaining(param)
模糊查询,包含。会自动在参数值前后拼接%%
… where NAME like ?
OrderBy findByAgeOrderByIdDesc(param)
根据Age查询并按照id倒叙排序
… where AGE = ? order by ID desc
Not findByNameNot(param)
不等于
… where NAME <> ?
In findByAgeIn(Collection ages)
in子查询,参数是一个集合
… where AGE in (?,?,?..)
NotIn findByAgeNotIn(Collection age)
not in作用与in相反
… where AGE not in (?,?,?..)
True findByActiveTrue()
字段值为true
… where ACTIVE = true
False findByActiveFalse()
字段值为false
… where ACTIVE = false
IgnoreCase findByNameIgnoreCase(param)
忽略大小写,完全匹配=
… where UPPER(NAME) = UPPER(?)
First,Top findFirst5ById(param),findTop5ById(param)
查询前n条数据。
Distinct findDistinctByName(param)
排重
所有字段都不重复的可以查出来
2.3.2 使用@Query自定义查询
如果Spring Data JPA提供的方法不能满足我们的需求,可以使用@Query在方法上添加自定义的查询语句:
//自定义查询
@Query("select count(distinct E.name) from Employee E")
Integer countDistinctName();
按位置绑定参数 //按位置传参数。注意参数的?占位符后面要写数字,代表的是方法的()中参数的位置。
@Query("select count(distinct E.name) from Employee E where E.salary>?2 and E.deptId=?1 ")
Integer countDistinctNameByDeptIdAndSalary(Integer deptId,Float salary);
按名称绑定参数: //按名称传参数,参数用:参数名占位。@param(“查询语句中的参数名”)。
@Query("select count(distinct E.name) from Employee E where E.deptId=:dept ")
Integer countDistinctNameByDeptId(@Param("dept") Integer deptId);
2.3.3 原生SQL
如果想执行原生SQL,可以在@Query中设置nativeQuery属性:
//原生sql。value是sql语句,nativeQuery=true表示原生sql
@Query(value = "select NAME from EMPLOYEE", nativeQuery = true)
List<String> findAllName();
2.3.4 分页和排序
与2.2节中的语法一样,分页和排序使用PagerRequest和Sort //排序传Sort
List<Employee> findByDeptIdEquals(Integer deptId, Sort sort);
//分页方法定义的时候,分页参数类型要是Pageable,调用的时候传PageRequest
Page<Employee> findByDeptIdEquals(Integer deptId, Pageable sort);
2.3.5 自定义增删改
自定义增删改使用@Modifying
//增删改用@Modifying,
//需要@Transactional否则会报错TransactionRequiredException
@Modifying
@Transactional
@Query(“update Employee E set E.name=?1 where E.id=?2”)
void updateName(String name, Integer id);
2.4 Example
Example可以动态组建查询条件。能自由组合各个字段的查询条件。
使用限制:
不支持嵌套分组
仅支持对字符串的模糊查询和正则匹配以及其它类型属性的精确匹配。
package dao;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import pojo.Employee;
/**
* 使用Example查询,要继承QueryByExampleExecutor
*/
public interface EmpExampleRepository
extends Repository<Employee, Integer>, QueryByExampleExecutor<Employee> {
}
测试类:
package test;
import dao.EmpExampleRepository;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.domain.*;
import pojo.Employee;
public class EmpExampleTest {
private ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
private EmpExampleRepository repository =
(EmpExampleRepository) ac.getBean("empExampleRepository");
@Test
public void testSelectOne() {
//创建封装查询条件的对象
Employee criteria = new Employee();
criteria.setId(5);//默认的查询条件都是等于
Example<Employee> example = Example.of(criteria);
Employee employee = repository.findOne(example);
System.out.println(employee.getName());
}
/**
* 测试多个条件查询
*/
@Test
public void testExampleMatcher() {
Employee criteria = new Employee();
criteria.setJob("开发");
criteria.setName("张");
//查询姓张的,工作是“开发”的员工
//where (NAME like ?) and JOB=?
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("name", new ExampleMatcher.GenericPropertyMatcher().startsWith())
.withMatcher("job", new ExampleMatcher.GenericPropertyMatcher().exact());
Example<Employee> example = Example.of(criteria, matcher);
Iterable<Employee> employees = repository.findAll(example);
for (Employee employee : employees) {
System.out.println(employee.getName());
}
}
/**
* 测试分页和排序
*/
@Test
public void testPager() {
Employee criteria = new Employee();
criteria.setDeptId(7);//根据部门id查询
Example<Employee> example = Example.of(criteria);
//按id倒叙排序
Sort sort = new Sort(Sort.Direction.DESC, "id");
//查询第1页,每页条
PageRequest request = new PageRequest(0, 5, sort);
Page<Employee> employees = repository.findAll(example, request);
for (Employee employee : employees.getContent()) {
System.out.println(employee.getName());
}
}
}
3. Spring Data JPA对事务的支持
默认的JPA操作数据的时候是有事务的(接口中默认的save方法和delete方法)。自定义的修改和删除需要使用@Transactional注解开启事务。见2.3.5示例
4. 动态SQL
4.1 Criteria
使用Criteria需要建立查询的元数据类,即封装查询条件字段的类
package pojo;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
@StaticMetamodel(Employee.class)
public class Employee_ {
public static volatile SingularAttribute<Employee, String> name;
public static volatile SingularAttribute<Employee, Float> salary;
}
测试代码:
@Test
public void testCritria() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
EntityManagerFactory factory =
(EntityManagerFactory) ac.getBean("entityManagerFactory");
EntityManager em = factory.createEntityManager();
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery =
criteriaBuilder.createQuery(Employee.class);
//封装查询条件
Root<Employee> emp = criteriaQuery.from(Employee.class);
List<Predicate> list = new ArrayList<>();
list.add(criteriaBuilder.like(emp.get(Employee_.name), "%张%"));
list.add(criteriaBuilder.ge(emp.get(Employee_.salary), 300));
//将查询条件加入到where中
criteriaQuery.where(list.toArray(new Predicate[list.size()]));
//执行查询
List<Employee> emps = em.createQuery(criteriaQuery).getResultList();
for (Employee employee : emps) {
System.out.println(employee.getName() + ":" + employee.getSalary());
}
}
4.2 Specification
创建接口:
package dao;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.Repository;
import pojo.Employee;
public interface EmpExcutorRespository extends Repository<Employee, Integer>,
JpaSpecificationExecutor<Employee> {
}
测试类:
@Test
public void testSpecification() {
//创建查询条件
Specification<Employee> specification = new Specification<Employee>() {
@Override
public Predicate toPredicate(Root<Employee> root,
CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate predicate = criteriaBuilder.conjunction();
predicate.getExpressions()
.add(criteriaBuilder.like(root.<String>get("name"), "%张%"));
predicate.getExpressions()
.add(criteriaBuilder.ge(root.<Float>get("salary"), 3000));
return predicate;
}
};
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
EmpExcutorRespository excutorRespository =
(EmpExcutorRespository) ac.getBean("empExcutorRespository");
//执行查询
List<Employee> emps = excutorRespository.findAll(specification);
for (Employee employee : emps) {
System.out.println(employee.getName() + ":" + employee.getSalary());
}
}
生成的语句:
Hibernate:
select
employee0_.ID as ID1_2_,
employee0_.DEPT_ID as DEPT_ID2_2_,
employee0_.HIRE_DATE as HIRE_DAT3_2_,
employee0_.JOB as JOB4_2_,
employee0_.NAME as NAME5_2_,
employee0_.SALARY as SALARY6_2_
from
EMPLOYEE employee0_
where
(
employee0_.NAME like ?
)
and employee0_.SALARY>=3000.0
5. 关联关系
5.1 One-To-One
每个员工都有属于自己的档案:
EMP_FILE表
EMP_FILE表的主键EMP_ID与EMPLOYEE表的ID保持一致,即两张表是按主键关联的一对一 新建员工档案类:
package pojo;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import javax.persistence.*;
@Entity
@Table(name = "EMP_FILE")
public class EmpFile {
@Id
@Column(name = "EMP_ID")
//这里的主键是来源于EMPLOYEE表,生成策略是foreign
//foreign生成策略中需要一个property属性指明主键来源于那个属性
@GenericGenerator(name = "pkGenerator", strategy = "foreign",
parameters = {@Parameter(name = "property", value = "emp")})
@GeneratedValue(generator = "pkGenerator")
private Integer empId;
@Column(name = "EMP_RECORD")
private String empInfo;
//一对一,在Employee中对应的属性是empFile
@OneToOne(mappedBy = "empFile")
private Employee emp;
//getter/setter方法略
}
修改Employee类,添加EmpFile属性:
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn//主键关联的类
private EmpFile empFile;
//getter/setter方法略
测试代码:
@Test
public void testOneToOne(){
Employee emp=new Employee();
emp.setName("one_primary");
emp.setDeptId(1);
EmpFile file=new EmpFile();
file.setEmpInfo("info");
emp.setEmpFile(file);
file.setEmp(emp);
repository.save(emp);
}
可以看到控制台上执行两条语句:
Hibernate:
insert
into
EMPLOYEE
(DEPT_ID, HIRE_DATE, JOB, NAME, SALARY)
values
(?, ?, ?, ?, ?)
Hibernate:
insert
into
EMP_FILE
(EMP_RECORD, EMP_ID)
values
(?, ?)
5.2 One-To-Many
一个部门有多个员工: 在Dept类中添加员工集合,注意要加FtechType.EAGER,否则关联查询的时候懒加载失败:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "dept",fetch = FetchType.EAGER)
private List<Employee> emps;
//getter/setter方法略
在Employee中添加部门属性:
@ManyToOne
@JoinColumn(name = "dept_id")
private Dept dept;
//getter/setter方法略
测试代码:
@Test
public void testOneToMany(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("classpath:spring-config.xml");
DeptRepository repository = (DeptRepository) ac.getBean("deptRepository");
Dept dept=repository.findOne(40);
System.out.println(dept.getName());
for (Employee employee : dept.getEmps()) {
System.out.println(employee.getName());
}
}