zoukankan      html  css  js  c++  java
  • JPA + SpringData 操作数据库 ---- 深入了解 SpringData

    原创播客,如需转载请注明出处。原文地址:http://www.cnblogs.com/crawl/p/7735616.html 

    ----------------------------------------------------------------------------------------------------------------------------------------------------------

    笔记中提供了大量的代码示例,需要说明的是,大部分代码示例都是本人所敲代码并进行测试,不足之处,请大家指正~

    本博客中所有言论仅代表博主本人观点,若有疑惑或者需要本系列分享中的资料工具,敬请联系 qingqing_crawl@163.com

    -----------------------------------------------------------------------------------------------------------------------------------------------------------

    前言:之前为大家详细介绍了 JPA 的知识,之前提到 JPA 和 SpringData 结合才能发挥出无比巨大的威力。那么,今天楼主开始介绍 SpringData,写此篇的目的主要是为了复习,如果能帮助到有需要的朋友,那再好不过了。

    一、SpringData 概述

    1. SpringData:Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷

    2.JPA Spring Data:致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来完成!

    二、Spring Data JPA 的 Helloworld

    1.环境搭建:

    1)新建 Java Project:springdata

    2)当前工程下新建 lib 目录

    3)加入 Spring 的 jar 包:spring-framework-4.0.0.RELEASE equired 目录下所有

    4)加入 Hibernate 和 JPA 的 jar 包:hibernate-release-4.2.4.Finallib equired 和 hibernate-release-4.2.4.Finallibjpa 目录下的所有

    5)加入 c3p0 和 MqSQL 的驱动

    6)手动进行 Build Path

    2.新建 Spring 的配置文件 applicationContext.xml,并进行数据源的配置

    1) db.properties 文件

    jdbc.user=root
    jdbc.password=qiqingqing
    jdbc.driverClass=com.mysql.jdbc.Driver
    jdbc.jdbcUrl=jdbc:mysql://localhost:3306/springdata

    2)applicationContext.xml 文件中:

    <!-- 配置自动扫描的包 -->
        <context:component-scan base-package="com.software.springdata"></context:component-scan>
    
        <!-- 1.配置数据源 -->
        <context:property-placeholder location="classpath:db.properties"/>
        
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="user" value="${jdbc.user}"></property>
            <property name="password" value="${jdbc.password}"></property>
            <property name="driverClass" value="${jdbc.driverClass}"></property>
            <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        </bean>

    3)注意:希望大家能够养成良好的单元测试的习惯,以便及时发现问题,及时解决:测试数据源是否获取成功,新建一个 SpringDataTest 单元测试类

        private ApplicationContext ctx = null;
    
        {
            ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            
        }
    
        //测试数据源是否获取成功
        @Test
        public void testDataSource() throws SQLException {
            DataSource dataSource = ctx.getBean(DataSource.class);
            System.out.println(dataSource.getConnection());
        }

    只要输出数据库连接的信息,那就证明我们数据源的的配置没有问题了。

    3.配置 JPA 的 EntityManagerFactory

    <!-- 2.配置 JPA 的 EntityManagerFactory -->
        <bean id="entityManagerFactory"
                     class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <!-- 配置数据源 -->             
            <property name="dataSource" ref="dataSource"></property>
            <!-- 配置 JPA 提供商的适配器 -->
            <property name="jpaVendorAdapter">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
            </property>
            <!-- 配置实体类所在包 -->
            <property name="packagesToScan" value="com.software.springdata"></property>
            <!-- 配置 JPA 实现产品的基本属性 -->
            <property name="jpaProperties">
                <props>
                    <!-- 生成的数据表的列的映射策略 -->
                    <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                    <!-- hibernate 基本属性 -->
                    <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
                    <prop key="hibernate.show_sql">true</prop>
                    <prop key="hibernate.format_sql">true</prop>
                    <prop key="hibernate.hbm2ddl.auto">update</prop>
                </props>
            </property>
        </bean>

    测试 JPA 是否配置成功:

    新建一个实体类 Person,添加必要的 JPA 注解,创建一个空的 JPA 的测试方法,执行此方法,查看是否生成数据表

    package com.software.springdata;
    
    import java.util.Date;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.ManyToOne;
    import javax.persistence.Table;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
    
    @Table(name="JPA_PERSONS")
    @Entity
    public class Person {
        
        private Integer id;
        
        private String lastName;
        
        private String email;
        
        private Date birth;
        
        private Integer addressId;
        
        @Column(name="ADD_ID")
        public Integer getAddressId() {
            return addressId;
        }
    
        public void setAddressId(Integer addressId) {
            this.addressId = addressId;
        }
    
        private Address address;
        
        @JoinColumn(name="ADDRESS_ID")
        @ManyToOne
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        @GeneratedValue
        @Id
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        @Column(name="LAST_NAME")
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        @Temporal(TemporalType.DATE)
        public Date getBirth() {
            return birth;
        }
    
        public void setBirth(Date birth) {
            this.birth = birth;
        }
    
        @Override
        public String toString() {
            return "Person [id=" + id + ", lastName=" + lastName + ", email="
                    + email + ", birth=" + birth + "]";
        }
        
    }

    4.配置 JPA 的事务管理器和支持注解的事务

    <!-- 3.配置 JPA 的事务管理器 -->
        <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"></property>
        </bean>
        
        <!-- 4.配置支持注解的事务 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>

    5.配置 SpringData

    1)加入 SpringData 的 jar 包:spring-data-jpa equired 目录下所有(自创建的目录),手动 Build Path

    image

    还要加入 SpringData 依赖的日志包 slf4j

    2)加入 jpa 的命名空间,左下角 Namespaces 中添加

    image

    配置 SpringData 扫描的包和 EntityManagerFactory

    <!-- 5.配置 Spring Data -->
        <!-- 加入 jpa 的命名空间:左下角 Namespaces 中加入 -->
        <!-- 配置 Spring Data 扫描的包和 EntityManagerFactory -->
        <!-- base-package:扫描 Repository Bean 所在的 package -->
        <jpa:repositories base-package="com.software.springdata"
                     entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>

    6.编写 SpringData 的代码,测试 HelloWorld

    1)创建一个 PersonRepsotory 接口继承 Repository 接口,声明一个方法

    image

    2)编写测试方法:需要在测试类中引入 PersonRepository 的实例,通过 ctx.getBean(PersonRepository.class); 获取,以后不再赘述。

    1 //测试 Spring Data 的 Helloworld
    2     @Test
    3     public void testSpringDataHelloworld() {
    4         Person person = repository.getByLastName("AA");
    5         System.out.println(person);
    6     }

    这样便会获取 LastName 为 AA 的数据库中的那条记录。

    三、Repository 接口

    1. Repository 是一个空接口,即是一个标记接口。源码

    2.若我们定义的接口实现了 Repository,则该接口会被 IOC 容器识别为一个 Repository Bean,纳入到 IOC 容器中,进而可以在该接口中定义一些符合规范的方法

    3.还可以通过 @RepositoryDefinition 注解来替代继承 Repository 接口

    4.Repository 接口的实现类

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

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

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

    4)自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。

    注:JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

     四、SpringData 的方法定义规范SpringData 的方法定义规范

    1.不能随便声明,需要符合一定的规范

    2.查询的方法以 find、read、get 开头

    3.涉及条件查询时条件的属性要以关键字连接,条件属性首字母大写

    1)SpringData 支持的关键字

    2)举例:

    1 //WHERE lastName like %? AND id < ?
    2     List<Person> getByLastNameEndingWithAndIdLessThan(String lastName, Integer age);
    3     
    4     //WHERE lastName like ?% AND id > ?
    5     List<Person> getByLastNameStartingWithAndIdGreaterThan(String lastName, Integer age);
    6     
    7     //WHERE email IN(?,?,?) OR birth < ?
    8     List<Person> 
    getByEmailInOrBirthLessThan
    (List<String> emails, Date date);

    测试:

     1 //测试 SpringData 支持的关键字
     2     @Test
     3     public void testKeyWords() {
     4         List<Person> persons = repository.getByLastNameEndingWithAndIdLessThan("A", 5);
     5         System.out.println(persons);
     6         
     7         persons = repository.getByLastNameStartingWithAndIdGreaterThan("A", 1);
     8         System.out.println(persons);
     9         
    10         persons = repository.getByEmailInOrBirthLessThan(Arrays.asList("aa@163.com", "cc@163.com",
    11                             "ee@163.com"), new Date());
    12         System.out.println(persons.size());
    13     }

    4.支持级联查询,若当前类有符合条件的查询,则优先使用,而不使用级联查询,若需要使用级联查询,则属性之间需要使用 _ (下划线)来连接

    1)创建 Address 类,添加对应的 JPA 注解,在 Person 类中添加 Address 类的引用,使用 @ManyToOne 映射,生成对应的数据表

    2)创建测试方法:此时为级联查询

     

    3)为 Person 类添加 addressId 属性后,若再进行相同的测试,则不是级联查询了,而查询条件为 Person 类中的 addressId 属性。若要再进行级联查询,需要在属性之间需要使用 _ (下划线)来连接

    五、@Query 注解

    1.使用 @Query 注解可以自定义 JPQL 语句实现更加灵活的查询

    定义方法:

    1 //查询 id 值最大的 Person
    2     //使用 @Query 注解可以自定义 JPQL 语句实现更加灵活的查询
    3     @Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")
    4     Person getMaxIdPerson();

    测试方法:

    //测试 @Query 注解
        @Test
        public void testQueryAnnotation() {
            Person person = repository.getMaxIdPerson();
            System.out.println(person);
        }

    2.为 @Query 注解传递参数的两种方式

    1)使用占位符:

    1 //为 @Query 注解传递参数的方式1:使用占位符, 此方式要求形参与定义的 JPQL 的参数位置一致
    2     @Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
    3     List<Person> testQueryAnnotationParam1(String lastName, String email);

    测试:

    1 //测试向 @Query 注解传参
    2     @Test
    3     public void testQueryAnnotationParam1() {
    4         List<Person> persons = repository.testQueryAnnotationParam1("AA", "aa@163.com");
    5         System.out.println(persons);
    6     }

    2)使用命名参数:

    1 //为 @Query 注解传递参数的方式2:使用命名参数, 此方式形参与定义的 JPQL 的参数位置不必一致
    2     @Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
    3     List<Person> testQueryAnnotationParam2(@Param("email") String email, @Param("lastName") String lastName);
    1 //测试向 @Query 注解传参
    2     @Test
    3     public void testQueryAnnotationParam2() {
    4         List<Person> persons = repository.testQueryAnnotationParam2("aa@163.com", "AA");
    5         System.out.println(persons);
    6     }

    3.带有 LIKE 关键字的 @Query 注解

    1)需要在测试方法中手动加上 %,不推荐

    1 //带有 LIKE 关键字的 @Query 注解
    2     @Query("SELECT p FROM Person p WHERE p.lastName LIKE ?1 AND p.email LIKE ?2")
    3     List<Person> testQueryAnnotationLikeParam(String lastName, String email);
    1 //测试带有 LIKE 关键字的 @Query 注解
    2     @Test
    3     public void testQueryAnnotationLikeParam() {
    4         List<Person> persons = repository.testQueryAnnotationLikeParam("%A%", "%aa%");
    5         System.out.println(persons);
    6     }

    2)SpringData 允许在占位符上添加 %

    1 //带有 LIKE 关键字的 @Query 注解:SpringData 允许在占位符上添加 %
    2     @Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% AND p.email LIKE %?2%")
    3     List<Person> testQueryAnnotationLikeParam2(String lastName, String email);
    1 //测试带有 LIKE 关键字的 @Query 注解
    2     @Test
    3     public void testQueryAnnotationLikeParam2() {
    4         List<Person> persons = repository.testQueryAnnotationLikeParam2("A", "aa");
    5         System.out.println(persons);
    6     }

    3)SpringData 允许在命名参数上添加 %

    //带有 LIKE 关键字的 @Query 注解:SpringData 允许在命名参数上添加 %
        @Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% AND p.email LIKE %:email%")
        List<Person> testQueryAnnotationLikeParam3(@Param("lastName") String lastName, @Param("email") String email);
    1 //测试带有 LIKE 关键字的 @Query 注解
    2     @Test
    3     public void testQueryAnnotationLikeParam3() {
    4         List<Person> persons = repository.testQueryAnnotationLikeParam3("A", "aa");
    5         System.out.println(persons);
    6     }

    4.使用 @Query 执行本地 SQL 查询,在 @Query 注解中添加参数 nativeQuery=true

    //本地 SQL 查询:设置 nativeQuery=true 即可执行本地查询
        @Query(value="SELECT count(id) FROM jpa_persons", nativeQuery=true)
        long getTotalCount();
    1 //测试本地 SQL 查询
    2     @Test
    3     public void testNativeQuery() {
    4         long count = repository.getTotalCount();
    5         System.out.println(count);
    6     }

    六、@Modifying 注解

    1.使用 @Modifying 配合 @Query 可以完成 UPDATE 和 DELETE 操作

    2.可以通过自定义 JPQL 完成 UPDATE 和 DELETE 操作,注:JPQL 不支持使用 INSERT

    3.在 @Query 中编写 JPQL 语句,但必须使用 @Modifying 注解修饰,以通知 SpringData 此操作是一个 UPDATE 或 DELETE 操作

    4.UPDATE 或 DELETE 操作需要使用事务,所以需要定义 service 层,在 service 层方法上添加事务操作

    5.示例:

    1 /*
    2      * 可以通过自定义 JPQL 完成 UPDATE 和 DELETE 操作,注:JPQL 不支持使用 INSERT
    3      * 在 @Query 中编写 JPQL 语句,但必须使用 @Modifying 注解修饰,以通知 SpringData 此操作是一个 UPDATE 或 DELETE 操作
    4      * UPDATE 或 DELETE 操作需要使用事务,所以需要定义 service 层,在 service 层方法上添加事务操作
    5      * 默认情况下,SpringData 的每个方法上都有事务,但都是只读事务,他们不能完成修改操作
    6      */
    7     @Modifying
    8     @Query("UPDATE Person p SET p.email = :email WHERE p.id = :id")
    9     void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);

    七、Repository 接口的子接口

    1、CrudRepository 接口;测试此接口的 save(Iterable<S> entities) 方法进行批量保存

    1)使自定义的接口继承 CrudRepository 接口

     

    2)Service 层:需要事务

     

    3)测试:

    2. PagingAndSortingRepository 接口

    1)实现分页操作(带排序):(只读事务,不需要再 service 层中编写

     

    //测试 PagingAndSortRepository 的 findAll(Pageable pageable) 方法,进行分页
        @Test
        public void testPagingAndSortingRepository() {
            //pageNo 从 0 开始
            int pageNo = 3 - 1;
            int pageSize = 5;
            
            //Sort 封装了排序信息,Order 指明具体是根据哪一个属性进行升序或者降序
            Order order1 = new Order(Direction.DESC, "id");
            Order order2 = new Order(Direction.ASC, "email");
            Sort sort = new Sort(order1, order2);
            
            //Pageable 接口通常使用其 PageRequest 实现类,其中封装了需要分页的信息
            PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
            Page<Person> page = pagingAndSortingRepository.findAll(pageable);
            
            System.out.println("总共有 " + page.getTotalElements() + " 条记录");
            System.out.println("总共有 " + page.getTotalPages() + " 页");
            System.out.println("当前页为:" + (page.getNumber() + 1));
            System.out.println("当前的 List: " + page.getContent());
            System.out.println("当前页的总记录数为:" + page.getNumberOfElements());
        }

    3. JpaRepository  接口:测试此接口的 saveAndFlush() 方法,此方法相当于 JPA 中的 merge() 方法,详见 JPA 笔记

     

    补:JpaSpecificationExecutor 接口,不属于Repository体系,实现一组 JPA Criteria 查询相关的方法 ,即带条件的分页查询

    1)自定义的接口必须实现 Repository 或 Repository 的子接口 还有 JpaSpecificationExecutor 接口

    八、自定义 Repository 方法:为某一个 Repository 上添加自定义方法

    1.步骤:

    1)定义一个接口: 声明要添加的, 并自实现的方法

    2)提供该接口的实现类: 类名需在要声明的 Repository(以 PersonRepository 为例) 后添加 Impl, 并实现方法

     

    3)声明 Repository 接口(即 PersonRepository 接口), 并继承 1) 声明的接口

    4)测试使用:

    ----------------------------------------------------------------------------------------------------------------

    相关链接:

    JPA + SpringData 操作数据库原来可以这么简单 ---- 深入了解 JPA - 1

    JPA + SpringData 操作数据库原来可以这么简单 ---- 深入了解 JPA - 2

    JPA + SpringData 操作数据库原来可以这么简单 ---- 深入了解 JPA - 3

    手把手教你解决无法创建 JPA 工程的问题

  • 相关阅读:
    BZOJ3997:[TJOI2015]组合数学(DP,Dilworth定理)
    BZOJ4807:車(组合数学,高精度)
    BZOJ4008:[HNOI2015]亚瑟王(DP,概率期望)
    BZOJ1499:[NOI2005]瑰丽华尔兹(DP,单调队列)
    洛谷1514 引水入城
    洛谷 1018 乘积最大
    八数码难题
    CODEVS 1069关押罪犯
    CODEVS 1067 机器翻译
    洛谷 P1417 烹调方案
  • 原文地址:https://www.cnblogs.com/crawl/p/7735616.html
Copyright © 2011-2022 走看看