Spring Data JPA

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方法时,分如下几种情况:

  1. 实体类没有主键值:执行insert新增数据
  2. 实体类有主键,且主键值在数据库中存在,执行update修改数据
  3. 实体类有主键,但主键值在数据库中不存在,执行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提供了多种构造方法:

  1. Sort(Order… orders):可以传入多个Order对象
  2. Sort(List orders):将多个Order封装到List中
  3. Sort(String… properties):传入多个排序的属性,按照默认的ASC进行排序
  4. Sort(Direction direction, String…
    properties):根据direction,对后面的多个属性进行排序,多个属性都按照同一个direction排序
  5. 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());
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/4741.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ExtScreen,为智能电视和VR设备打造的快应用引擎

和手机相比&#xff0c;智能电视端的生态一直都不怎么行&#xff0c;具体来讲有以下这几个问题&#xff1a; 电视芯片运算能力差&#xff0c;配置普遍不如手机&#xff1b;电视交互基于遥控器&#xff0c;完全不同于触摸屏操作的手机&#xff1b;电视的生态比较封闭&#xff0…

【JavaWeb】Cookie和Session

目录 Cookie Cookie定义 Cookie数据的来源 Cookie数据的存储 Cookie数据的使用 使用Cookie原因 Session Session定义 如何存储数据 Cookie和Session的区别 使用Cookie和Session简单实现登录页面 Cookie Cookie定义 Cookie是浏览器提供持久化存储数据的机制。 Cook…

这么方便吗?用ChatGPT生成Excel(详解步骤)

文章目录前言使用过 ChatGPT 的人都知道&#xff0c;提示占据非常重要的位置。而 Word&#xff0c;Excel、PPT 这办公三大件中&#xff0c;当属 Excel 最难搞&#xff0c;想要熟练掌握它&#xff0c;需要记住很多公式。但是使用提示就简单多了&#xff0c;和 ChatGPT 聊聊天就能…

【vue3】基础概念的介绍

⭐【前言】 首先&#xff0c;恭喜你打开了一个系统化的学习专栏&#xff0c;在这个vue专栏中&#xff0c;大家可以根据博主发布文章的时间顺序进行一个学习。博主vue专栏指南在这&#xff1a;vue专栏的学习指南 &#x1f973;博主&#xff1a;初映CY的前说(前端领域) &#x1f…

【音视频】zlmediakit总结一

推拉流理论 推流&#xff1a;将直播的内容推送至服务器的过程。 拉流&#xff1a;指服务器已有直播内容&#xff0c;用指定地址进行拉取的过程。 拉流&#xff0c;即是指服务器里面有流媒体视频文件&#xff1b; 但zlmediakit里也有个广义的拉流概念如下。对于用户而言&#xf…

面试官灵魂拷问[二]:SQL 语句中 where 条件后写上 1=1 是什么意思?

面试官灵魂拷问系列又来更新啦! “SQL 语句中 where 条件后写上 11 是什么意思&#xff1f;” 这玩意就跟很多新语言支持尾部逗号的原理一样的。 比如 Kotlin 支持数组写成 [1, 2, 3, 4, ] &#xff0c;注意4后边那个逗号&#xff0c;为什么呢&#xff1f;因为当你增加一个项…

医院LIS系统源码,云LIS系统源码,独立实验室LIS源码

实验室云LIS系统源码 LIS系统源码 LIS源码 基于B/S架构的实验室管理系统云LIS&#xff0c;整个系统的运行基于WEB层面&#xff0c;只需要在对应的工作台安装一个浏览器软件有外网即可访问。 私信了解更多源码内容&#xff01; 技术架构&#xff1a;Asp.NET CORE 3.1 MVC SQ…

MySQL表设计思路(一对多、多对多...)

要开始单独负责需求了&#xff0c;捋一捋表设计的思路。 文章目录一、MySQL中的数据类型二、一对一的关系设计二、一对多的关系设计三、多对多的关系设计四、经验总结一、MySQL中的数据类型 字符串类型 varchar&#xff1a;即variable char &#xff0c;可边长度的字符串&#…

Tomcat启动JSP项目,搞起来了

虽然有点复古&#xff0c;但是还是有很多小伙伴在使用的&#xff0c;小编来一篇保姆级教程 1、用idea打开jsp项目 2、添加tomcat配置 3、点击后会出现配置框,这里画框的地方都选上&#xff0c;版本选择1.8&#xff0c;其他的信息内容默认后&#xff0c;点击确认 4、点击…

FITC-PEG-Biotin,荧光素-聚乙二醇-生物素的相关检测

FITC-PEG-Biotin 荧光素聚乙二醇生物素 英文名称&#xff1a;Fluorescein (polyethylene glycol) Biotin 中文名称&#xff1a;荧光素聚乙二醇生物素 激光/发射波长&#xff1a;515nm&#xff5e;520 nm 分子量&#xff1a;2000、3400、5000其他分子量可制定 溶剂&#xff…

【C++】异常

文章目录C传统处理错误方式C异常概念异常使用1.异常的抛出和捕获2.异常的重新抛出异常安全异常规范自定义异常体系C标准库的异常体系异常的优缺点C传统处理错误方式 C语言传统的错误处理机制&#xff1a; 1. 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以…

nodejs+vue文旅门户信息网站 elementui旅游项目推荐系统 景点门票预订网站vscode

在社会快速发展的影响下&#xff0c;服务行业继续发展&#xff0c;随着旅游的人数不断增加&#xff0c;使哈尔滨旅游项目推荐平台的管理和运营比过去十年更加信息化&#xff0c;依照这一现实为基础&#xff0c;设计一个快捷而又方便的网上哈尔滨旅游项目推荐平台是一项十分重要…

day73【代码随想录】二刷链表

文章目录前言一、环形链表||&#xff08;力扣142&#xff09;二、寻找重复数&#xff08;力扣287&#xff09;三、缺失的第一个正数&#xff08;力扣41&#xff09;每日一题day73&#xff1a;等差子数组&#xff08;力扣1630&#xff09;前言 1、等差子数组 2、寻找重复数 3、…

ThreeJS-纹理(十)

关键代码&#xff1a; const textureLoader new THREE.TextureLoader(); //纹理加载器加载图片 const docColorLoader textureLoader.load(static/1.webp) // let color new THREE.Color(Math.random(), Math.random(), Math.random()); const cubeMaterial new THREE.Mesh…

法规标准-GB/T 39901标准解读(2021版)

GB/T 39901是做什么的&#xff1f; GB/T 39901全名为乘用车自动紧急制动系统(AEBS)性能要求及试验方法&#xff0c;其中主要对AEBS的功能要求、性能要求及测试要求进行介绍 一般要求 1.安装有自动紧急制动系统的车辆应安装符合GB 21670-2008要求的防抱制动系统 2.AEBS的电磁…

近世代数 笔记和题型连载 第七章(阿贝尔群和循环群)

文章目录基础概念1.阿贝尔群2.循环群3.有限循环群4.元素的阶5.无限循环群相关题型1.判断一个代数系统的代数结构2.判定一个群是否是循环群3.判定一个群是否是循环群4.循环群的生成元有关问题5.判定元素的阶6.判定元素的阶7.判定元素的阶8.求给定循环群的所有子群9.求给定循环群…

七. MySQL 其它优化相关

目录一. 数据库问题排查流程二. 表连接驱动表与被驱动表Nested Loop Join 与小表驱动大表JoinBuffer 连接缓冲区Index Nested-Loop Join 索引嵌套循环连接Batched Key Access join 批量的key访问连接Block Nested-Loop Join 缓存块嵌套循环连接三. 半连接in 与 existssemi-join…

Vue 04 - Vue模板语法

目录 介绍 Mustache 插值语法 Attribute指令语法 代码演示 运行效果 介绍 Vue.js 使用了基于 HTML 的模板语法&#xff0c;允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML&#xff0c;所以能被遵循规范的浏览器和 HTML 解析器解…

PCB模块化设计10——PCI-E高速PCB布局布线设计规范

目录PCB模块化设计10——PCI-E高速PCB布局布线设计规范1、PCI-E管脚定义2、PCI-E叠层和参考平面3、 PCB设计指南1、阻抗要求2、线宽线距3、长度匹配4、走线弯曲角度5、测试点、过孔、焊盘6、AC去耦电容放置方法7、金手指和连接器的注意事项8、其他的注意事项PCB模块化设计10——…

STM32学习笔记(一)基础知识

文章目录一、什么是单片机&#xff1f;二、STM系列单片机命名规则三、STM32F103C8T6单片机简介四、标准库与HAL库区别五、通用输入输出端口GPIO六、复位和时钟控制&#xff08;RCC&#xff09;七、时钟八、中断和事件九、定时器一、什么是单片机&#xff1f; 单片机和PC电脑相比…