6.2 Spring Boot集成jpa

作者: 光剑书架上的书 | 来源:发表于2017-04-26 00:36 被阅读618次

    6.2 Spring Boot集成jpa

    Java持久化API(JPA,Java Persistence API)是一个将对象映射为关系数据库的标准技术。JPA通过注解或XML描述ORM(Object Relationship Mapping,对象-关系表的映射关系),并将运行期的实体对象持久化到数据库中。

    其中,SQL(结构化查询语言, Structured Query Language),是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句的紧耦合。

    JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注解。

    JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。

    JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。

    在SpringBoot中,如果我们想使用JPA作为数据库ORM层,很简单,我们只需要添加spring-boot-starter-data-jpa依赖即可:

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

    spring-boot-starter-data-jpa提供了以下关键依赖:

    • Hibernate - 一个非常流行的JPA实现。
    • Spring Data JPA - 让实现基于JPA的repositories更容易。
    • Spring ORMs - Spring框架的ORM。

    详细的依赖树如下

    在SpringBoot中,模块依赖图如下:

    当然,还有数据源的一些配置:

    #mysql
    spring.datasource.url = jdbc:mysql://localhost:3306/teda?useUnicode=true&characterEncoding=UTF8
    spring.datasource.username = root
    spring.datasource.password = root
    spring.datasource.driverClassName = com.mysql.jdbc.Driver
    spring.datasource.max-active=0
    spring.datasource.max-idle=0
    spring.datasource.min-idle=0
    spring.datasource.max-wait=10000
    spring.datasource.max-wait-millis=31536000
    
    # Specify the DBMS
    spring.jpa.database = MYSQL
    # Show or not log for each sql query
    spring.jpa.show-sql = true
    # Hibernate ddl auto (create, create-drop, update)
    spring.jpa.hibernate.ddl-auto = update
    # Naming strategy
    spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
    # stripped before adding them to the entity manager)
    spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
    
    

    在实体类上使用@NamedQuery

    我们可以直接在实体类上,定义查询方法。代码示例:

    package com.steda.entity
    
    import java.util.Date
    import javax.persistence._
    
    import scala.beans.BeanProperty
    
    @Entity
    @NamedQuery(name = "findByState",
      query = "select t from TedaCase t where t.state = ?1")
    class TedaCase {
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      @BeanProperty
      var id: Long = _
      @BeanProperty
      var name: String = _
      ...
    }
    
    

    然后,我们继承CrudRepository接口之后,定义一个同名的方法findByState,就可以直接用这个方法了,它会执行我们定义好的查询语句并返回结果。代码示例:

    package com.steda.dao
    
    
    import com.steda.entity.TedaCase
    import org.springframework.data.jpa.repository.Query
    import org.springframework.data.repository.CrudRepository
    
    trait TedaCaseDao extends CrudRepository[TedaCase, java.lang.Long] {
      def findByState(state: Integer): java.util.List[TedaCase]
      ...
    }
    
    
    

    同样的,如果我们想定义多条NamedQuery,也是可以的。代码示例如下:

    @NamedQueries({ 
            @NamedQuery(name="findAllUser",query="select u from User u"), 
            @NamedQuery(name="findUserWithId",query="select u from User u WHERE u.id = ?1"), 
            @NamedQuery(name="findUserWithName",query="select u from User u WHERE u.name = :name") 
             
    }) 
    

    其背后的方法的生成自动生成原理,是由类org.hibernate.jpa.spi.AbstractEntityManagerImpl来完成的。实质思想就是通过注解在运行时动态生成对应的查询方法,实现了元编程。

    在接口方法上使用@Query

    指定了nativeQuery = true,即使用原生的sql语句查询。使用原生的sql语句, 根据数据库的不同,在sql的语法或结构方面可能有所区别。举例如下:

    @Query(value="select * from param_json_template order by id desc",nativeQuery = true)
    

    默认false。我们可以使用java对象作为表名来查询。但是要注意,就不能使用原生sql的select * from ,要使用java字段名。举例如下:

    @Query(value="select  id,paramObject,paramJsonTemplateStr  from ParamJsonTemplate p where p.paramObject like %:paramObject% order by p.id desc")
    

    如果我们想指定参数名,可以通过@Param(value = "paramObject") 来指定方法变量名,然后在查询语句中,使用:paramObject来使用该变量。
    举例如下:

    @Query(value="select  id,paramObject,paramJsonTemplateStr  from ParamJsonTemplate p where p.paramObject like %:paramObject% order by p.id desc")
      def findByParamObject(@Param(value = "paramObject") paramObject:String): java.util.List[ParamJsonTemplate]
    

    完整实例代码:

    package com.steda.dao
    
    import org.springframework.data.repository.CrudRepository
    import com.steda.entity.ParamJsonTemplate
    import org.springframework.data.jpa.repository.Query
    import org.springframework.data.repository.query.Param
    
    trait ParamJsonTemplateDao extends CrudRepository[ParamJsonTemplate, java.lang.Long] {
    
    
      @Query(value="select * from param_json_template order by id desc",nativeQuery = true)
      def findAll(): java.util.List[ParamJsonTemplate]
    
    
      @Query(value="select  id,paramObject,paramJsonTemplateStr  from ParamJsonTemplate p where p.paramObject like %:paramObject% order by p.id desc")
      def findByParamObject(@Param(value = "paramObject") paramObject:String): java.util.List[ParamJsonTemplate]
    
      def save(p: ParamJsonTemplate): ParamJsonTemplate
    
    }
    
    

    JpaRepository 创建查询的顺序

    Spring Data JPA 在为接口创建代理对象时,可以利用创建方法进行查询,也可以利用@Query注释进行查询,那么如果在命名规范的方法上使用了@Query,那spring data jpa是执行我们定义的语句进行查询,还是按照规范的方法进行查询呢?它该优先采用哪种策略呢?

    QueryLookupStrategy定义了3个属性key,用以指定查找的顺序。它有如下三个取值:

    1:create-if-not-found:如果方法通过@Query指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方法名字来创建查询。这是 query-lookup-strategy 属性的默认值。

    2:create:通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过 @Query指定的查询语句,都将会被忽略

    3:use-declared-query:如果方法通过@Query指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常。

    Spring Data JPA 在org.springframework.data.repository.query.QueryLookupStrategy中定义了如下策略枚举值:

    CREATE, USE_DECLARED_QUERY, CREATE_IF_NOT_FOUND
    

    其源码如下:

    /*
     * Copyright 2008-2010 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.springframework.data.repository.query;
    
    import java.lang.reflect.Method;
    import java.util.Locale;
    
    import org.springframework.data.repository.core.NamedQueries;
    import org.springframework.data.repository.core.RepositoryMetadata;
    import org.springframework.util.StringUtils;
    
    /**
     * Strategy interface for which way to lookup {@link RepositoryQuery}s.
     * 
     * @author Oliver Gierke
     */
    public interface QueryLookupStrategy {
    
        public static enum Key {
    
            CREATE, USE_DECLARED_QUERY, CREATE_IF_NOT_FOUND;
    
            /**
             * Returns a strategy key from the given XML value.
             * 
             * @param xml
             * @return a strategy key from the given XML value
             */
            public static Key create(String xml) {
    
                if (!StringUtils.hasText(xml)) {
                    return null;
                }
    
                return valueOf(xml.toUpperCase(Locale.US).replace("-", "_"));
            }
        }
    
        /**
         * Resolves a {@link RepositoryQuery} from the given {@link QueryMethod} that can be executed afterwards.
         * 
         * @param method
         * @param metadata
         * @param namedQueries
         * @return
         */
        RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries);
    }
    
    

    具体的实现逻辑,在org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy中。其关键方法如下:

    
        /**
         * Creates a {@link QueryLookupStrategy} for the given {@link EntityManager} and {@link Key}.
         * 
         * @param em must not be {@literal null}.
         * @param key may be {@literal null}.
         * @param extractor must not be {@literal null}.
         * @param evaluationContextProvider must not be {@literal null}.
         * @return
         */
        public static QueryLookupStrategy create(EntityManager em, Key key, QueryExtractor extractor,
                EvaluationContextProvider evaluationContextProvider) {
    
            Assert.notNull(em, "EntityManager must not be null!");
            Assert.notNull(extractor, "QueryExtractor must not be null!");
            Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
    
            switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
                case CREATE:
                    return new CreateQueryLookupStrategy(em, extractor);
                case USE_DECLARED_QUERY:
                    return new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider);
                case CREATE_IF_NOT_FOUND:
                    return new CreateIfNotFoundQueryLookupStrategy(em, extractor, new CreateQueryLookupStrategy(em, extractor),
                            new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider));
                default:
                    throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
            }
        }
    
    
    

    从这句switch (key != null ? key : Key.CREATE_IF_NOT_FOUND),我们可以看出默认值是CREATE_IF_NOT_FOUND。

    小结

    本章示例工程源代码:

    https://github.com/EasySpringBoot/teda

    参考资料:
    1.http://docs.jboss.org/hibernate/orm/5.2/quickstart/html_single/
    2.https://spring.io/guides/gs/accessing-data-jpa/
    3.http://baike.baidu.com/item/JPA

    相关文章

      网友评论

        本文标题:6.2 Spring Boot集成jpa

        本文链接:https://www.haomeiwen.com/subject/tgwbzttx.html