Bootstrap

Spring Data

一、为什么要用Spring Data

在这里插入图片描述

在实际开发过程中,会接触到不同的存储技术。比如针对不同的场景,需要es、mysql或者redis。那么开发人员就需要去学校这些存储技术的不同脚本或者相关中间件,无疑开发成本和时间成本是比较大的。

所以希望有一种技术,能够统一这些不同的存储方式的实现。spring data就是来做这样的事情的。

在这里插入图片描述

操作不同的存储技术,使用对应的spring data模板即可。

spring data帮助我们统一了数据访问层,降低了学习成本,提升了开发效率。针对不同的存储技术,提供了不同的模板对象

总结

  • S p r i n g   D a t a 致力为数据访问 ( D A O ) 提供熟悉且一致的基于 S p r i n g 的编程模板 \color{red}{Spring\ Data致力为数据访问(DAO)提供熟悉且一致的基于Spring的编程模板} Spring Data致力为数据访问(DAO)提供熟悉且一致的基于Spring的编程模板

  • 其目的是统一和简化不同类型持久存储的访问 \color{red}{其目的是统一和简化不同类型持久存储的访问} 其目的是统一和简化不同类型持久存储的访问


二、JPA介绍及JDBC的关系

JPA与JDBC

  • 相同处:
    1. 都跟数据库操作有关,JPA 是JDBC 的升华,升级版。
    2. JDBC和JPA都是一组规范接口
    3. 都是由SUN官方推出的
  • 不同处
    1. .JDBC是由各个关系型数据库实现的, JPA 是由ORM框架实现
    2. JDBC 使用SQL语句和数据库通信。 JPA用面向对象方式, 通过ORM框架来生成SQL,进行操作。
    3. JPA在JDBC之上的, JPA也要依赖JDBC才能操作数据库。

在这里插入图片描述

加载JDBC驱动,其实就是指定具体存储技术的实现

JDBC是我们最熟悉的用来操作数据库的技术,但是随之而来带来了一些问题:

  1. 需要面向SQL语句来操作数据库,开发人员学习成本更高。
  2. 数据库的移至性不高,不同数据库的SQL语句无法通用。
  3. java对象和数据库类型的映射是个麻烦事。

举个例子:
现在我要和另一个国家的人进行交流:
在这里插入图片描述那么需要用英语来进行交流,那么不同国家的人会使用不同的语言进行交流。对于我来说,若要和不同国家的人打交道,我的学习成本是比较大的。

上面的我相当于Java,不同国家的人相当于不同的存储技术。若使用JDBC,因为不同的存储技术之间规范不一样,则需要开发人员去学习相关的规范。

若能够有统一规范的工具就好了,比如翻译机:
在这里插入图片描述

此时,我只需要说中文,然后选定翻译机的模板让他去帮我做转换成其他国家语言的操作。那么对我而言,和谁讲话都只需要说中文就好了,减少了很多学习成本(只需要学习这个翻译机怎么用就行)。

这里的翻译机就相当于jpa。

当然我打招呼会指定某个外国人,比如 hello 二狗,所以这个翻译机需要找到二狗是哪个外国人。

这里翻译机找到外国人的过程,就是进行ORM映射的过程,所以我们需指定好类的每个属性和某个字段的映射关系

所以, J P A 是一种 O R M 规范! \color{red}{JPA是一种ORM规范!} JPA是一种ORM规范!

即帮我们找到指定的存储技术实例,并帮我们转换成其合法的SQL。

该规范为我们提供了:

  1. ORM映射元数据:
    • JPA支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;
    • 如:@Entity@Table@Id@Column 等注解。
  2. JPA 的API:
    • 用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。
    • 如:entityManager.merge(T t);
  3. JPQL查询语言:
    • 通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。
    • 如:from Student s where s.name = ?
      这里的Student是个对象

三、Hibernate与JPA

JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口,而接口是需要实现才能工作的。

而Hibernate就是实现了JPA接口的ORM框架。

JPA是一套ORM规范,Hibernate实现了JPA规范!

在这里插入图片描述

扩展:
hibernate和之前学的mybatis有什么区别呢?

  • mybatis:小巧、高效、简单、直接、半自动
    • 半自动的ORM框架,
    • 小巧: mybatis就是jdbc封装
    • 在国内更流行。
    • 场景: 在业务比较复杂系统进行使用
  • hibernate:强大、方便、高效、(简单)复杂、绕弯子、全自动
    • 全自动的ORM框架,
    • 强大:根据ORM映射生成不同SQL
    • 在国外更流。
    • 场景: 在业务相对简单的系统进行使用,随着微服务的流行。

四、Hibernate&JPA快速搭建

  1. 添加相关依赖

    <?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>springdata</artifactId>
            <groupId>com.guoshao.springdata</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>01-jpa-hibernate</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <!-- junit4 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
                <scope>test</scope>
            </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>5.1.22</version>
            </dependency>
            <!--openjpa-->
            <dependency>
                <groupId>org.apache.openjpa</groupId>
                <artifactId>openjpa-all</artifactId>
                <version>3.2.0</version>
            </dependency>
    
        </dependencies>
    
    </project>
    
  2. hibernate有一个概念就是 c o d e   f i r s t \color{red}{code\ first} code first,先创建实体类,然后hibernate会帮我们创建表

  3. 所以,先写一个实体类:

    package com.guoshao.pojo;
    
    import javax.persistence.*;
    
    @Entity     // 作为hibernate 实体类
    @Table(name = "tb_customer")       // 映射的表明
    public class Customer {
    
        /**
         * @Id:声明主键的配置
         * @GeneratedValue:配置主键的生成策略
         *      strategy
         *          GenerationType.IDENTITY :自增,mysql
         *                 * 底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
         *          GenerationType.SEQUENCE : 序列,oracle
         *                  * 底层数据库必须支持序列
         *          GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
         *          GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略
         * @Column:配置属性和字段的映射关系
         *      name:数据库表中字段的名称
         */
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Long custId; //客户的主键
    
        @Column(name = "cust_name")
        private String custName;//客户名称
    
        @Column(name="cust_address")
        private String custAddress;//客户地址
    
        public Long getCustId() {
            return custId;
        }
    
        public void setCustId(Long custId) {
            this.custId = custId;
        }
    
        public String getCustName() {
            return custName;
        }
    
        public void setCustName(String custName) {
            this.custName = custName;
        }
    
    
        public String getCustAddress() {
            return custAddress;
        }
    
        public void setCustAddress(String custAddress) {
            this.custAddress = custAddress;
        }
    
        @Override
        public String toString() {
            return "Customer{" +
                    "custId=" + custId +
                    ", custName='" + custName + '\'' +
                    ", custAddress='" + custAddress + '\'' +
                    "}\n";
        }
    }
    
  4. 编写hibernate.cfg.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 此文件是 Hibernate 的配置文件,遵循 Hibernate 3.0 配置 DTD -->
    <!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>
            <!-- 数据库连接相关配置 -->
            <!-- 配置 JDBC 驱动程序,这里使用 MySQLJDBC 驱动 -->
            <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
            <!-- 数据库连接 URL,连接到本地 3306 端口的 springdata_jpa 数据库,设置字符编码为 UTF-8 -->
            <property name="connection.url">jdbc:mysql://localhost:3306/springdata_jpa?characterEncoding=UTF-8</property>
            <!-- 数据库用户名 -->
            <property name="connection.username">root</property>
            <!-- 数据库密码 -->
            <property name="connection.password">123456</property>
    
            <!-- SQL 语句显示相关配置 -->
            <!-- 允许显示生成的 SQL 语句,方便调试 -->
            <property name="show_sql">true</property>
            <!-- 对显示的 SQL 语句进行格式化,提高可读性 -->
            <property name="format_sql">true</property>
    
            <!-- 数据库表的自动更新策略 -->
            <!-- 使用 update 策略,启动应用时会根据映射更新表结构,不存在的表将被创建 -->
            <property name="hbm2ddl.auto">update</property>
    
            <!-- 数据库方言配置 -->
            <!-- 采用 MySQL 5 InnoDB 存储引擎的方言,确保生成的 SQL 符合 MySQL 5 语法 -->
            <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
    
            <!-- 实体类映射配置 -->
            <!--com.xushu.pojo.Customer 类映射到数据库表 -->
            <mapping class="com.guoshao.pojo.Customer"/>
            <!--com.xushu.pojo.User 类映射到数据库表 -->
            <mapping class="com.guoshao.pojo.User"/>
            <!--com.xushu.pojo.Wife 类映射到数据库表 -->
            <mapping class="com.guoshao.pojo.Wife"/>
            <!--com.xushu.pojo.Hobby 类映射到数据库表 -->
            <mapping class="com.guoshao.pojo.Hobby"/>
        </session-factory>
    </hibernate-configuration>
    
  5. 测试

    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.Before;
    import org.junit.Test;
    
    public class HibernateTest {
        // 声明一个 SessionFactory 对象,用于创建 Session 实例
        private SessionFactory sf;
    
        // 在每个测试方法执行前进行初始化操作
        @Before
        public void init() {
            // 1. 创建标准服务注册对象,并加载配置文件 hibernate.cfg.xml
            StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                  .configure("/hibernate.cfg.xml")
                  .build();
            // 2. 根据服务注册对象创建元数据资源集,构建元数据并生成唯一的 Session 工厂
            sf = new MetadataSources(registry)
                  .buildMetadata()
                  .buildSessionFactory();
        }
    
        // 测试方法,用于保存 Customer 对象
        @Test
        public void testC() {
            // 创建 Session 实例,用于执行数据库操作
            Session sess = sf.openSession();
            // 开始事务,保证操作的原子性
            Transaction tx = sess.beginTransaction();
            try {
                // 创建 Customer 实例
                Customer customer = new Customer();
                customer.setCustName("张三");
                // 将 Customer 对象保存到数据库中
                sess.save(customer);
                // 提交事务,将操作持久化到数据库
                tx.commit();
            } catch (Exception e) {
                // 若出现异常,回滚事务,避免数据不一致
                if (tx!= null) {
                    tx.rollback();
                }
                e.printStackTrace();
            } finally {
                // 关闭 Session,释放资源
                if (sess!= null) {
                    sess.close();
                }
            }
            // 关闭 SessionFactory,释放资源
            if (sf!= null) {
                sf.close();
            }
        }
    }
    
  6. 测试HQL

    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 java.util.List;
    
    public class HibernateQueryTest {
    
        @Test
        public void testR_HQL() {
            // 创建标准服务注册器构建器
            StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
            // 配置相关属性(例如数据库连接等信息,此处省略具体配置过程)
            StandardServiceRegistry registry = registryBuilder.build();
            // 创建元数据资源并构建会话工厂
            SessionFactory sf = new MetadataSources(registry).buildMetadata().buildSessionFactory();
    
            // session进行持久化操作
            try (Session session = sf.openSession()) {
                Transaction tx = session.beginTransaction();
                // 修正后的HQL语句
                String hql = " FROM Customer where custId=:id";
                List<Customer> resultList = session.createQuery(hql, Customer.class)
                        // 修正后的参数设置方式
                       .setParameter("id", 1L)
                       .getResultList();
                System.out.println(resultList);
                tx.commit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

HQL查询编写规则:
在这里插入图片描述

当前使用的hibernate,若我们要换成open-jpa进行操作持久化操作,这时候就需要将函数中涉及hibernate持久化操作的函数都有进行替换,很麻烦。

但是如果使用了JPA,进行上述的切换是很方便的。再次回到上面那张图:
在这里插入图片描述

即如果单独使用hibernate的API来进行持久化操作,则不能随意切换其他ORM框架。但是使用JPA,来回切换其他ORM框架是很方便的。

五、基于JPA数据持久化操作

  1. 相关依赖不做改动

  2. resources下的META-INF添加xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    
        <!-- 需要配置persistence-unit节点
             持久化单元:
             name:持久化单元名称
             transaction-type:事务管理的方式
             JTA:分布式事务管理
             RESOURCE_LOCAL:本地事务管理
        -->
        <persistence-unit name="hibernateJPA" transaction-type="RESOURCE_LOCAL">
            <!-- jpa的实现方式 -->
            <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    
            <!-- 配置持久化实体类,此处应替换为实际的全限定名,例如:<class>com.example.entity.Customer</class> -->
            <class>xxx</class>
    
            <!-- 可选配置:配置jpa实现方的配置信息 -->
            <properties>
                <!-- 数据库信息 -->
                <property name="javax.persistence.jdbc.user" value="root"/>
                <property name="javax.persistence.jdbc.password" value="123456"/>
                <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/springdata_jpa?serverTimezone=UTC"/>
    
                <!-- 配置jpa实现方(hibernate)的配置信息 -->
                <property name="hibernate.show_sql" value="true" />
                <property name="hibernate.hbm2ddl.auto" value="update" />
                <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
            </properties>
        </persistence-unit>
    </persistence>
    
  3. 测试类

    package com.guoshao;
    
    import com.guoshao.pojo.Customer;
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.util.List;
    
    public class JpaTest {
    
        private EntityManagerFactory factory;
        private EntityManager em;
    
        @Before
        public void init() {
            // 加载配置文件,创建entityManagerFactory
            factory = Persistence.createEntityManagerFactory("hibernateJPA");
            // 获取entityManager对象
            em = factory.createEntityManager();
        }
    
        /**
         * 查询全部
         * jqpl:from cn.itcast.domain.Customer
         * sql:SELECT * FROM cst_customer
         */
        @Test
        public void testR_HQL() {
            EntityTransaction tx = em.getTransaction();
            try {
                tx.begin();
                String jpql = "select c from Customer c";
                Query query = em.createQuery(jpql);
                List<Customer> list = query.getResultList();
                for (Customer obj : list) {
                    System.out.print(obj);
                }
                tx.commit();
            } catch (Exception e) {
                if (tx.isActive()) {
                    tx.rollback();
                }
                e.printStackTrace();
            } finally {
                em.close();
            }
        }
    
        @Test
        public void testR() {
            EntityTransaction tx = em.getTransaction();
            try {
                tx.begin();
                Customer customer = em.getReference(com.guoshao.pojo.Customer.class, 1L);
                System.out.println("===========================");
                System.out.println(customer);
                tx.commit();
            } catch (Exception e) {
                if (tx.isActive()) {
                    tx.rollback();
                }
                e.printStackTrace();
            } finally {
                em.close();
            }
        }
    }
    

    查询:

    • em.find(x.class,id):立即查询
    • em.em.getReference(c.class,id):延迟查询

    保存和更新/没有则插入

    • em.merge(对象)
      merge会先查询,看有没有再进行插入或更新
      若不想查询,再进行插入,直接更新则需要编写hql语句
  4. 切换jpa的具体实现,修改xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    
        <!-- 需要配置persistence-unit节点
             持久化单元:
             name:持久化单元名称
             transaction-type:事务管理的方式
             JTA:分布式事务管理
             RESOURCE_LOCAL:本地事务管理
        -->
        <persistence-unit name="openJpa" transaction-type="RESOURCE_LOCAL">
            <!-- jpa的实现方式 -->
            <provider>org.openjpa.jpa.persistence.PersistenceProviderImpl</provider>
    
            <!-- 配置持久化实体类,此处应替换为实际的全限定名,例如:<class>com.example.entity.Customer</class> -->
            <class>xxx</class>
    
            <!-- 可选配置:配置jpa实现方的配置信息 -->
            <properties>
                <!-- 数据库信息 -->
                <property name="javax.persistence.jdbc.user" value="root"/>
                <property name="javax.persistence.jdbc.password" value="123456"/>
                <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/springdata_jpa?serverTimezone=UTC"/>
    
                <!-- 配置jpa实现方(openjpa)的配置信息 -->
                <!-- 可以自动生成数据库表-->
                <property name="openjpa.jdbc.SynchronizedMappings" value="buildSchema(ForeignKeys=true)" />
            </properties>
        </persistence-unit>
    </persistence>
    
  5. 修改factory = Persistence.createEntityManagerFactory("openJpa");

六、JPA的四种状态

  1. 临时状态(new):
    刚创建出来,没有与entityManager发生关系,没有被持久化,不处于entityManager中的对象
  2. 持久状态(managed):
    与entityManager发生关系,已经被持久化,您可以把持久化状态当做实实在在的数据库记录。
  3. 删除状态(remove):
    执行remove方法,事物提交之前
  4. 游离状态(detached):
    游离状态就是提交到数据库后,事务commit后实体的状态,因为事务已经提交了,此时实体的属性任你如何改变,也不会同步到数据库,因为游离是没人管的孩子,不在持久化上下文中。

在这里插入图片描述

七、JPA相关的API

  • public void persist(Object entity)

    • persist方法可以将实例转换为managed(托管)状态。在调用flush()方法或提交事物后,实例将会被插入到数据库中。 对不同状态下的实例A,persist会产生以下操作:
      1. 如果A是一个new状态的实体,它将会转为managed状态;
      2. 如果A是一个managed状态的实体,它的状态不会发生任何改变。但是系统仍会在数据库执行INSERT操作;
      3. 如果A是一个removed(删除)状态的实体,它将会转换为受控状态;
      4. 如果A是一个detached(分离)状态的实体,该方法会抛出IllegalArgumentException异常,具体异常根据不同的 JPA实现有关。
  • public void merge(Object entity)

    • merge方法的主要作用是将用户对一个detached状态实体的 进行归档,归档后将产生 一个新的managed状态对象。对不同状态下的实例A,merge会产生以下操作:
      1. 如果A是一个detached状态的实体,该方法会将A的修改提交到数据库,并返回一个新的managed状态的实例A2;
      2. 如果A是一个new状态的实体,该方法会产生一个根据A产生的managed状态实体A2;
      3. 如果A是一个managed状态的实体,它的状态不会发生任何改变。但是系统仍会在数据库执行UPDATE操作;
      4. 如果A是一个removed状态的实体,该方法会抛出IllegalArgumentException异常。
  • public void refresh(Object entity)

    • refresh方法可以保证当前的实例与数据库中的实例的内容一致。 对不同状态下的实例A,refresh会产生以下操作:
      1. 如果A是一个new状态的实例,不会发生任何操作,但有可能会抛出异常,具体情况根据不同JPA实现有关;
      2. 如果A是一个managed状态的实例,它的属性将会和数据库中的数据同步;
      3. 如果A是一个removed状态的实例,该方法将会抛出异常: Entity not managed
      4. 如果A是一个detached状态的实体,该方法将会抛出异常。
  • public void remove(Object entity)

    • remove方法可以将实体转换为removed状态,并且在调用flush()方法或提交事物后删除数据库中的数据。
      对不同状态下的实例A,remove会产生以下操作:
      1. 如果A是一个new状态的实例,A的状态不会发生任何改变,但系统仍会在数据库中执行DELETE语句;
      2. 如果A是一个managed状态的实例,它的状态会转换为removed;
      3. 如果A是一个removed状态的实例,不会发生任何操作;
      4. 如果A是一个detached状态的实体,该方法将会抛出异常。

hibernate也有类似mybtais二级缓存的东西:

  • 一级缓存是基于EntityManager
    二级缓存是基于session

八、Spring Data Jpa

Spring Data Jpa旨在改进数据访问层的实现以提升开发效率

spirng data jpa是spring提供的一套简化JPA开发的框架,按照约定好的规则进行【方法命名】去写dao层接口就可以在不写接口实现的情况下,实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等。

现在抛出一个问题:

SpringData Jpa 极大简化了数据库访问层代码。 如何简化的呢?

答:使用了SpringDataJpa,我们的dao层中只需要写接 口,就自动具有了增删改查、分页查询等方法。

在这里插入图片描述

到此,其实spring data jpa就是在jap之上再加了一层,底层原理是使用jdk动态代理去做增强工。因此我们只需要去调用接口就能运行相应的SQL函数。

九、Spring Data JPA实例

spring data官网

  1. 在父类pom中设置好spring data统一版本管理依赖:
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-bom</artifactId>
                <version>2020.0.14</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  2. 子项目添加依赖:
    <dependencies>
        <!-- Spring Data JPA 依赖 -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- Hibernate 实体管理器依赖 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.32.Final</version>
        </dependency>
        <!-- MySQL 数据库驱动依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.22</version>
        </dependency>
        <!-- JUnit 4 测试框架依赖,作用域为test,仅在测试阶段使用 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <!-- Alibaba Druid 数据库连接池依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <!-- Spring 测试相关依赖,作用域为test,用于测试阶段 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
  3. 根据官网的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:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:jdbc="http://www.springframework.org/schema/jdbc"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:jpa="http://www.springframework.org/schema/data/jpa"
           xmlns:task="http://www.springframework.org/schema/task"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    
        <!-- 1.dataSource 配置数据库连接池 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver" />
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
            <property name="user" value="root" />
            <property name="password" value="111111" />
        </bean>
    
        <!-- 2.配置entityManagerFactory -->
        <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="packagesToScan" value="cn.itcast.entity" />
            <property name="persistenceProvider">
                <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
            </property>
            <!-- JPA的供应商适配器 -->
            <property name="jpaVendorAdapter">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                    <property name="generateDdl" value="false" />
                    <property name="database" value="MYSQL" />
                    <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
                    <property name="showSql" value="true" />
                </bean>
            </property>
        </bean>
    
        <!-- 整合spring data jpa -->
        <jpa:repositories base-package="cn.guoshao.dao"
                           transaction-manager-ref="transactionManager"
                           entity-manager-factory-ref="entityManagerFactory" />
    
        <!-- 3.事务管理器 -->
        <!-- JPA事务管理器 -->
        <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory" />
        </bean>
    
        <!-- 基于注解方式的事务,开启事务的注解驱动
             如果基于注解的和xml的事务都配置了会以注解的优先
        -->
        <tx:annotation-driven transaction-manager="transactionManager" />
    
        <context:component-scan base-package="cn.itcast" />
    
        <!-- 组装其它配置文件 -->
    </beans>
    
  4. 编写pojo:
    package com.guoshao.pojo;
    
    import javax.persistence.*;
    
    @Entity     // 作为hibernate 实体类
    @Table(name = "tb_customer")       // 映射的表明
    public class Customer {
    
        /**
         * @Id:声明主键的配置
         * @GeneratedValue:配置主键的生成策略
         *      strategy
         *          GenerationType.IDENTITY :自增,mysql
         *                 * 底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
         *          GenerationType.SEQUENCE : 序列,oracle
         *                  * 底层数据库必须支持序列
         *          GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
         *          GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略
         * @Column:配置属性和字段的映射关系
         *      name:数据库表中字段的名称
         */
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Long custId; //客户的主键
    
        @Column(name = "cust_name")
        private String custName;//客户名称
    
        @Column(name="cust_address")
        private String custAddress;//客户地址
    
        public Long getCustId() {
            return custId;
        }
    
        public void setCustId(Long custId) {
            this.custId = custId;
        }
    
        public String getCustName() {
            return custName;
        }
    
        public void setCustName(String custName) {
            this.custName = custName;
        }
    
    
        public String getCustAddress() {
            return custAddress;
        }
    
        public void setCustAddress(String custAddress) {
            this.custAddress = custAddress;
        }
    
        @Override
        public String toString() {
            return "Customer{" +
                    "custId=" + custId +
                    ", custName='" + custName + '\'' +
                    ", custAddress='" + custAddress + '\'' +
                    "}\n";
        }
    }
    
  5. 编写CustomerRepository,写在<jpa:repositories base-package="cn.guoshao.dao">这个指定的包目录下:
    public interface CustomerRepository extends CrudRepository<Customer,Long>{
    }
    
    • 泛形第一个参数:操作的实体类
    • 泛形第二个参数:主键的类型
  6. 测试:
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import java.util.Optional;
    
    // 基于junit4 spring单元测试
    @ContextConfiguration("/spring.xml")
    @RunWith(SpringJUnit4ClassRunner.class)
    public class SpringdataJpaTest {
    
        @Autowired
        CustomerRepository repository;
    
        @Test
        public void testR() {
            repository.findById(1L).ifPresent(System.out::println);
        }
    
        @Test
        public void testC() {
            Customer customer = new Customer();
            customer.setCustId(3L);
            customer.setCustName("李四");
            try {
                repository.save(customer);
                System.out.println("客户数据保存成功");
            } catch (Exception e) {
                System.out.println("客户数据保存失败,原因:" + e.getMessage());
                e.printStackTrace();
            }
        }
    
        @Test
        public void testD() {
            Customer customer = new Customer();
            customer.setCustId(3L);
            customer.setCustName("李四");
            try {
                repository.delete(customer);
                System.out.println("客户数据删除成功");
            } catch (Exception e) {
                System.out.println("客户数据删除失败,原因:" + e.getMessage());
                e.printStackTrace();
            }
        }
    }
    

当然也可以使用配置类的方式,只需要将xml替换成:

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DruidDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableJpaRepositories("com.guoshao.repository")
@EnableTransactionManagement
public class JpaConfig {

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/springdata_jpa");
        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.guoshao.pojo");
        factory.setDataSource(dataSource());
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }
}

测试类中读取方式变成:@ContextConfiguration(classes=SpringDataJPAConfig.class)

十、使用Spring Data Repositories

Spring Data repository 抽象的目标是显着减少为各种持久性存储实现数据访问层所需的样板代码量

若要进行增删改查,需要实现CrudRepository接口,其包含函数:

// 用来插入和修改 有主键就是修改 没有就是新增
// 获得插入后自增 id, 获得返回值
<S extends T> S save(S entity);

// 通过集合保存多个实体
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
// 通过主键查询实体
Optional<T> findById(ID id);
// 通过主键查询是否存在 返回 boolean
boolean existsById(ID id);
// 查询所有
Iterable<T> findAll();
// 通过集合的主键 查询多个实体, 返回集合
Iterable<T> findAllById(Iterable<ID> ids);
// 查询总数量
long count();
// 根据 id 进行删除
void deleteById(ID id);
// 根据实体进行删除
void delete(T entity);
// 删除多个
void deleteAllById(Iterable<? extends ID> ids); 
// 删除多个传入集合实体
void deleteAll(Iterable<? extends T> entities); 
// 删除所有
void deleteAll();

CrudRepository还有一个子接口:PagingAndSortingRepository。额外提供排序和分页的能力。

需要分页和排序的能力,则实现PagingAndSortingRepository接口。

  • 测试分页

    @Test
    public void testPaging() {
        Page<Customer> all = repository.findAll(PageRequest.of(0, 2));
        System.out.println(all.getTotalPages());
        System.out.println(all.getTotalElements());
        System.out.println(all.getContent());
    }
    
  • 测试排序

    @Test
    public void testSortTypeSafe() {
        // 使用 Sort 类的静态方法 sort 创建一个 TypedSort<Customer> 实例
        Sort.TypedSort<Customer> sortType = Sort.sort(Customer.class);
    
        // 使用 TypedSort 的 by 方法创建一个排序条件,这里根据 Customer 类的 custId 属性进行排序,并指定为降序
        Sort sort = sortType.by(Customer::getCustId).descending();
    
        // 使用 repository 的 findAll 方法进行查询,并将排序条件传入
        Iterable<Customer> all = repository.findAll(sort);
    
        // 打印查询结果
        System.out.println(all);
    }
    

十一、自定义操作

有时候这些默认的函数并不能够满足需求,则需要我们去编写sql

  1. 使用jpal,加上@Query
    • 查询
      在这里插入图片描述
    • 修改
      在这里插入图片描述
    • 注意:jpql并不支持新增操作。但hibernate提供了一种伪新增的方式
      在这里插入图片描述
  2. 使用原生SQL,需要将nativeQuery设置为true
    在这里插入图片描述
  3. 使用规定方法名(详细官方文档地址:文档地址)
    支持的查询方法主题关键字(前缀)
    • 决定当前方法作用
    • 只支持查询和删除
    1. 查询主题关键字
      在这里插入图片描述
    2. 支持的查询方法谓词关键字和修饰符(决定查询条件)
      在这里插入图片描述
;