Spring Data JPA快速入门
  ILwIY8Berufg 2023年11月02日 82 0


一、Spring Data Jpa 简介

1.JPA

JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范(JSR 338,这些接口所在包为javax.persistence,详细内容可参考:https://github.com/javaee/jpa-spec) JPA的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。总的来说,JPA包括以下3方面的技术:

  • ORM映射元数据: 支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系
  • API: 操作实体对象来执行CRUD操作
  • 查询语言: 通过面向对象而非面向数据库的查询语言(JPQL)查询数据,避免程序的SQL语句紧密耦合

JPA架构

Spring Data JPA快速入门_spring

2.Spring Data Jpa

https://spring.io/projects/spring-data-jpa#overview

Spring Data Jpa官方解释

Spring Data JPA快速入门_User_02

Spring Data JPA是Spring Data家族的一部分,可以轻松实现基于JPA的存储库。 此模块处理对基于JPA的数据访问层的增强支持。 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。

在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦。 必须编写太多样板代码来执行简单查询以及执行分页和审计。 Spring Data JPA旨在通过减少实际需要的工作量来显著改善数据访问层的实现。 作为开发人员,您编写repository接口,包括自定义查找器方法,Spring将自动提供实现。

Spring Data生态

Spring Data JPA快速入门_JPA_03

3.Jpa、Hibernate、Spring Data Jpa三者之间的关系

JPA是ORM规范,Hibernate、TopLink等是JPA规范的具体实现,这样的好处是开发者可以面向JPA规范进行持久层的开发,而底层的实现则是可以切换的。Spring Data Jpa则是在JPA之上添加另一层抽象(Repository层的实现),极大地简化持久层开发及ORM框架切换的成本。

Jpa、Hibernate、Spring Data Jpa三者之间的关系

Spring Data JPA快速入门_JPA_04

二、入门案例

项目结构

Spring Data JPA快速入门_User_05

build.gradle

dependencies {
    compile group: 'junit', name: 'junit', version: '4.12'
    compile group: 'org.springframework', name: 'spring-test', version: '5.1.8.RELEASE'
    compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.3.Final'
    compile group: 'org.hibernate', name: 'hibernate-c3p0', version: '5.4.3.Final'
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
    compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.1.9.RELEASE'
    compileOnly 'org.projectlombok:lombok:1.18.4'
    annotationProcessor 'org.projectlombok:lombok:1.18.4'
}

user.sql

INSERT INTO `user` VALUES (1, 'Andrew', 22, '湖北');
INSERT INTO `user` VALUES (2, 'Jack', 22, '湖北');
INSERT INTO `user` VALUES (3, 'Andy', 18, '湖南');
INSERT INTO `user` VALUES (4, 'Tom', 18, '河北');
  • 配置spring相关
  • 数据源信息
  • jpa的实现方式
  • 配置要用到的实体类
  • 配置jpa实现方的配置信息
  • 配置事务管理器
  • 声明式事务

spring_data_jpa.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jpa="http://www.springframework.org/schema/data/jpa"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--spring-->
    <!--配置spring的注解扫描-->
    <context:component-scan base-package="com.daniel"/>
    <!--spring data jpa-->
    <!--整合spring data jpa-->
    <jpa:repositories base-package="com.daniel.dao" entity-manager-factory-ref="entityManagerFactory"
                      transaction-manager-ref="transactionManager"/>
    <!--创建实体管理器工厂,交给spring管理-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置要扫描的包,实体所在包-->
        <property name="packagesToScan" value="com.daniel.entity"/>
        <!--配置jpa的实现方法2-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>
        <!--jpa的实现方法的配置-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--数据库类型-->
                <property name="database" value="MYSQL"/>
                <!--控制台显示sql语句-->
                <property name="showSql" value="true"/>
                <!--是否自动创建数据库表-->
                <property name="generateDdl" value="true"/>
                <!--数据库方言-->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
            </bean>
        </property>
        <!--自动生成数据库表-->
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>
    <!--数据源配置项-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa?characterEncoding=utf8"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
</beans>

User.java

package com.daniel.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * @Author Daniel
 * @Description 实体类
 **/
@Data
@Entity
@Table(name = "user")
public class User {
    @Id
    // 自增
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    @Column(name = "name")
    private String name;
    @Column(name = "age")
    private Integer age;
    @Column(name = "address")
    private String address;
}

@Query

  • value:数据库操作语句
  • nativeQuery:是否是原生查询,默认false,即默认使用jpql查询

@Modifying:声明当前是一个更新操作,需要修改数据库数据。

  • 只能用于void或int/Integer的返回类型
  • 因为需要修改数据库数据,未防止修改失败造成未知后果,需要搭配事务管理来是使用

@Transactional:添加事务管理支持

  • 一般需要设置rollbackFor或者noRollbackFor,来表示什么情况下进行事务回滚

@Rollback:是否可以回滚,默认true

方法命名规则查询

spring data jpa制定了一些约定,如果按照这些约定来定义方法名,则会自动解析出sql语句。

findBy + 属性名 + 查询方式 + (And|Or) + 属性名 + 查询方式...

查询方式

方法命名

sql where字句

And

findByNameAndPwd

where name= ? and pwd =?

Or

findByNameOrSex

where name= ? or sex=?

Is,Equals

findById,findByIdEquals

where id= ?

Between

findByIdBetween

where id between ? and ?

LessThan

findByIdLessThan

where id < ?

LessThanEquals

findByIdLessThanEquals

where id <= ?

GreaterThan

findByIdGreaterThan

where id > ?

GreaterThanEquals

findByIdGreaterThanEquals

where id > = ?

After

findByIdAfter

where id > ?

Before

findByIdBefore

where id < ?

IsNull

findByNameIsNull

where name is null

isNotNull,NotNull

findByNameNotNull

where name is not null

Like

findByNameLike

where name like ?

NotLike

findByNameNotLike

where name not like ?

StartingWith

findByNameStartingWith

where name like '?%'

EndingWith

findByNameEndingWith

where name like '%?'

Containing

findByNameContaining

where name like '%?%'

OrderBy

findByIdOrderByXDesc

where id=? order by x desc

Not

findByNameNot

where name <> ?

In

findByIdIn(Collection<?> c)

where id in (?)

NotIn

findByIdNotIn(Collection<?> c)

where id not in (?)

True

findByAaaTue

where aaa = true

False

findByAaaFalse

where aaa = false

IgnoreCase

findByNameIgnoreCase

where UPPER(name)=UPPER(?)

UserDao.java

package com.daniel.dao;

import com.daniel.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

/**
 * @Author Daniel
 * @Description Dao层
 * 继承两个接口
 * 1.JpaRepository:封装了增删改查分页排序等基本操作
 * 2.JpaSpecificationExecutor:封装了标准查询
 **/
public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
    /*    // 原生sql语句查询
    @Query(value = "query * from user where name = :name and age = :age", nativeQuery = true)
    public User findUserByName(@Param("name") String userName, @Param("age") int age);*/

    // jpql查询
    @Query(value = "from User where name = :name and age = :age")
    public User findUserByName(@Param("name") String userName, @Param("age") int age);

    // jpql更新
    @Query(value = "update User set name = :name where id = :id")
    @Modifying
    public Integer updateNameById(@Param("id") int id, @Param("name") String userName);

    // 方法命名规则查询
    public User findByName(String name);

    public User findByNameLike(String name);

    public User findByNameLikeAndAge(String name, int age);

    public List<User> findByIdBetween(int idMin, int idMax);
}
1.增删改查

CURDTest.java

package com.daniel.test;

import com.daniel.dao.UserDao;
import com.daniel.entity.User;
import org.junit.runner.RunWith;
import org.junit.Test;
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;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * @Author Daniel
 * @Description 数据库操作
 **/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring_data_jpa.xml")
public class CURDTest {
    @Autowired
    private UserDao userDao;

    @Test
    public void save() {
        User user = new User();
        user.setName("Daniel");
        user.setAge(22);
        user.setAddress("湖北");
        User userResult = userDao.save(user);
        System.out.println(userResult);
    }

    @Test
    public void delete() {
        userDao.deleteById(5);
    }

    @Test
    /*
    如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,
    加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚
     */
    @Transactional(rollbackFor = Exception.class)
//    @Rollback(value = false)//发生异常不回滚
    public void update() {
        User user = userDao.findUserByName("Jack", 22);
        System.out.println(user);
        userDao.updateNameById(user.getId(), "Jack_2");
        // 抛出异常
        int exception = 1 / 0;
        userDao.updateNameById(user.getId(), "Jack_3");
    }


    @Test
    @Transactional
    public void query() {
        // 统计
        long count = userDao.count();
        System.out.println("count:" + count);
        // exists系列方法,判断数据库中是否存在
        boolean flag = userDao.existsById(5);
        System.out.println("exists系列方法:" + flag);
        /*
        1.findById得到的是一个Optional(jdk1.8的新特性之一),之后用.get()就可以获取相应的对象
        2.getOne得到的是对应得实体类对象,如果需要在单元测试中使用此方法,要加上事务支持注解,
        即@Transactional,才可以正常使用,否则可能会抛出
        org.hibernate.LazyInitializationException: could not initialize prox
         */
        // find系列方法,立即加载,在一开始进行操作的时候就从数据库查询数据
        Optional<User> user1 = userDao.findById(3);
        System.out.println("find系列方法:" + user1.get());
        // getOne,延迟加载,返回的是一个动态代理对象,调用时才从数据库查询数据
        User user2 = userDao.getOne(3);
        System.out.println("getOne:" + user2);
    }


    // 使用方法命名规则查询
    @Test
    public void definedQuery() {
        User user1 = userDao.findByName("Andrew");
        System.out.println(user1);

        User user2 = userDao.findByNameLike("t%");
        System.out.println(user2);

        User user3 = userDao.findByNameLikeAndAge("j%", 22);
        System.out.println(user3);

        List<User> users = userDao.findByIdBetween(1, 3);
        users.forEach(new Consumer<User>() {
            @Override
            public void accept(User user) {
                System.out.println(user);
            }
        });
    }
}
2.标准查询

这里是JpaSpecificationExecuto的源码,为了便于阅读,我删除了注释,有的方法已经在上面的代码中演示过。可以看到,这5个方法有个共同点,接收一个Specification参数。

package org.springframework.data.jpa.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;


public interface JpaSpecificationExecutor<T> {
    //查询一个
    Optional<T> findOne(@Nullable Specification<T> spec);
    //查询全部
    List<T> findAll(@Nullable Specification<T> spec);
    //查询全部  提供分页功能
    Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
    //查询全部,提供排序功能
    List<T> findAll(@Nullable Specification<T> spec, Sort sort);
    //统计
    long count(@Nullable Specification<T> spec);
}

Specification是对JPA规范中Root、CriteriaQuery、CriteriaBuilder的一层封装,用于构建过滤条件。实例化Specification需要实现它的toPerdicate方法

这里介绍一个例子,查询表中年龄大于18的湖北人

SpecificationTest.java

package com.daniel.test;

import com.daniel.dao.UserDao;
import com.daniel.entity.User;
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.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.function.Consumer;

/**
 * @Author Daniel
 * @Description 标准查询
 **/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring_data_jpa.xml")
public class SpecificationTest {
    @Autowired
    private UserDao userDao;

    @Test
    public void get() {
        Specification<User> specification = new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                // 构造两个过滤条件
                Predicate namePredicate = criteriaBuilder.like(root.get("address"), "湖北%");
                // >=18
                Predicate agePredicate = criteriaBuilder.ge(root.get("age"), 18);
                // 组合过滤条件
                Predicate predicate = criteriaBuilder.and(namePredicate, agePredicate);
                return predicate;
            }
        };
        // 根据id排序
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        // 分页(pageIndex,pageSize,sort)
        Pageable pageable = PageRequest.of(0, 10, sort);
        Page<User> users = userDao.findAll(specification, pageable);
        users.forEach(new Consumer<User>() {
            @Override
            public void accept(User user) {
                System.out.println(user);
            }
        });
    }
}
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
ILwIY8Berufg