Spring Data JPA框架

Spring Data JPA 概述

  • 什么是Spring Data JPA
    Spring Data JPA 是 Spring 基于JPA 规范的基础上封装的⼀套 JPA 应⽤框架,可使开发者⽤极简的
    代码即可实现对数据库的访问和操作。

  • Spring Data 家族


    Spring Data 家族
  • JPA是Java Persistence api:Java持久层api规范。

  • Spring Data JPA 是Spring提供的一个JPA操作的框架。

  • Hibernate是实现了JPA规范的ORM框架。

Spring Data JPA规范和Hibernate之间的关系

Spring Data JPA规范和Hibernate之间的关系

Spring Data JPA应用

数据库准备:


image.png

Spring Data JPA开发步骤梳理

  • 创建工程
    • 导入Meven依赖
    • 配置Spring的配置文件
    • 编写实体类,使用JPA注解配置映射关系
    • 编写Spring Data JPA的Dao层接口
  • 使用Dao层接口完成Dao层开发

Spring Data JPA开发实现

  • Maven依赖
<dependencies>
    <!--单元测试jar-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--spring-data-jpa 需要引⼊的jar,start-->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>2.1.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>javax.el-api</artifactId>
        <version>3.0.1-b04</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.web</groupId>
        <artifactId>javax.el</artifactId>
        <version>2.2.6</version>
    </dependency>
    <!--spring-data-jpa 需要引⼊的jar,end-->
    <!--spring 相关jar,start-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <!--spring对orm框架的⽀持包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <!--spring 相关jar,end-->
    <!--hibernate相关jar包,start-->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.4.0.Final</version>
    </dependency>
    <!--hibernate对jpa的实现jar-->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.4.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.0.Final</version>
    </dependency>
    <!--hibernate相关jar包,end-->
    <!--mysql 数据库驱动jar-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    <!--druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>
    <!--spring-test-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
</dependencies>

  • 配置Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 https://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 https://www.springframework.org/schema/context/spring-context.xsd
 http://www.springframework.org/schema/data/jpa
 https://www.springframework.org/schema/data/jpa/spring-jpa.xsd
">
    <!--对Spring和SpringDataJPA进⾏配置-->
    <!--1、创建数据库连接池druid-->
    <!--引⼊外部资源⽂件-->
    <context:property-placeholder
            location="classpath:jdbc.properties"/>
    <!--第三⽅jar中的bean定义在xml中-->
    <bean id="dataSource"
          class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--2、配置⼀个JPA中⾮常重要的对象,entityManagerFactory
    entityManager类似于mybatis中的SqlSession
    entityManagerFactory类似于Mybatis中的SqlSessionFactory
    -->
    <bean id="entityManagerFactory"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    >
        <!--配置⼀些细节.......-->
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置包扫描(pojo实体类所在的包)-->
        <property name="packagesToScan"
                  value="com.xdf.jpa.pojo"/>
        <!--指定jpa的具体实现,也就是hibernate-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>
        <!--jpa⽅⾔配置,不同的jpa实现对于类似于beginTransaction等细节实现
       起来是不⼀样的,
        所以传⼊JpaDialect具体的实现类-->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
        </property>
        <!--配置具体provider,hibearnte框架的执⾏细节-->
        <property name="jpaVendorAdapter">
            <bean
                    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--定义hibernate框架的⼀些细节-->
                <!--
                配置数据表是否⾃动创建因为我们会建⽴pojo和数据表之间的映射关系
 程序启动时,如果数据表还没有创建,是否要程序给创建⼀下
 -->
                <property name="generateDdl" value="false"/>
                <!--
                指定数据库的类型
                hibernate本身是个dao层框架,可以⽀持多种数据库类型
               的,这⾥就指定本次使⽤的什么数据库
                -->
                <property name="database" value="MYSQL"/>
                <!--
                配置数据库的⽅⾔
                hiberante可以帮助我们拼装sql语句,但是不同的数据库sql
               语法是不同的,所以需要我们注⼊具体的数据库⽅⾔
                -->
                <property name="databasePlatform"
                          value="org.hibernate.dialect.MySQLDialect"/>
                <!--是否显示sql
                操作数据库时,是否打印sql
                -->
                <property name="showSql" value="true"/>
            </bean>
        </property>
    </bean>
    <!--3、引⽤上⾯创建的entityManagerFactory
    <jpa:repositories> 配置jpa的dao层细节
    base-package:指定dao层接⼝所在包
    -->
    <jpa:repositories base-package="com.xdf.jpa.dao" entity-manager-factory-ref="entityManagerFactory"
    transaction-manager-ref="transactionManager"/>
    <!--4、事务管理器配置
    jdbcTemplate/mybatis 使⽤的是DataSourceTransactionManager
 jpa规范:JpaTransactionManager
 -->
    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!--5、声明式事务配置-->
    <!--
    <tx:annotation-driven/>
    -->
    <!--6、配置spring包扫描-->
    <context:component-scan base-package="com.xdf.jpa"/>
</beans>
  • 编写实体类User,使用注解配置映射关系
package com.xdf.jpa.pojo;

import javax.persistence.*;

/**
 * @author xdf
 * @version 1.0
 * @date Create in 14:38 2021/6/22
 * @description 用户实体
 * @modifiedBy
 */
@Entity
@Table(name = "tb_user")
public class User {
    /**
     * 主键
     * @Id 标记为主键
     * @GeneratedValue 标记主键生成策略
     *
     * 常用的生成策略:
     *  GenerationType.IDENTITY 依赖数据库的主键自增  mysql
     *  GenerationType.SEQUENCE 依赖序列来产生主键    Oracle
     *
     * @Column 实体属性和数据库字段映射
     */
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    /**
     * 用户名
     *
     */
    @Column(name = "username")
    private String username;
    /**
     * 密码
     */
    @Column(name = "address")
    private String address;
    /**
     * 手机号
     */
    @Column(name = "phone")
    private String phone;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

  • 编写Dao接口
package com.xdf.jpa.dao;

import com.xdf.jpa.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/**
 * @author xdf
 * @version 1.0
 * @date Create in 14:47 2021/6/22
 * @description User的持久层接口
 *  继承JpaRepository接口,可以不编写代码直接实现基本的curd操作,两个泛型分别代表:实体类类型,主键类型。
 *  继承JpaSpecificationExecutor接口,可以不编写代码实现复杂查询,泛型为实体类类型。
 * @modifiedBy
 */
public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {


    /**
     * 自定义查询,使用JPQL语法实现查询操作
     * @param id 主键id
     * @param username username
     * @return User实体集合
     */
    @Query("from User where id=?1 and username=?2")
    List<User> findByJpql(Long id,String username);

    /**
     * 自定义查询,使用sql语法实现自定义查询
     * 需要设置nativeQuery = true
     * @param username 用户名
     * @param address 地址
     * @return 用户实体集合
     */
    @Query(value = "select * from tb_user u where u.username=?1 and u.address=?2",nativeQuery = true)
    List<User> findBySql(String username,String address);


    /**
     * 按方法命名规则来定义查询
     * 查询条件写在By后面,多个查询条件用And连接,查询方式根在属性名之后
     * @param username 用户名
     * @param address 地址
     * @return 用户实体集合
     */
    List<User> findByUsernameAndAddressLike(String username,String address);

}
  • 测试Dao层功能:
import com.xdf.jpa.dao.UserDao;
import com.xdf.jpa.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

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

/**
 * @author xdf
 * @version 1.0
 * @date Create in 15:00 2021/6/22
 * @description UserDao测试类
 * @modifiedBy
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class UserDaoTest {

    /**
     * IOC注入dao
     */
    @Autowired
    private UserDao userDao;


    /**
     * 基础的查询操作
     */
    @Test
    public void testFindById() {
        Optional<User> userOptional = userDao.findById(1L);
        if (userOptional.isPresent()) {
            User user = userOptional.get();
            System.out.println(user);
        }
    }

    @Test
    public void testFindOne() {
        User user = new User();
        user.setId(1L);
        user.setUsername("user1");
        Example<User> userExample = Example.of(user);
        Optional<User> userOptional = userDao.findOne(userExample);
        userOptional.ifPresent(System.out::println);
    }


    @Test
    public void testSave() {

        // 新增和更新都使用save方法,有主键是更新,无主键是插入
        User user = new User();
        user.setUsername("zhangsan");
        user.setAddress("上海外滩");
        user.setPhone("123");

        User save = userDao.save(user);
        System.out.println(save);
    }

    @Test
    public void testDelete() {
        userDao.deleteById(2L);
    }

    @Test
    public void testFindAll() {
        List<User> all = userDao.findAll();
        System.out.println(Arrays.toString(all.toArray()));
    }

    @Test
    public void testSort() {
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        List<User> all = userDao.findAll(sort);
        System.out.println(Arrays.toString(all.toArray()));
    }

    @Test
    public void testPage() {
        // 起始页,从0开始;页大小
        Pageable pageable = PageRequest.of(0, 1);
        Page<User> all = userDao.findAll(pageable);
        System.out.println(all);
    }


    @Test
    public void testJpql() {
        List<User> user1 = userDao.findByJpql(1L, "user1");
        System.out.println(Arrays.toString(user1.toArray()));
    }

    @Test
    public void testSql() {
        List<User> bySql = userDao.findBySql("user1", "上海虹桥");
        System.out.println(Arrays.toString(bySql.toArray()));
    }

    @Test
    public void testMethodName() {
        List<User> byUsernameAndAddressLike = userDao.findByUsernameAndAddressLike("user1", "上海%");
        System.out.println(Arrays.toString(byUsernameAndAddressLike.toArray()));
    }


    /**
     * 动态条件封装
     * 匿名内部类
     *
     * toPredicate:动态条件组装
     *
     */
    @Test
    public void testSpecification() {
        Specification<User> specification = new Specification<User>() {

            @Override
            public Specification<User> and(Specification<User> other) {
                return null;
            }

            @Override
            public Specification<User> or(Specification<User> other) {
                return null;
            }

            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> name = root.get("username");
                Predicate predicate = criteriaBuilder.equal(name, "user1");
                return predicate;
            }
        };
        Optional<User> userOptional = userDao.findOne(specification);
        User user = userOptional.get();
        System.out.println(user);
    }

    @Test
    public void testSpecificationMultiCon() {
        Specification<User> specification = new Specification<User>() {

            @Override
            public Specification<User> and(Specification<User> other) {
                return null;
            }

            @Override
            public Specification<User> or(Specification<User> other) {
                return null;
            }

            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> username = root.get("username");
                Path<Object> address = root.get("address");
                Predicate predicate = criteriaBuilder.equal(username, "user1");
                Predicate like = criteriaBuilder.like(address.as(String.class), "上海%");
                return criteriaBuilder.and(predicate, like);
            }
        };

        Optional<User> user = userDao.findOne(specification);
        System.out.println(user);
    }
}

Spring Data JPA执行过程源码分析

代理对象怎么产生,过程怎么样?

JPA的Dao层接口也是通过动态代理来实现的。
在Spring容器中,bean都是在refresh方法中初始化的。


image.png

refresh方法的finishBeanFactoryInitializationpreInstantiateSingletons方法中打断点观察:

image.png

发现userDao在Ioc容器中注册为一个FactoryBean:JpaRepositoryFactoryBean


image.png

image.png

FactoryBean的getObject方法可以获取真正的bean 。

这个FactoryBean是什么时候放入到容器中去的?
因为这个beanDefinition是在getMergedLocalBeanDefinition中获取到的,跟进去看

image.png

先从mergedBeanDefinitions集合中去查询,没有找到。那么就会进入getMergedBeanDefinition

getMergedBeanDefinition中发现一个向集合mergedBeanDefinitions中put值的代码:


这个地方应该是个关键代码。
断点打到这里,发现


这里put进入的BeanDefinition正好就是JpaRepositoryFactoryBean,而传入的bd也是JpaRepositoryFactoryBeanmbd是跟进bd创建出来的。那么我找找bd是什么时候传入的。

回到上面,发现是在this.getBeanDefinition(beanName)获取到的。

beanDefinitionMap中取到的userDao的bean定义。
我们再跟踪beanDefinitionMap的put方法,看是在什么位置设置到集合中去的
我们发现在registerBeanDefinition中对beanDefinitionMap进行了put操作。因此在registerBeanDefinition打断点观察


发现在这里进行BeanDefinition注册的时候,传入的就是一个FactoryBean类。
根据调用栈找到RepositoryConfigurationDelegate中的registerRepositoriesIn构建BeanDefinition

依次跟进去返现,被直接指定为一个JpaRepositoryFactoryBean类。

在扫描Dao并注册到BeanDefinition中的时候,固定注册JpaRepositoryFactoryBean到容器中。

getObject方法到底返回什么bean?
JpaRepositoryFactoryBean的getObject是在父类中定义的


getObject是从一个集合repository中获取的。
我们看这个集合是在哪了被赋值的?


afterPropertiesSet方法中,发现集合被赋值

afterPropertiesSet是InitializingBean的钩子方法,在Spring的生命周期中被调用。

getRepositoryInformation方法中找到了要代理的对象是SimpleJpaRespository

创建代理对象工厂进行代理。



创建代理

代理对象类型SimpleRepository有什么特别的?


SimpleRepository实现了JpaRepository<T, ID>, JpaSpecificationExecutor<T>两个接口


调用Jpa底层实现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容