Spring Data JPA

    1. Data

概念

Spring Data致力于为数据访问层提供统一的的基于Spring的编程模板。

优势

对接多种数据存储技术,统一了数据访问层:Nysql、Oracle、ElasticSearch、Redis、MongoDB、Neo4j
Spring Data主要模块

Spring Data Common

String Data JDBC

String Data JPA

String Data Redis

String Data MongoDB

String Data REST

String Data ElasticSearch

String Data Neo4j

创建父工程

创建一个maven父工程用来管理Spring Data各模块,src目录不需要,删除之。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tongwx</groupId>
    <artifactId>spring-data</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>hibernate</module>
    </modules>

</project>

什么是JDBC

JDBC(Java Database Connectivity,Java数据库连接)是Sun公司设计的用于Java程序操作数据库的接口,由各个关系型数据库进行实现。

什么是JPA

JPA(Java Persistence API,Java持久层API)是Sun官方提出的Java持久化规范,由各个ORM(Object-Relational Mapping)框架进行实现。

JPA通过注解或XML进行对象关系映射,并将运行期的实体对象持久化到数据库中。

什么是Spring Data Jpa

Spring Data JPA是Spring基于JPA规范封装的一套JPA应用框架,底层使用了Hibernate的JPA技术实现,可简化对数据的访问和操作。

Sun引入新的JPA ORM规范出于两个原因:其一,简化现有的持久化开发工作;其二,Sun希望整合统一ORM技术。

Spring Data Jpa官网

Spring Data JPA

Spring Data JPA - Reference Documentation

GitHub - spring-projects/spring-data-jpa: Simplifies the development of creating a JPA-based data access layer.

使用Hibernate操作数据库

Spring Data JPA的底层实现是Hibernate,以下是Hibernate的一个示例

创建maven模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-data</artifactId>
        <groupId>com.tongwx</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>hibernate</artifactId>

    <dependencies>
        <!-- hibernate对jpa的支持包 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.32.Final</version>
        </dependency>
        <!-- c3p0 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>5.4.29.Final</version>
        </dependency>
        <!-- Mysql and MariaDB -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <!-- junit4 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <!-- log日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- lombok表达式 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

实体类

package com.tongwx.hibernate.pojo;

import lombok.Data;

import javax.persistence.*;//要导javax.persistence下的,勿导org.hibernate.annotations下的

@Data
@Entity//声明为实体类
@Table(name = "customer")//配置实体类和表的映射关系
public class Customer {
        @Id//声明主键
        @GeneratedValue(strategy = GenerationType.IDENTITY)//配置主键的生成策略
        @Column(name = "cust_id")//配置实体类属性和表字段的映射关系
        private Long custId;

        @Column(name = "cust_name")
        private String custName;

        @Column(name="cust_address")
        private String custAddress;
}

主键生成策略有:

GenerationType.IDENTITY: 自增,MySQL支持

GenerationType.SEQUENCE: 序列,Oracle支持

GenerationType.TABLE: 使用一个特定的数据库表格来保存主键

GenerationType.AUTO: 由程序自动选择主键生成策略

hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 配置数据库连接信息(数据库要先手动创建) -->
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8&serverTimezone=GMT%2B8</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
        <!-- 是否显示sql语句,默认false -->
        <property name="show_sql">true</property>
        <!-- 是否格式化sql,默认false -->
        <property name="format_sql">true</property>
        <!-- 表生成策略:
            none:默认策略。不自动生成
            update:有则更新无则创建
            create:每次都创建 -->
        <property name="hbm2ddl.auto">update</property>
        <!-- 配置方言(数据库类型) -->
        <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
        <!-- 配置需要映射的pojo -->
        <mapping class="com.tongwx.hibernate.pojo.Customer"/>
    </session-factory>
</hibernate-configuration>

增删改查API

package com.tongwx.hibernate;

import com.tongwx.hibernate.pojo.Customer;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.junit.Test;

import java.util.List;

public class HibernateTest {

    @Test
    public void testCrud() {
        StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure("/hibernate.cfg.xml").build();
        SessionFactory sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
        Session session = sessionFactory.openSession();
        Transaction transaction;
        // 增
        transaction = session.beginTransaction();
        Customer saveCustomer = new Customer();
        saveCustomer.setCustName("张三");
        session.save(saveCustomer);
        session.evict(saveCustomer);//save后未commit就evict不会使save失败
        transaction.commit();
        // 更新(全字段更新,为null的属性会覆盖原字段值,所以最好先查出来再改再更新)
        transaction = session.beginTransaction();
        Customer toUpdateCustomer = new Customer();
        toUpdateCustomer.setCustId(1L);
        toUpdateCustomer.setCustAddress("123456");
        //session.saveOrUpdate(toUpdateCustomer);
        session.update(toUpdateCustomer);
        transaction.commit();
        session.evict(toUpdateCustomer);//update后未commit就evict会使update失败
        // 实时查询
        Customer findCustomer = session.find(Customer.class, 1L);
        System.out.println(findCustomer);
        session.evict(findCustomer);
        // 实时查询
        Customer getCustomer = session.get(Customer.class, 1L);
        System.out.println("session.get实时查询,在本句输出之前输出sql");
        System.out.println(getCustomer);
        session.evict(getCustomer);
        // 延迟查询
        Customer loadCustomer = session.load(Customer.class, 1L);
        System.out.println("session.load延迟查询,在本句输出之后输出sql");
        System.out.println(loadCustomer);
        session.evict(loadCustomer);

        // 增(HQL)
        transaction = session.beginTransaction();
        session.createQuery("insert into Customer (custName) select custName from Customer where custId=1")
                .executeUpdate();
        transaction.commit();
        // 更新(HQL)
        transaction = session.beginTransaction();
        session.createQuery("Update Customer set custName=:custName where custId=:id")
                .setParameter("custName", "李四")
                .setParameter("id", 2L)
                .executeUpdate();
        transaction.commit();
        // 单个查询(HQL)
        Customer singleResult = session
                .createQuery("SELECT c FROM Customer c where c.custId=:id", Customer.class)
                .setParameter("id", 2L)
                .getSingleResult();
        System.out.println("singleResult::" + singleResult);
        session.evict(singleResult);
        // 列表查询(HQL)
        List<Customer> resultList = session.createQuery("SELECT c FROM Customer c", Customer.class).getResultList();
        System.out.println("更新后resultList:" + resultList);
        resultList.forEach(session::evict);

        // 删除
        transaction = session.beginTransaction();
        Customer toDeleteCustomer = new Customer();
        toDeleteCustomer.setCustId(1L);
        //session.remove(toDeleteCustomer);
        session.delete(toDeleteCustomer);
        // 删除(HQL)
        session.createQuery("DELETE FROM Customer WHERE custId=:id")
                .setParameter("id", 2L)
                .executeUpdate();
        transaction.commit();

        session.close();
        sessionFactory.close();
    }

}

Hibernate对象三种状态

持久对象称为PO(Persistence Object),瞬时对象和脱管对象又称为VO(Value Object)

Transient(瞬时态/自由态/临时态):

触发时机:new、delete()

未与session和数据库的数据关联

Persistent(持久态):

触发时机:get()、load()、find()、iterate()、save()、update()、saveOrUpdate()、persist()、lock()

与session和数据库相关联

Detached(脱管态):

触发时机:evict()、clear()、close()、commit()、setter

脱管对象比瞬时对象多了一个数据库记录标识值

save()和persist()将会引发SQL INSERT

delete()会引发SQL DELETE,

update()或merge()会引发SQL UPDATE。

对持久化(persistent)实例的修改在刷新提交的时候会被检测到,它也会引起SQL UPDATE。

saveOrUpdate()或者replicate()会引发SQLINSERT或者UPDATE

persist和save区别

persist:标识符(identifier)填入到持久化实例中可能被推迟到flush的时候。

save:标识符立即填入到持久化实例中

merge和update区别

merge:如果session中存在相同持久化标识(identifier)的实例,用用户给出的顺时对象覆盖session已有的持久对象

update:如果session中存在相同持久化标识(identifier)的实例,会抛出异常

lock和update区别

update是把一个已经更改过的脱管状态的对象变成持久状态

lock是把一个没有更改过的脱管状态的对象变成持久状态

clear和evict的区别

clear完整的清除session缓存

evcit(obj)把某个持久化对象从session的缓存中清空。

在一个事务中,调用save后马上再调用evict,数据会保存到库中;调用update后马上再调用evict,数据不会保存到库中。

Hibernate事务

Hibernate缓存

    • JPA操作数据库

Jpa的对象4种状态

临时状态:刚创建出来,没有与entityManager发生关系,没有被持久化,不处于entityManager中的对象

持久状态:与entityManager发生关系,已经被持久化,可以把持久化状态当做实实在在的数据库记录

删除状态:执行remove方法,事物提交之前

游离状态:游离状态就是事务commit后实体的状态,因为事务已经提交了,此时实体的属性任你如何改变,也不会同步到数据库。

public void persist(Object entity)

persist方法可以将实例转换为managed(托管)状态。在调用flush()方法或提交事物后,实例将会被插入到数据库中。

对不同状态下的实例A,persist会产生以下操作:

如果A是一个new状态的实体,它将会转为managed状态;

如果A是一个managed状态的实体,它的状态不会发生任何改变。但是系统仍会在数据库执行INSERT操作;

如果A是一个removed(删除)状态的实体,它将会转换为受控状态;

如果A是一个detached(分离)状态的实体,该方法会抛出IllegalArgumentException异常,具体异常根据不同的JPA实现有关。

public void merge(Object entity)

merge方法的主要作用是将用户对一个detached状态实体的修改进行归档,归档后将产生一个新的managed状态对象。

对不同状态下的实例A,merge会产生以下操作:

如果A是一个detached状态的实体,该方法会将A的修改提交到数据库,并返回一个新的managed状态的实例A2;

如果A是一个new状态的实体,该方法会产生一个根据A产生的managed状态实体A2;

如果A是一个managed状态的实体,它的状态不会发生任何改变。但是系统仍会在数据库执行UPDATE操作;

如果A是一个removed状态的实体,该方法会抛出IllegalArgumentException异常。

public void refresh(Object entity)

refresh方法可以保证当前的实例与数据库中的实例的内容一致。

对不同状态下的实例A,refresh会产生以下操作:

如果A是一个new状态的实例,不会发生任何操作,但有可能会抛出异常,具体情况根据不同JPA实现有关;

如果A是一个managed状态的实例,它的属性将会和数据库中的数据同步;

如果A是一个removed状态的实例,该方法将会抛出异常Entity not managed

如果A是一个detached状态的实体,该方法将会抛出异常。

public void remove(Object entity)

remove方法可以将实体转换为removed状态,并且在调用flush()方法或提交事物后删除数据库中的数据。

对不同状态下的实例A,remove会产生以下操作:

如果A是一个new状态的实例,A的状态不会发生任何改变,但系统仍会在数据库中执行DELETE语句;

如果A是一个managed状态的实例,它的状态会转换为removed;

如果A是一个removed状态的实例,不会发生任何操作;

如果A是一个detached状态的实体,该方法将会抛出异常。

开发步骤

创建Jpa核心配置文件

src\main\resources\META-INF\persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!-- 配置持久化单元
    transaction-type:事务类型(RESOURCE_LOCAL:本地事务管理;JTA:分布式事务管理 -->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
    <!-- 配置JPA规范的服务提供商 -->
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <properties>
        <!-- 配置数据库连接信息 -->
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/spring-data-jpa" />
        <property name="javax.persistence.jdbc.user" value="root" />
        <property name="javax.persistence.jdbc.password" value="root" />
        <!-- Jpa服务提供商的可选配置 -->
        <property name="hibernate.show_sql" value="true" />
        <property name="hibernate.format_sql" value="true" />
        <property name="hibernate.hbm2ddl.auto" value="create" /><!-- 启动时建表方式:create已有表则先删后建;update已有表则不建;none不建表 -->
    </properties>
</persistence-unit>
</persistence>

注:配置文件的根元素模板在idea中有:

File-》New-》Edit File Template...-》Other标签页-》JPA-》Deployment descriptors-》persistence_2_0.xml

创建实体类并使用Jpa注解配置映射关系

    1. Data JPA框架搭建

Mysql建表

CREATE TABLE customer (

id BIGINT (32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',

NAME VARCHAR (32) NOT NULL COMMENT '客户名称(公司名称)',

source VARCHAR (32) DEFAULT NULL COMMENT '客户信息来源',

industry VARCHAR (32) DEFAULT NULL COMMENT '客户所属行业',

LEVEL VARCHAR (32) DEFAULT NULL COMMENT '客户级别',

address VARCHAR (128) DEFAULT NULL COMMENT '客户联系地址',

phone VARCHAR (64) DEFAULT NULL COMMENT '客户联系电话',

PRIMARY KEY (`id`)

) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4;

父工程添加spring-data-bom

父工程的pom中添加spring-data-bom来统一管理SpringData子项目的版本:

<!-- 统一管理SpringData子项目的版本-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-bom</artifactId>
            <version>2021.1.3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

创建maven模块

在这里因为我们是创建的maven工程,所以有必要添加hibernate支持。如果是SpringBoot项目的话,可以不去添加依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-data</artifactId>
        <groupId>com.tongwx</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-data-jpa</artifactId>

    <dependencies>
        <!--spring-data-jpa-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!--hibernate对jpa的支持包-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.32.Final</version>
        </dependency>
        <!--mysql and MariaDB-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <!-- log日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--spring-test-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.15</version>
        </dependency>
        <!-- lombok表达式 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

配置Spring Data Jpa

方式一:JavaConfig配置:com.tongwx.spring.data.jpa.config.SpringDataJPAConfig:

package com.tongwx.spring.data.jpa.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration
@EnableJpaRepositories(basePackages = "com.tongwx.spring.data.jpa.repositories")
@EnableTransactionManagement
public class SpringDataJPAConfig {

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactory.setDataSource(dataSource());
        entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
        entityManagerFactory.setPackagesToScan("com.tongwx.spring.data.jpa.pojo");
        return entityManagerFactory;
    }

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        //jpaVendorAdapter.setDatabase(Database.MYSQL);
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setShowSql(true);
        return jpaVendorAdapter;
    }
}

方式二:xml配置:resources/springdata.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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/data/jpa
        https://www.springframework.org/schema/data/jpa/spring-jpa.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--用户整合jpa @EnableJpaRepositories-->
    <jpa:repositories base-package="com.tongwx.spring.data.jpa.repositories"
                      entity-manager-factory-ref="entityManagerFactory"
                      transaction-manager-ref="transactionManager" />

    <!--EntityManagerFactory-->
    <bean name="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="jpaVendorAdapter">
            <!--hibernate实现-->
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--是否生成数据库表,true相当于hbm2ddl.auto的update,false相当于hbm2ddl.auto的none-->
                <property name="generateDdl" value="true"/>
                <!--是否打印sql-->
                <property name="showSql" value="true"/>
            </bean>
        </property>
        <!--设置实体类的包-->
        <property name="packagesToScan" value="com.tongwx.spring.data.jpa.pojo"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--数据源-->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data?characterEncoding=UTF-8"/>  <!--这里要注意:在我配置了其他属性之后,就加载不出来了!-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!--声明式事务-->
    <bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!--启动注解方式的声明式事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

实体类

package com.tongwx.spring.data.jpa.pojo;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity//声明为实体类
@Table(name = "customer")//配置实体类和表的映射关系
public class Customer {
        @Id//声明主键
        @GeneratedValue(strategy = GenerationType.IDENTITY)//配置主键的生成策略
        @Column(name = "cust_id")//配置实体类属性和表字段的映射关系
        private Long custId;

        @Column(name = "cust_name")
        private String custName;

        @Column(name="cust_address")
        private String custAddress;
}

注:

@Column常用属性:

unique:是否值唯一。默认false。(给String指定unique为true时,需同时指定length属性值为小于等于unique允许最大长度767bytes/4)

nullable:是否允许null。默认true

查询方式

DAO接口继承自

说明

原生API查询

    • API操作数据库

CrudRepository接口常用增删改查API(CrudRepository接口继承了Repository接口)

    <S extends T> S save(S entity);

    <S extends T> Iterable<S> saveAll(Iterable<S> entities);

    Optional<T> findById(ID id);

    boolean existsById(ID id);

    Iterable<T> findAll();

    Iterable<T> findAllById(Iterable<ID> ids);

    long count();

    void deleteById(ID id);

    void delete(T entity);

    void deleteAllById(Iterable<? extends ID> ids);

    void deleteAll(Iterable<? extends T> entities);

    void deleteAll();

PagingAndSortingRepository接口API(PagingAndSortingRepository接口继承了CrudRepository接口)

    Iterable<T> findAll(Sort sort);

Page<T> findAll(Pageable pageable);

DAO接口

package com.tongwx.spring.data.jpa.repositories;

import com.tongwx.spring.data.jpa.pojo.Customer;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface CustomerRepositories extends PagingAndSortingRepository<Customer,Long>{
}

注:泛型参数第一个是pojo类型,第二个是pojo的主键类型

原生API操作

package com.tongwx.spring.data.jpa;

import com.tongwx.spring.data.jpa.config.SpringDataJPAConfig;
import com.tongwx.spring.data.jpa.pojo.Customer;
import com.tongwx.spring.data.jpa.repositories.CustomerRepositories;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Optional;

//@ContextConfiguration("/springdatajpa.xml")
@ContextConfiguration(classes = {SpringDataJPAConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerApiRepositoriesTest {

    @Autowired
    CustomerApiRepositories repositories;

    /**
     * save时:
     * 若对象主键为空,使用persist方法来insert
     * 若对象主键非空,先load,有记录且属性值有差异则merge(update),无记录则insert
     * (insert时若主键自增则用自增值不用设置值)
     * (update时是全量属性更新,
     *      如果只想更新和字段不一致的属性,可在pojo类上标注@DynamicUpdate注解
     *      如果只想更新手动改过值的属性,需先查询再修改再保存)
     */
    @Test
    public void testSaveOrUpdate() {
        Customer customer = new Customer();
        //customer.setCustId(8L);
        customer.setCustName("王五");
        customer.setCustAddress("王五的家");
        repositories.save(customer);
    }

    @Test
    public void testQuery() {
        // 按主键查
        Optional<Customer> opt = repositories.findById(3L);
        System.out.println("==========按主键查结果==========");
        opt.ifPresent(System.out::println);
        // 查全部
        Iterable<Customer> all = repositories.findAll();
        System.out.println("==========查全部结果==========");
        all.forEach(System.out::println);
        // 分页查
        PageRequest pageRequest = PageRequest.of(0, 2, Sort.Direction.ASC, "custName");
        Page<Customer> page = repositories.findAll(pageRequest);
        System.out.println("==========分页查结果==========");
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("当前页内容:");
        page.getContent().forEach(System.out::println);
        // 硬编码不安全的排序
        Sort sort = Sort.by("custId").descending();
        Iterable<Customer> sortAll = repositories.findAll(sort);
        System.out.println("==========硬编码不安全的排序结果==========");
        sortAll.forEach(System.out::println);
        // 类型安全的排序
        Sort.TypedSort<Customer> sortType = Sort.sort(Customer.class);
        Sort typeSort = sortType.by(Customer::getCustName).ascending()
                .and(sortType.by(Customer::getCustAddress).descending());
        Iterable<Customer> typeSortAll = repositories.findAll(typeSort);
        System.out.println("==========类型安全的排序结果==========");
        typeSortAll.forEach(System.out::println);
        // 排序后分页
        //PageRequest sortAndPageRequest = PageRequest.of(0, 2, Sort.Direction.ASC, "custName");
        PageRequest sortAndPageRequest = PageRequest.of(0, 2, typeSort);
        Page<Customer> sortAndPage = repositories.findAll(sortAndPageRequest);
        System.out.println("==========排序后分页结果==========");
        System.out.println("总记录数:" + sortAndPage.getTotalElements());
        System.out.println("总页数:" + sortAndPage.getTotalPages());
        System.out.println("当前页内容:");
        sortAndPage.getContent().forEach(System.out::println);
    }

    /**
     * 在这里delete方法,可以发现一个问题:
     *  1、底层会帮我们先进行查询一次,在进行删除!
     *  2、这样属性就不是游离状态,而是持久状态了!
     */
    @Test
    public void testDelete() {
        Customer customer = new Customer();
        customer.setCustId(7L);
        customer.setCustName("王五");
        repositories.delete(customer);
    }
}

    • JPQL或原生SQL操作数据库

DAO接口

package com.tongwx.spring.data.jpa.repositories;

import com.tongwx.spring.data.jpa.pojo.Customer;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface CustomerJpqlOrSqlRepositories extends PagingAndSortingRepository<Customer,Long> {

    /**
     * 如果查出单条,可用pojo也可用集合接收,如果查出多条,需要用集合接收
     * 占位符两种表示形式:
     *      1)英文问号紧跟序号,序号从1开始
     *      2)具名参数形式:英文冒号紧跟变量名,此时需在形式参数前添加注解@Param("变量名")
     */
    @Query("from Customer where custName = ?1")
    List<Customer> findByCustName(String custName);


    @Query("from Customer where idCard = :idCard")
    Customer findByIdCard(@Param("idCard")String idCard);

    /**
     * 增删改数据时,要加上@Modifying注解,并且在service层加上事务如@Transactional
     * @return 受影响行数
     */
    @Query("update Customer set custName = :custName where custId = :custId")
    @Modifying
    int updateByCustId(@Param("custName") String custName, @Param("custId") Long custId);

    /**
     * JPQL不支持insert语句,Jpa的实现hibernate的HQL支持insert...select语句
     * @return 受影响行数
     */
    @Query("insert into Customer (custName) select custName from Customer where custId = ?1")
    @Modifying
    int insertBySelect(Long custId);

    /**
     * 使用原生SQL查询:在JPQL查询基础上:
     *      1)给@Query注解增加参数:nativeQuery = true
     *      2)修改JPQL语句为SQL语句
     * @return 受影响行数
     */
    @Query(value = "insert into customer (cust_name) values (?1)", nativeQuery = true)
    @Modifying
    int insertByNativeQuery(String custName);

}

JPQL或原生SQL操作

package com.tongwx.spring.data.jpa;

import com.tongwx.spring.data.jpa.config.SpringDataJPAConfig;
import com.tongwx.spring.data.jpa.pojo.Customer;
import com.tongwx.spring.data.jpa.repositories.CustomerJpqlOrSqlRepositories;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

//@ContextConfiguration("/springdatajpa.xml")
@ContextConfiguration(classes = {SpringDataJPAConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerJpqlOrSqlRepositoriesTest {

    @Autowired
    CustomerJpqlOrSqlRepositories repositories;

    @Test
    public void testQuery() {
        List<Customer> customers = repositories.findByCustName("王五");
        customers.forEach(System.out::println);

        Customer customer = repositories.findByIdCard("1");
        System.out.println(customer);
    }

    @Test
    @Transactional
    @Rollback(false)//@Transactional遇@Test时默认会回滚事务:@Rollback.value default true
    public void testUpdate(){
        int result = repositories.updateByCustId("赵六", 1L);
        System.out.println(result);
    }

    @Test
    @Transactional
    @Rollback(false)//@Transactional遇@Test时默认会回滚事务:@Rollback.value default true
    public void testInsert(){
        int result = repositories.insertBySelect(1L);
        System.out.println(result);
    }

    @Test
    @Transactional
    @Rollback(false)//@Transactional遇@Test时默认会回滚事务:@Rollback.value default true
    public void testNativeQuery(){
        int result = repositories.insertByNativeQuery("钱七");
        System.out.println(result);
    }

}

存储库查询关键字

查询主题关键词

只支持查询和删除

官方文档:Spring Data JPA - Reference Documentation

关键词

描述

find…By

read…By

get…By

query…By

search…By

stream…By

一般查询方法通常返回存储库类型、Collection或Streamable子类型或结果包装器(例如Page)GeoResults或任何其他特定于存储的结果包装器。可以用作findBy…,findMyDomainTypeBy…或与其他关键字结合使用。

exists…By

存在投影,通常返回boolean结果。

count…By

返回数字结果的计数投影。

delete…By

remove…By

删除查询方法不返回任何结果 (void) 或删除计数。

…First<number>…

…Top<number>…

将查询结果限制为第一个<number>结果。此关键字可以出现在主题中介于find(和其他关键字)和之间的任何位置by。

…Distinct…

使用不同的查询只返回唯一的结果。请查阅特定于商店的文档是否支持该功能。此关键字可以出现在主题中介于find(和其他关键字)和之间的任何位置by。

谓词关键字

官方文档Spring Data JPA - Reference Documentation

关键词

样本

JPQL 片段

Distinct

findDistinctByLastnameAndFirstname

select distinct …​ where x.lastname = ?1 and x.firstname = ?2

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

IsEquals

findByFirstname,findByFirstnameIs,findByFirstnameEquals

… where x.firstname = ?1

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age <= ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull,Null

findByAge(Is)Null

… where x.age is null

IsNotNull,NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1(附加绑定的参数%

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1(带前缀的参数绑定%

Containing

findByFirstnameContaining

… where x.firstname like ?1(包裹在 中的参数绑定%

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> ages)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstname) = UPPER(?1)

谓词修饰符

官方文档Spring Data JPA - Reference Documentation

修饰符

描述

IgnoreCase,IgnoringCase

与谓词关键字一起使用以进行不区分大小写的比较。

AllIgnoreCase,AllIgnoringCase

忽略所有合适属性的大小写。在查询方法谓词的某处使用。

OrderBy…

指定静态排序顺序,后跟属性路径和方向(例如OrderByFirstnameAscLastnameDesc)。

代码示例

DAO接口

package com.tongwx.spring.data.jpa.repositories;

import com.tongwx.spring.data.jpa.pojo.Customer;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface CustomerMethodNameRepositories extends PagingAndSortingRepository<Customer,Long> {

    /**
     * 使用方法命名查询:
     *      只支持查询和删除
     *      删除操作仍然需要加上@Modifying注解和在service层加上事务如@Transactional
     */
    @Modifying
    int deleteByCustNameLike(String custName);

}

方法命名查询或删除

package com.tongwx.spring.data.jpa;

import com.tongwx.spring.data.jpa.config.SpringDataJPAConfig;
import com.tongwx.spring.data.jpa.repositories.CustomerMethodNameRepositories;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

//@ContextConfiguration("/springdatajpa.xml")
@ContextConfiguration(classes = {SpringDataJPAConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerMethodNameRepositoriesTest {

    @Autowired
    CustomerMethodNameRepositories methodNameRepositories;

    @Test
    @Transactional
    @Rollback(false)//@Transactional遇@Test时默认会回滚事务:@Rollback.value default true
    public void testDelete() {
        int result = methodNameRepositories.deleteByCustNameLike("%五%");
        System.out.println(result);
    }
}

    • Example查询数据库(可实现动态查询)
  1. 只支持查询
  2. 不支持嵌套或分组的属性约束,如 firstname = ?1 or (firstname = ?2 and lastname = ?3)
  3. 只支持字符串 start/contains/ends/ignoreCase/regex 匹配和其他属性类型的精确匹配
  4. 自定义的Repository接口除了继承PagingAndSortingRepository接口外,还要继承QueryByExampleExecutor接口

DAO接口

package com.tongwx.spring.data.jpa.repositories;


import com.tongwx.spring.data.jpa.pojo.Customer;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

public interface CustomerExampleRepositories extends PagingAndSortingRepository<Customer,Long>, QueryByExampleExecutor<Customer> {
}

Example查询

package com.tongwx.spring.data.jpa;

import com.tongwx.spring.data.jpa.config.SpringDataJPAConfig;
import com.tongwx.spring.data.jpa.pojo.Customer;
import com.tongwx.spring.data.jpa.repositories.CustomerExampleRepositories;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//@ContextConfiguration("/springdatajpa.xml")
@ContextConfiguration(classes = {SpringDataJPAConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerExampleRepositoriesTest {

    @Autowired
    CustomerExampleRepositories repositories;

    @Test
    public void test02() {
        // 筛选器,设置筛选字段
        Customer customer = new Customer();
        customer.setCustName("QIAN");
        customer.setCustAddress("BEI");
        customer.setIdCard("CH");
        // 匹配器,设置匹配规则
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withIgnorePaths("custName")//设置不需匹配的属性
                .withIgnoreCase("custAddress", "idCard")//设置忽略大小写,无参则所有设值的属性忽略大小写
                .withStringMatcher(ExampleMatcher.StringMatcher.STARTING)//设置所有String类型以设置的值开头
                /*
                 * 当withMatcher设置的属性匹配规则与withIgnoreCase冲突时(如caseSensitive、startsWith、endsWith),withMatcher设置的优先
                 * 当withMatcher设置的属性匹配规则与withStringMatcher冲突时(如startsWith与ENDING),withMatcher设置的优先
                 */
                .withMatcher("idCard", ExampleMatcher.GenericPropertyMatcher::endsWith);
        /*
         *     select *
         *     from customer
         *     -- withStringMatcher的STARTING与withMatcher的endsWith冲突,取endsWith;withIgnoreCase与withMatcher的endsWith冲突,不忽略大小写
         *     where id_card like '%CH'
         *     and lower(cust_address) like 'bei%'
         */
        Example<Customer> example = Example.of(customer, matcher);
        System.out.println(repositories.findAll(example));
    }
}

    • Specification查询数据库(可实现动态查询)

使用Query by Example只能针对字符串进行条件设置,如果希望对所有类型支持,可以使用Specification

使用Specification,需要自定义的Repository接口继承接口JpaSpecificationExecutor

限制:不能分组、聚合函数,需要自己通过entityManager实现。

DAO接口

package com.tongwx.spring.data.jpa.repositories;


import com.tongwx.spring.data.jpa.pojo.Customer;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface CustomerSpecificationRepositories extends PagingAndSortingRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}

Specification查询

package com.tongwx.spring.data.jpa;

import com.google.common.collect.Lists;
import com.tongwx.spring.data.jpa.config.SpringDataJPAConfig;
import com.tongwx.spring.data.jpa.pojo.Customer;
import com.tongwx.spring.data.jpa.repositories.CustomerSpecificationRepositories;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

//@ContextConfiguration("/springdatajpa.xml")
@ContextConfiguration(classes = {SpringDataJPAConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerSpecificationRepositoriesTest {

    @Autowired
    CustomerSpecificationRepositories repositories;

    @Test
    public void testQuery() {
        //模拟一个前端请求参数
        Customer param = new Customer();
        param.setCustName("Q");
        param.setCustAddress("Beijing/Shanghai/Guangzhou/Shenzhen");


        List<Customer> results = repositories.findAll(new Specification<Customer>() {
            /**
             * @param root 查询哪个表 = from
             * @param query 查询哪些字段,排序是什么 =组合(order by . where)
             * @param criteriaBuilder 条件之间是什么关系,如何生成一个查询条件,每一个查询条件都是什么类型(>、between、in、…) = where
             * @return Predicate(Expression):每一条查询条件的详细描述
             */
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if(param.getCustId() != null){
                    predicates.add(criteriaBuilder.equal(root.get("custId"), param.getCustId()));
                }
                if(StringUtils.hasText(param.getCustName())){
                    predicates.add(criteriaBuilder.like(root.get("custName"), "%" + param.getCustName() + "%"));
                }
                if(StringUtils.hasText(param.getCustAddress())){
                    CriteriaBuilder.In<String> in = criteriaBuilder.in(root.get("custAddress"));
                    Arrays.stream(param.getCustAddress().split("/")).forEach(in::value);
                    predicates.add(in);
                }
                if(StringUtils.hasText(param.getIdCard())){
                    predicates.add(criteriaBuilder.like(root.get("idCard"), "%" + param.getIdCard() + "%"));
                }
                Predicate and = criteriaBuilder.and(predicates.toArray(new Predicate[0]));

                //如果不需要结果排序,直接返回and
                //return and;

                //结果排序
                Order idCardAsc = criteriaBuilder.asc(root.get("idCard"));
                Order custNameAsc = criteriaBuilder.asc(root.get("custName"));
                return query.where(and).orderBy(Lists.newArrayList(idCardAsc, custNameAsc)).getRestriction();

            }
        });
        results.forEach(System.out::println);
    }

}

    • QueryDSL查询数据库(可实现动态查询)

QueryDSL是基于ORM框架或SQL平台上的一个通用查询框架。借助QueryDSL可以在任何支持的ORM框架或SQL平台上以通用API方式构建查询。 JPA是QueryDSL的主要集成技术,是JPQL和Criteria查询的代替方法。目前QueryDSL支持的平台包括JPA、JDO、SQL、Mongodb等

Querydsl扩展能让我们以链式方式代码编写查询方法。该扩展需要一个接口QueryDslPredicateExecutor,它定义了很多查询方法。接口继承了该接口,就可以使用该接口提供的各种方法了。

QueryDSL官网:Querydsl - Unified Queries for Java

添加依赖和插件

插件是为了让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)。

    <properties>
        <querydsl.version>4.4.0</querydsl.version>
        <apt.version>1.1.3</apt.version>
    </properties>

    <dependencies>
        <!-- querydsl -->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>${apt.version}</version>
                <dependencies>
                    <dependency>
                        <groupId>com.querydsl</groupId>
                        <artifactId>querydsl-apt</artifactId>
                        <version>${querydsl.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/queries</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                            <logOnlyOnError>true</logOnlyOnError>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

将插件扩展配置项<outputDirectory>指定的目录添加为Sources

先编译当前模块,编译完成后当前模块下会生成<outputDirectory>指定的目录target/generated-sources/queries

检查该目录是否为Sources,如否,将该目录标记为Sources

DAO接口

package com.tongwx.spring.data.jpa.repositories;

import com.tongwx.spring.data.jpa.pojo.Customer;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface CustomerQueryDslRepositories extends PagingAndSortingRepository<Customer,Long>, QuerydslPredicateExecutor<Customer> {
}

QueryDSL查询

等于(equal): eq

不等于(not equal): ne

小于(less than): lt

大于(greater than): gt

小于等于(less than or equal): loe

大于等于(greater than or equal): goe

package com.tongwx.spring.data.jpa;

import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.tongwx.spring.data.jpa.config.SpringDataJPAConfig;
import com.tongwx.spring.data.jpa.pojo.Customer;
import com.tongwx.spring.data.jpa.pojo.QCustomer;
import com.tongwx.spring.data.jpa.repositories.CustomerQueryDslRepositories;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Optional;

//@ContextConfiguration("/springdatajpa.xml")
@ContextConfiguration(classes = {SpringDataJPAConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerQueryDslRepositoriesTest {

    @Autowired
    private CustomerQueryDslRepositories repositories;
    @PersistenceContext
    private EntityManager entityManager;
    @Test
    public void testSingleQuery() {
        QCustomer customer = QCustomer.customer;
        BooleanExpression expression = customer.custId.loe(10L)
                .and(customer.custName.in("张三", "Qianqi"))
                .and(customer.custAddress.eq("Beijing"))
                .and(customer.idCard.startsWithIgnoreCase("CH"));
        Optional<Customer> one = repositories.findOne(expression);
        one.ifPresent(System.out::print);
    }

    /**
     * select       cust_address, count(cust_id)
     * from         customer
     * where        cust_id between 1 and 10
     * group by     cust_address
     * order by     cust_address asc
     */
    @Test
    public void testGroupQuery(){
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QCustomer qCustomer = QCustomer.customer;
        QueryResults<Tuple> tupleQueryResults = queryFactory.from(qCustomer)
                .select(qCustomer.custAddress, qCustomer.custId.count())
                .where(qCustomer.custId.between(1, 10))
                .orderBy(qCustomer.custAddress.asc())
                .groupBy(qCustomer.custAddress)
                .fetchResults();
        System.out.println("custAddress\t\tcount(custId)");
        for (Tuple result : tupleQueryResults.getResults()) {
            System.out.print(result.get(qCustomer.custAddress) + "\t\t\t");
            System.out.println(result.get(qCustomer.custId.count()));
        }
    }

}

    1. Boot整合Spring Data JPA

新建模块

创建Spring Initializr模块,依赖选择Spring Web、Spring Data JPA、MySQL Driver,Spring Boot版本选择2.x

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.10</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tongwx</groupId>
    <artifactId>springboot-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-jpa</name>
    <description>springboot-jpa</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--Spring Data JPA启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--Spring Web启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--MySQL驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--测试启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- lombok表达式 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

POJO类

package com.tongwx.springbootjpa.pojo;


import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

@Data
@Entity//声明为实体类
@Table(name = "customer")//配置实体类和表的映射关系
@DynamicUpdate
public class Customer {
        @Id//声明主键
        @GeneratedValue(strategy = GenerationType.IDENTITY)//配置主键的生成策略
        @Column(name = "cust_id")//配置实体类属性和表字段的映射关系
        private Long custId;

        @Column(name = "id_card", unique = true, length = 100)
        private String idCard;

        @Column(name = "cust_name")
        private String custName;

        @Column(name="cust_address")
        private String custAddress;
}

Repository接口

package com.tongwx.springbootjpa.repository;


import com.tongwx.springbootjpa.pojo.Customer;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

public interface CustomerExampleRepository extends QueryByExampleExecutor<Customer>, PagingAndSortingRepository<Customer,Long> {
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值