6.2 Spring Boot集成jpa
Java持久化API(JPA琼讽,Java Persistence API)是一個將對象映射為關(guān)系數(shù)據(jù)庫的標(biāo)準(zhǔn)技術(shù)扮惦。JPA通過注解或XML描述ORM(Object Relationship Mapping订歪,對象-關(guān)系表的映射關(guān)系)橄碾,并將運行期的實體對象持久化到數(shù)據(jù)庫中由境。
其中,SQL(結(jié)構(gòu)化查詢語言, Structured Query Language)拼卵,是持久化操作中很重要的一個方面奢浑,通過面向?qū)ο蠖敲嫦驍?shù)據(jù)庫的查詢語言查詢數(shù)據(jù),避免程序的SQL語句的緊耦合腋腮。
JPA的主要目標(biāo)之一就是提供更加簡單的編程模型:在JPA框架下創(chuàng)建實體和創(chuàng)建Java 類一樣簡單雀彼,沒有任何的約束和限制,只需要使用 javax.persistence.Entity進行注解即寡。
JPA的框架和接口也都非常簡單徊哑,沒有太多特別的規(guī)則和設(shè)計模式的要求,開發(fā)者可以很容易的掌握聪富。
JPA基于非侵入式原則設(shè)計莺丑,因此可以很容易的和其它框架或者容器集成。
在SpringBoot中墩蔓,如果我們想使用JPA作為數(shù)據(jù)庫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提供了以下關(guān)鍵依賴:
- Hibernate - 一個非常流行的JPA實現(xiàn)奸披。
- Spring Data JPA - 讓實現(xiàn)基于JPA的repositories更容易昏名。
- Spring ORMs - Spring框架的ORM。
詳細的依賴樹如下
在SpringBoot中阵面,模塊依賴圖如下:
當(dāng)然葡粒,還有數(shù)據(jù)源的一些配置:
#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,就可以直接用這個方法了夫壁,它會執(zhí)行我們定義好的查詢語句并返回結(jié)果拾枣。代碼示例:
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來完成的邑茄。實質(zhì)思想就是通過注解在運行時動態(tài)生成對應(yīng)的查詢方法姨蝴,實現(xiàn)了元編程。
在接口方法上使用@Query
指定了nativeQuery = true肺缕,即使用原生的sql語句查詢左医。使用原生的sql語句, 根據(jù)數(shù)據(jù)庫的不同,在sql的語法或結(jié)構(gòu)方面可能有所區(qū)別同木。舉例如下:
@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")
如果我們想指定參數(shù)名洲尊,可以通過@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 創(chuàng)建查詢的順序
Spring Data JPA 在為接口創(chuàng)建代理對象時躯护,可以利用創(chuàng)建方法進行查詢,也可以利用@Query注釋進行查詢姆吭,那么如果在命名規(guī)范的方法上使用了@Query榛做,那spring data jpa是執(zhí)行我們定義的語句進行查詢唁盏,還是按照規(guī)范的方法進行查詢呢内狸?它該優(yōu)先采用哪種策略呢?
QueryLookupStrategy定義了3個屬性key厘擂,用以指定查找的順序昆淡。它有如下三個取值:
1:create-if-not-found:如果方法通過@Query指定了查詢語句,則使用該語句實現(xiàn)查詢刽严;如果沒有昂灵,則查找是否定義了符合條件的命名查詢避凝,如果找到,則使用該命名查詢眨补;如果兩者都沒有找到管削,則通過解析方法名字來創(chuàng)建查詢。這是 query-lookup-strategy 屬性的默認值撑螺。
2:create:通過解析方法名字來創(chuàng)建查詢含思。即使有符合的命名查詢,或者方法通過 @Query指定的查詢語句甘晤,都將會被忽略
3:use-declared-query:如果方法通過@Query指定了查詢語句含潘,則使用該語句實現(xiàn)查詢;如果沒有线婚,則查找是否定義了符合條件的命名查詢遏弱,如果找到,則使用該命名查詢塞弊;如果兩者都沒有找到漱逸,則拋出異常。
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);
}
具體的實現(xiàn)邏輯居砖,在org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy中虹脯。其關(guān)鍵方法如下:
/**
* 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奏候。
小結(jié)
本章示例工程源代碼:
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