likes
comments
collection
share

【Spring Data JPA 系列】05、Spring Data JPA

作者站长头像
站长
· 阅读数 21

5、Spring data Jpa

5.1、什么是spring data jpa?

官网:docs.spring.io/spring-data…

Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.

Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.

SpringDataJPA 是更大的 SpringData 系列的一部分,它使得基于 JPA 的存储库的实现更加容易。该模块处理对基于 JPA 的数据访问层(repositories)的增强支持。它使得构建使用数据访问技术的 Spring 驱动的应用程序变得更加容易。

实现应用程序的数据访问层已经很麻烦了。为了执行简单的查询以及执行分页和审计,必须编写太多的样板代码。Spring Data JPA 旨在通过将工作量减少到实际需要的数量来显著改进数据访问层的实现。作为开发人员,您编写存储库接口,包括自定义查找器(finder)方法,Spring 将自动提供实现。

简单理解:

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

Spring data Jpa 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM (如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦!

5.2、特性(Features)

  • Sophisticated support to build repositories based on Spring and JPA
  • Support for Querydsl predicates and thus type-safe JPA queries
  • Transparent auditing of domain class
  • Pagination support, dynamic query execution, ability to integrate custom data access code
  • Validation of @Query annotated queries at bootstrap time
  • Support for XML based entity mapping
  • JavaConfig based repository configuration by introducing @EnableJpaRepositories.

SpringData JPA 极大简化了数据库访问层代码。如何简化呢?使用springdataJpa,我们的dao层只需要写接口,就自动有了增删查改、分页查询等方法。

【Spring Data JPA 系列】05、Spring Data JPA

这里Java application通过dao层进行访问,其实就是拿到了Jdk的动态代理!

5.3、Spring Data JPA实例

我们来实现一个基于Spring Data JPA的示例感受一下和之前单独使用的区别:

pom.xml依赖

1、最好在父maven项目中设置spring data统一版本管理依赖:因为不同的spring data子项目发布时间版本不一致,你自己维护很麻烦,这样不同的spring data子项目能保证是统一版本!

定义统一的springdata子项目的版本,在总的pom.xml进行添加!

     <!-- 统一管理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>

2、然后在我们的子项目中进行导入使用!

pom.xml

 <dependencies>
     <dependency>
         <groupId>org.springframework.data</groupId>
         <artifactId>spring-data-jpa</artifactId>
     </dependency>
     <!--junit-->
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
     </dependency>
     <!--hibernate对jpa的支持包!-->
     <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-entitymanager</artifactId>
         <version>6.0.0.Alpha6</version>
     </dependency>
     <!--mysql and MariaDB-->
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.27</version>
     </dependency>
     <!--连接池-->
     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid</artifactId>
         <version>1.2.8</version>
     </dependency>
     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-test</artifactId>
         <version>5.3.15</version>
     </dependency>
 </dependencies>

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

02-springdata-jpa

3、创建实体类!

 package com.yykk.pojo;
 
 import javax.persistence.*;
 
 /**
  * @author yykk
  */
 @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)
     private Long id;
 
     @Column(name = "cust_name")
     private String custName;//客户名称
 
     @Column(name = "cust_source")
     private String custSource;//客户来源
 
     @Column(name = "cust_level")
     private String custLevel;//客户级别
 
     @Column(name = "cust_industry")
     private String custIndustry;//客户所属行业
 
     @Column(name = "cust_phone")
     private String custPhone;//客户的联系方式
 
     @Column(name = "cust_address")
     private String custAddress;//客户地址
 
 
     public Long getId() {
         return id;
     }
 
     public void setId(Long id) {
         this.id = id;
     }
 
     public String getCustName() {
         return custName;
     }
 
     public void setCustName(String custName) {
         this.custName = custName;
     }
 
     public String getCustSource() {
         return custSource;
     }
 
     public void setCustSource(String custSource) {
         this.custSource = custSource;
     }
 
     public String getCustLevel() {
         return custLevel;
     }
 
     public void setCustLevel(String custLevel) {
         this.custLevel = custLevel;
     }
 
     public String getCustIndustry() {
         return custIndustry;
     }
 
     public void setCustIndustry(String custIndustry) {
         this.custIndustry = custIndustry;
     }
 
     public String getCustPhone() {
         return custPhone;
     }
 
     public void setCustPhone(String custPhone) {
         this.custPhone = custPhone;
     }
 
     public String getCustAddress() {
         return custAddress;
     }
 
     public void setCustAddress(String custAddress) {
         this.custAddress = custAddress;
     }
 
     @Override
     public String toString() {
         return "Customer{" +
                 "id=" + id +
                 ", custName='" + custName + ''' +
                 ", custSource='" + custSource + ''' +
                 ", custLevel='" + custLevel + ''' +
                 ", custIndustry='" + custIndustry + ''' +
                 ", custPhone='" + custPhone + ''' +
                 ", custAddress='" + custAddress + ''' +
                 '}';
     }
 }

4、配置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.yykk.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">
                 <!--生成数据库表-->
                 <property name="showSql" value="true"></property>
                 <property name="generateDdl" value="true"></property>
             </bean>
         </property>
         <!--设置实体类的包-->
         <property name="packagesToScan" value="com.yykk.pojo"></property>
         <property name="dataSource" ref="dataSource"></property>
     </bean>
 
     <!--数据源-->
     <bean class="com.alibaba.druid.pool.DruidDataSource" name="dataSource">
         <property name="username" value="root"/>
         <property name="password" value="123456"/>
         <property name="url" value="jdbc:mysql://localhost:3306/spring_data?characterEncoding=UTF-8"/>  <!--这里要注意:在我配置了其他属性之后,就加载不出来了!-->
         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
     </bean>
 
     <!--声明式事务-->
     <bean class="org.springframework.orm.jpa.JpaTransactionManager" name="transactionManager">
         <property name="entityManagerFactory" ref="entityManagerFactory"></property>
     </bean>
 
     <!--启动注解方式的声明式事务-->
     <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
 
 </beans>

5、创建repository/repositories 接口!

 package com.yykk.repositories;
 
 import com.yykk.pojo.Customer;
 import org.springframework.data.repository.CrudRepository;
 
 public interface CustomerRepositories extends CrudRepository<Customer,Long> {
 }

6、测试!

 import com.yykk.pojo.Customer;
 import com.yykk.repositories.CustomerRepositories;
 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 java.util.Optional;
 
 @ContextConfiguration("/springdata.xml")
 @RunWith(SpringJUnit4ClassRunner.class)
 public class SpringdataJpaTest {
 
     @Autowired
     CustomerRepositories repositories;
 
     @Test
     public void test_Query() {
         Optional<Customer> id = repositories.findById(3L);
         System.out.println(id.get());
     }
 
     @Test
     public void test_Insert() {
         Customer customer = new Customer();
         customer.setCustName("李四");
         customer.setCustAddress("南京");
         repositories.save(customer);
     }
 
     @Test
     public void test_Update() {
         Customer customer = new Customer();
         customer.setId(7L);
         customer.setCustName("王五");
         repositories.save(customer);
     }
 
     /**
      * 在这里delete方法,可以发现一个问题:
      *  1、底层会帮我们先进行查询一次,在进行删除!
      *  2、这样属性就不是游离状态,而是持久状态了!
      */
     @Test
     public void test_Delete() {
         Customer customer = new Customer();
         customer.setId(7L);
         customer.setCustName("王五");
         repositories.delete(customer);
     }
 }

7、使用JavaConfig进行配置!

config/SpringDataJPAConfig

 package com.yykk.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.yykk.repositories")
 @EnableTransactionManagement
 public class SpringDataJPAConfig {
 
     @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/spring_data?characterEncoding=UTF-8");
         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;
     }
 
     @Bean
     public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
         LocalContainerEntityManagerFactoryBean lemfb = new LocalContainerEntityManagerFactoryBean();
         lemfb.setDataSource(dataSource());
         lemfb.setJpaVendorAdapter(jpaVendorAdapter());
         lemfb.setPackagesToScan("com.yykk.pojo");
         return lemfb;
     }
 }

8、测试!

5.4、使用Spring Data Repositories

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

CrudRepository

 // 用于插入和修改 ,有主键就是修改,没有就是新增!
 // 获得插入后自增id,获得返回值
 <S extends T> S save(S entity);
 
 // 通过集合保存多个实体
 <S extends T> Iterable<S> saveAll(Iterable<S> entities);
 
 // 通过主键查询实体
 Optional<T> findById(ID 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();

5.5、实现分页与排序

1、首先我们需要更改的是我们的repository继承PagingAndSortingRepository,其他配置相同即可!

 package com.yykk.repositories;
 
 import com.yykk.pojo.Customer;
 import org.springframework.data.repository.PagingAndSortingRepository;
 
 public interface CustomerRepositories extends PagingAndSortingRepository<Customer,Long> {
 }

2、测试!

 import com.yykk.config.SpringDataJPAConfig;
 import com.yykk.pojo.Customer;
 import com.yykk.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;
 
 @ContextConfiguration(classes = SpringDataJPAConfig.class)
 @RunWith(SpringJUnit4ClassRunner.class)
 public class SpringDataJPAPagingAndSortTest {
 
     @Autowired
     CustomerRepositories repositories;
 
     @Test
     public void testPaging() {
         Page<Customer> all = repositories.findAll(PageRequest.of(0, 2));
         System.out.println(all.getTotalElements());
         System.out.println(all.getTotalPages());
         System.out.println(all.getContent());
     }
 
     @Test
     public void testSort() {
         Sort sort = Sort.by("id").descending();
         Iterable<Customer> all = repositories.findAll(sort);
         System.out.println(all);
     }
 
     @Test
     public void testSortTypeSafe() {
         Sort.TypedSort<Customer> sortType = Sort.sort(Customer.class);
 
         Sort sort = sortType.by(Customer::getId).ascending()
                 .and(sortType.by(Customer::getCustName)).descending();
 
         Iterable<Customer> all = repositories.findAll(sort);
         System.out.println(all);
     }
 }

5.6、知识点总结

底层数据库操作笼统讲就是增删改查,例如JDBC模板,Hibernate,Mybaties,JPA。接下来尝试springBootJPA旅程
 
 1. @Column(nullable = false)
 
  内容扩展
 
 unique=true是指这个字段的值在这张表里不能重复,所有记录值都要唯一,就像主键那样。
 
 nullable=false是这个字段在保存时必需有值,不能还是null值就调用save去保存入库。
 
 这两个用法是不同的,需要看个人需要,互相不可取代,根据个人需要可以两个都设置也可以只设置其中一个。
 
 hibernate常用方法介绍:
 
 delete(Object entity) 删除指定的持久化实例在程序中一般先用    Assert.notNullAssert.isTrue断言entity是否为空 和 entityid是否大于0,否则事务回滚。再用get(Class entityClass,Serializable id,LockMode lockMode)加锁查询出持久化实例,一般用lockMode.update悲观锁,最后用delete(Object entity)来删除此实例。
 
 deleteAll(Collection entities) 删除集合内全部持久化实例entities必须为持久化实例,否则报数据库异常错误。
 
 find(String queryString) 根据HQL查询字符串来返回实例集合find方法在执行时会先查找缓存,如果缓存找不到再查找数据库,如果再找不到就会返回null
 
 get(Class entityClass,Serializable id)根据主键加载特定持久化实例在程序中一般先用     Assert.isTrue断言id是否大于0,若大于0继续执行,若查到数据则返回实例,否则返回空不同于loadload若有数据则返回实例,否则报出ObjectNotFoundEcception异常,相比来说get效率高些。
 
 save(Object entity) 保存新的实例,在程序中一般先用    Assert.notNull断言实体是否为空,在进行保存。
 

 @Query
 
 1. 一个使用@Query注解的简单例子
 
 @Query(value = "select name,author,price from Book b where b.price>?1 and b.price findByPriceRange(long price1, long price2);
  
 
 2.  Like表达式
 
 @Query(value = "select name,author,price from Book b where b.name like %:name%")
 List findByNameMatch(@Param("name") String name);
  
 
 3. 使用Native SQL Query
 
 所谓本地查询,就是使用原生的sql语句(根据数据库的不同,在sql的语法或结构方面可能有所区别)进行查询数据库的操作。
 
 @Query(value = "select * from book b where b.name=?1", nativeQuery = true)
 List findByName(String name);
  
 
 4. 使用@Param注解注入参数
 
 @Query(value = "select name,author,price from Book b where b.name = :name AND b.author=:author AND b.price=:price")
 List findByNamedParam(@Param("name") String name, @Param("author") String author,
         @Param("price") long price);
  
 
 5. SPEL表达式(使用时请参考最后的补充说明)
 
    '#{#entityName}'值为'Book'对象对应的数据表名称(book)。
 
 public interface BookQueryRepositoryExample extends Repository{
        @Query(value = "select * from #{#entityName} b where b.name=?1", nativeQuery = true)
        List findByName(String name);
 
 }
  
 
 6. 一个较完整的例子
 
 public interface BookQueryRepositoryExample extends Repository {
     @Query(value = "select * from Book b where b.name=?1", nativeQuery = true) 
     List findByName(String name);// 此方法sql将会报错(java.lang.IllegalArgumentException),看出原因了吗,若没看出来,请看下一个例子
 
     @Query(value = "select name,author,price from Book b where b.price>?1 and b.price findByPriceRange(long price1, long price2);
 
     @Query(value = "select name,author,price from Book b where b.name like %:name%")
     List findByNameMatch(@Param("name") String name);
 
     @Query(value = "select name,author,price from Book b where b.name = :name AND b.author=:author AND b.price=:price")
     List findByNamedParam(@Param("name") String name, @Param("author") String author,
             @Param("price") long price);
 
 }
  
 
 7.  解释例6中错误的原因:
 
      因为指定了nativeQuery = true,即使用原生的sql语句查询。使用java对象'Book'作为表名来查自然是不对的。只需将Book替换为表名book。
 
 @Query(value = "select * from book b where b.name=?1", nativeQuery = true)
 List findByName(String name);
  
  
 
 补充说明(2017-01-12):
 
   有同学提出来了,例子5中用'#{#entityName}'为啥取不到值啊?
 
   先来说一说'#{#entityName}'到底是个啥。从字面来看,'#{#entityName}'不就是实体类的名称么,对,他就是。
 
   实体类Book,使用@Entity注解后,spring会将实体类Book纳入管理。默认'#{#entityName}'的值就是'Book'
 
   但是如果使用了@Entity(name = "book")来注解实体类Book,此时'#{#entityName}'的值就变成了'book'
 
   到此,事情就明了了,只需要在用@Entity来注解实体类时指定name为此实体类对应的表名。在原生sql语句中,就可以把'#{#entityName}'来作为数据表名使用。