Spring Boot [组件学习-Spring Data JPA]

夏建木
2023-12-01

导读:

在上篇文章中对Spring MVC常用的一些注解做了简要的说明,在这篇文章中主要对Spring Data JPA 做一个简要的说明,并附有一个简单的例子,可以体会到Spring Data JPA 的强大之处。

Spring Data JPA 与JPA的关系:

JPA是什么?

JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibernate,TopLink,JDO等ORM框架各自为营的局面。值得注意的是,JPA是在充分吸收了现有Hibernate,TopLink,JDO等ORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。
JPA定义了在对数据库中的对象处理查询和事务运行时的EntityManager的API。JPA定义一个对象级查询语言,JPQL,如果学习过Hibernate的话你可以把它看做Hibernate中的Hql语句,以允许从所述数据库中的对象的查询。
下面是JPA常用的一些解决方案:

EclipseLink (Eclipse)
Hibernate (RedHat)
Open JPA (Apache)
DataNucleus
Ebean (SourceForge)
TopLink Essentials (Glassfish)
TopLink (Oracle)
Kodo (Oracle)

Spring Data JPA:

你可以把它做做事JPA的超集 类似于 C与C++的关系,Spring Data JPA可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。

也就是说Spring Data JPA 在JPA的基础上提供了更高层次的抽象,帮助我们实现了许多常用的对数据库的操作,从而提高开发效率。

Spring Data JPA 的一些常用方法

在了解Spring Data JPA之前我们首先看一下它为我们提供了那些便利。

1.简单查询:

面对简单的查询时可以通过解析方法名创建查询或使用使用 @Query 创建查询语句

比如现在我们有个方法叫做 User findByName(String name),我们可以很清楚的明白它的意思,但有没有办法让ORM框架根据方法名帮助我们推断出Sql来呢?在Spring Data JPA中这是可以的,我们只要将我们的接口继承 org.springframework.data.repository.Repository <T, ID extends java.io.Serializable>,或者使用@RepositoryDefinition 注解进行修饰。

解析方法名创建查询 示例:

public interface UserRepository extends Repository<User, Long> {
    User findByName(String name)
} 
@RepositoryDefinition(domainClass = User.class, idClass = Long.class) 
public interface UserRepository{
    User findByName(String name)
} 

@Query注解查询 示例:

public interface UserRepository extends Repository<User, Long> {
    //对于更新操作需要添加@Modifying
    @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);
} 

当然还不止这些Spring Data JPA 还未我们提供了几个常用的Repository:

  • Repository: 仅仅是一个标识,没有任何方法,方便Spring自动扫描识别

  • CrudRepository: 继承Repository,实现了一组CRUD相关的方法

  • PagingAndSortingRepository: 继承CrudRepository,实现了一组分页排序相关的方法

  • JpaRepository: 继承PagingAndSortingRepository,实现一组JPA规范相关的方法

通过继承它们可以获得更强大的功能,当然它们之所能够运行是因为有了默认的实现类:

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>,
        JpaSpecificationExecutor<T> {
   
}

在查询的时候,通常需要同时根据多个属性进行查询,Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:

  • And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);

  • Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);

  • Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);

  • LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max);

  • GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min);

  • IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull();

  • IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();

  • NotNull --- 与 IsNotNull 等价;

  • Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user);

  • NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);

  • OrderBy --- 等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);

  • Not --- 等价于 SQL 中的 "! =",比如 findByUsernameNot(String user);

  • In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

  • NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

当然面对比较复杂的查询时,它们就显得无力了,如果在面对比较复杂的查询时,想要有自己的实现方法,只需要通过一个XXXImpl结尾的实现类即可使用我们自己的实现方法(比如UserRepository接口的同一个包下面建立一个普通类UserRepositoryImpl来表示该类的实现类)。

了解更多:使用 Spring Data JPA 简化 JPA 开发 - IBM

2.复杂的查询

面对复杂的查询时可以使用本地查询或JPQL或使用JPA的动态接口(JpaSpecificationExecutor)

在面对负责的条件查询时,需要有条件判断这个时候方法名推断就显得不够强大了,比如当这里有个简单的例子:“我们要根据用户的年龄,地址,性别 查询用户信息时”, 在考虑可能存在条件为空的情况下,在使用方法名推断时我们需要提供8个相应的方法,当然如果使用流的话有一个findAll方法即可,但是这无疑增大了数据库与发射生成对象压力所以并不推荐这种方式,那么在这种情况下如果可以使用判断进行条件拼接的话,便可以将查询的方法缩减到一个 这便是以上三种方式的优点(一般情况下不推荐 本地查询)。

JPQL示例:

DO:

@Entity
public class User extends AbstractPersistable<Long> {

    private String name;

    private Integer age;

    private String AddressCode;

    @Enumerated(EnumType.ORDINAL)
    private Sex sex;
    
    /**省略get/set**/
}

DAO:

public interface UserCustomRepository {

    Page<User> search(Integer age, String AddressCode, User.Sex sex, Pageable pageRequest);

}
public interface UserRepository extends CrudRepository<User, Long>, UserCustomRepository {

}
public class UserRepositoryImpl implements UserCustomRepository {

    @PersistenceContext
    private EntityManager em;

    @Override
    public Page<User> search(Integer age, String AddressCode, User.Sex sex, Pageable pageRequest) {
        String querySql = "select t ";
        String countSql = "select count(t) ";
        StringBuffer sqlBuffer = new StringBuffer("from User t where 1=1");

        if (null != age) {
            sqlBuffer.append(" and t.age = :age");
        }
        if (null != AddressCode) {
            sqlBuffer.append(" and t.AddressCode = :address");
        }
        if (null != sex) {
            sqlBuffer.append(" and t.sex = :sex");
        }

        querySql += sqlBuffer.toString();
        countSql += sqlBuffer.toString();

        Query dataQuery = em.createQuery(querySql);
        Query countQuery = em.createQuery(countSql);

        if (null != age) {
            dataQuery.setParameter("age", age);
            countQuery.setParameter("age", age);
        }
        if (null != AddressCode) {
            dataQuery.setParameter("address", AddressCode);
            countQuery.setParameter("address", AddressCode);
        }
        if (null != sex) {
            dataQuery.setParameter("sex", sex);
            countQuery.setParameter("sex", sex);
        }

        Page<User> page = (pageRequest == null ? new PageImpl(dataQuery.getResultList()) : this.readPage(dataQuery, countQuery, pageRequest));
        return page;
    }

    private Page<User> readPage(Query dataQuery, Query countQuery, Pageable pageable) {
        dataQuery.setFirstResult(pageable.getOffset());
        dataQuery.setMaxResults(pageable.getPageSize());
        long totalSize = (long) countQuery.getSingleResult();
        List<User> content = totalSize > (long) pageable.getOffset() ? dataQuery.getResultList() : Collections.emptyList();
        return new PageImpl(content, pageable, totalSize);
    }


}

Test:

@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringBootJpaApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void contextLoads() {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            String name = "name" + i;
            Integer age = 19 + (i + 1) % 5;
            String addressCode = "address";
            User.Sex sex = i % 2 == 0 ? User.Sex.MAN : User.Sex.WOMAN;
            User user = new User(name, age, addressCode, sex);
            System.out.println(JSON.toJSON(user));
            userList.add(user);
        }
        userRepository.save(userList);
    }

    @Test
    public void search() {
        int page = 0;
        int size = 2;
        Pageable pageable = new PageRequest(page, size);
        Page<User> pageUser = userRepository.search(null, null, null, pageable);
        pageUser = userRepository.search(19, null, User.Sex.WOMAN, pageable);
        System.out.println(JSON.toJSON(pageUser));
    }

}

JPA的动态接口(JpaSpecificationExecutor):

只做了简单的修改
DO:

public interface UserRepository extends CrudRepository<User, Long>, UserCustomRepository, JpaSpecificationExecutor<User> {

}

Test:

@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringBootJpaApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void contextLoads() {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            String name = "name" + i;
            Integer age = 19 + (i + 1) % 5;
            String addressCode = "address";
            User.Sex sex = i % 2 == 0 ? User.Sex.MAN : User.Sex.WOMAN;
            User user = new User(name, age, addressCode, sex);
            System.out.println(JSON.toJSON(user));
            userList.add(user);
        }
        userRepository.save(userList);
    }

    @Test
    public void search() {
        int page = 0;
        int size = 2;
        Pageable pageable = new PageRequest(page, size);
        Page<User> pageUser = userRepository.search(null, null, null, pageable);
//        System.out.println(JSON.toJSON(pageUser));
        pageUser = userRepository.search(19, null, User.Sex.WOMAN, pageable);
        System.out.println(JSON.toJSON(pageUser));
//        pageUser = userRepository.search(null, null, null, pageable);
//        System.out.println(JSON.toJSON(pageUser));
//        pageUser = userRepository.search(null, null, null, pageable);
//        System.out.println(JSON.toJSON(pageUser));
    }

    @Test
    public void findAll() {
        int page = 0;
        int size = 2;
        Pageable pageable = new PageRequest(page, size);
        Page<User> pageUser = search(19, null, User.Sex.WOMAN, pageable);
        System.out.println(JSON.toJSON(pageUser));
    }

    public Page<User> search(final Integer age, final String AddressCode, final User.Sex sex, Pageable pageRequest) {
/*      第一种查询方式
        Specification<User> specification = (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            //创建查询条件
            List<Predicate> predicates = new ArrayList<>();
            if (age != null) {
                predicates.add(cb.equal(root.<Integer>get("age"), age));
            }
            if (AddressCode != null) {
                predicates.add(cb.equal(root.get("AddressCode").as(String.class), AddressCode));
            }
            if (sex != null) {
                predicates.add(cb.equal(root.get("sex").as(User.Sex.class), sex));
            }
            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
        };

*/
        //第二种
        Specification<User> specification = (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            //创建查询条件
            List<Predicate> predicates = new ArrayList<>();
            if (age != null) {
                predicates.add(cb.equal(root.<Integer>get("age"), age));
            }
            if (AddressCode != null) {
                predicates.add(cb.equal(root.get("AddressCode").as(String.class), AddressCode));
            }
            if (sex != null) {
                predicates.add(cb.equal(root.get("sex").as(User.Sex.class), sex));
            }
            Predicate[] assetTradingArray = new Predicate[predicates.size()];
            predicates.toArray(assetTradingArray);
            query.where(assetTradingArray);//这种方式使用JPA的API设置了查询条件,所以不需要再返回查询条件Predicate给Spring Data Jpa,故最后return null;即可。
            return null;
        };
        return userRepository.findAll(specification, pageRequest);
    }

}

了解更多:纯干货,Spring-data-jpa详解,全方位介绍。

Spring Data JPA 在Spring-Boot中的使用:

第一步将相关依赖导入POM文件:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--tool-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.23</version>
        </dependency>
    </dependencies>

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

第二步配置yml文件(使用了内存数据库因此没有配数据源):

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        hbm2ddl:
          auto: update

第三步编写main函数:

@SpringBootApplication
public class SpringBootJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJpaApplication.class, args);
    }
}

第四步编写相关的Repository:

public interface UserRepository extends CrudRepository<User, Long>, UserCustomRepository, JpaSpecificationExecutor<User> {

}

相关逻辑编写->运行->测试->发布

学习资料收集:

教程:

博客介绍:

视频:

结语:

Spring Data JPA 的解析方法名创建查询非常强大,只是声明持久层的接口,相关的查询它就已经帮你去做了,让我想起一个问题框架的终点在哪?是人工智能吗?

参考文档:

全面阐释和精彩总结JPA
Spring Data 系列(二) Spring+JPA入门(集成Hibernate)
Spring Data概念【从零开始学Spring Boot】

 类似资料: