SpringData JPA基本/高級/多數(shù)據(jù)源的使用詳解
一、Spring Data JPA基本用法
Spring Data JPA 是 Spring Boot 體系中約定優(yōu)于配置的最佳實(shí)現(xiàn),大大簡化了項(xiàng)目中數(shù)據(jù)庫的操作
1、概念
JPA由來
ORM 框架能夠?qū)?Java 對象映射到關(guān)系數(shù)據(jù)庫中,能夠直接持久化復(fù)雜的 Java 對象。ORM 框架的出現(xiàn),可以讓開發(fā)者從數(shù)據(jù)庫編程中解脫出來,把更多的精力放在了業(yè)務(wù)模型與業(yè)務(wù)邏輯上。目前比較流行的 ORM 框架有 Hibernate、MyBatis、TopLink、Spring JDBC 等。
在 JPA 規(guī)范之前,由于沒有官方的標(biāo)準(zhǔn),使得各 ORM 框架之間的 API 差別很大,使用了某種 ORM 框架的系統(tǒng)會嚴(yán)重受制于該 ORM 的標(biāo)準(zhǔn)?;诖?,Sun 引入新的 JPA ORM,主要的原因有:其一,簡化現(xiàn)有 Java EE 和 Java SE 應(yīng)用開發(fā)工作;其二,Sun 希望整合 ORM 技術(shù),實(shí)現(xiàn)統(tǒng)一的 API 調(diào)用接口。
JPA是什么
JPA(Java Persistence API)是 Sun 官方提出的 Java 持久化規(guī)范。它為 Java 開發(fā)人員提供了一種對象 / 關(guān)聯(lián)映射工具來管理 Java 應(yīng)用中的關(guān)系數(shù)據(jù)。它的出現(xiàn)主要是為了簡化現(xiàn)有的持久化開發(fā)工作和整合 ORM 技術(shù),結(jié)束現(xiàn)在 Hibernate、TopLink、JDO 等 ORM 框架各自為營的局面。
注意:JPA 是一套規(guī)范,不是一套產(chǎn)品,那么像 Hibernate、TopLink、JDO 它們是一套產(chǎn)品,如果說這些產(chǎn)品實(shí)現(xiàn)了這個 JPA 規(guī)范,那么我們就可以稱他們?yōu)?JPA 的實(shí)現(xiàn)產(chǎn)品。
Spring Data JPA
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規(guī)范的基礎(chǔ)上封裝的一套 JPA 應(yīng)用框架,可以讓開發(fā)者用極簡的代碼即可實(shí)現(xiàn)對數(shù)據(jù)的訪問和操作。它提供了包括增、刪、改、查等在內(nèi)的常用功能,且易于擴(kuò)展,學(xué)習(xí)并使用 Spring Data JPA 可以極大提高開發(fā)效率。Spring Data JPA 其實(shí)就是 Spring 基于 Hibernate 之上構(gòu)建的 JPA 使用解決方案,方便在 Spring Boot 項(xiàng)目中使用 JPA 技術(shù)。
Spring Data JPA 讓我們解脫了 DAO 層的操作,基本上所有 CRUD 都可以依賴于它實(shí)現(xiàn)。
2、快速上手
1.添加依賴
<dependency> ? ? <groupId>org.springframework.boot</groupId> ? ? <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ?<dependency> ? ? <groupId>mysql</groupId> ? ? <artifactId>mysql-connector-java</artifactId> </dependency>
2.添加配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.properties.hibernate.hbm2ddl.auto=create spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect #SQL 輸出 spring.jpa.show-sql=true #format 一下 SQL 進(jìn)行輸出 spring.jpa.properties.hibernate.format_sql=true
hibernate.hbm2ddl.auto 參數(shù)的作用主要用于:自動創(chuàng)建、更新、驗(yàn)證數(shù)據(jù)庫表結(jié)構(gòu),有四個值。
create
:每次加載 Hibernate 時都會刪除上一次生成的表,然后根據(jù) model 類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執(zhí)行,這就是導(dǎo)致數(shù)據(jù)庫表數(shù)據(jù)丟失的一個重要原因。create-drop
:每次加載 Hibernate 時根據(jù) model 類生成表,但是 sessionFactory 一關(guān)閉,表就自動刪除。update
:最常用的屬性,第一次加載 Hibernate 時根據(jù) model 類會自動建立起表的結(jié)構(gòu)(前提是先建立好數(shù)據(jù)庫),以后加載 Hibernate 時根據(jù) model 類自動更新表結(jié)構(gòu),即使表結(jié)構(gòu)改變了,但表中的行仍然存在,不會刪除以前的行。要注意的是當(dāng)部署到服務(wù)器后,表結(jié)構(gòu)是不會被馬上建立起來的,是要等應(yīng)用第一次運(yùn)行起來后才會。validate
:每次加載 Hibernate 時,驗(yàn)證創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),只會和數(shù)據(jù)庫中的表進(jìn)行比較,不會創(chuàng)建新表,但是會插入新值。
其他
dialect
主要是指定生成表名的存儲引擎為 InnoDBshow-sql
是否在日志中打印出自動生成的 SQL,方便調(diào)試的時候查看
3.實(shí)體類
@Entity public class User { ? ? @Id ? ? @GeneratedValue ? ? private Long id; ? ? @Column(nullable = false, unique = true) ? ? private String userName; ? ? @Column(nullable = false) ? ? private String passWord; ? ? @Column(nullable = false, unique = true) ? ? private String email; ? ? @Column(nullable = true, unique = true) ? ? private String nickName; ? ? @Column(nullable = false) ? ? private String regTime; ? ? //省略 getter settet 方法、構(gòu)造方法 }
@Entity(name="EntityName")
必須,用來標(biāo)注一個數(shù)據(jù)庫對應(yīng)的實(shí)體,數(shù)據(jù)庫中創(chuàng)建的表名默認(rèn)和類名一致。其中,name 為可選,對應(yīng)數(shù)據(jù)庫中一個表,使用此注解標(biāo)記 Pojo 是一個 JPA 實(shí)體。@Table(name="",catalog="",schema="")
可選,用來標(biāo)注一個數(shù)據(jù)庫對應(yīng)的實(shí)體,數(shù)據(jù)庫中創(chuàng)建的表名默認(rèn)和類名一致。通常和@Entity配合使用,只能標(biāo)注在實(shí)體的 class 定義處,表示實(shí)體對應(yīng)的數(shù)據(jù)庫表的信息。@Id
必須,@Id定義了映射到數(shù)據(jù)庫表的主鍵的屬性,一個實(shí)體只能有一個屬性被映射為主鍵。@GeneratedValue(strategy=GenerationType,generator="")
可選,strategy: 表示主鍵生成策略,有 AUTO、INDENTITY、SEQUENCE 和 TABLE 4 種,分別表示讓 ORM 框架自動選擇,generator: 表示主鍵生成器的名稱。@Column(name = "user_code", nullable = false, length=32)
可選,@Column 描述了數(shù)據(jù)庫表中該字段的詳細(xì)定義,這對于根據(jù) JPA 注解生成數(shù)據(jù)庫表結(jié)構(gòu)的工具。name: 表示數(shù)據(jù)庫表中該字段的名稱,默認(rèn)情形屬性名稱一致;nullable: 表示該字段是否允許為 null,默認(rèn)為 true;unique: 表示該字段是否是唯一標(biāo)識,默認(rèn)為 false;length: 表示該字段的大小,僅對 String 類型的字段有效。@Transient
可選,@Transient 表示該屬性并非一個到數(shù)據(jù)庫表的字段的映射,ORM 框架將忽略該屬性。@Enumerated
可選,使用枚舉的時候,我們希望數(shù)據(jù)庫中存儲的是枚舉對應(yīng)的 String 類型,而不是枚舉的索引值,需要在屬性上面添加 @Enumerated(EnumType.STRING) 注解。
4.Repository構(gòu)建
創(chuàng)建的 Repository 只要繼承 JpaRepository 即可,就會幫我們自動生成很多內(nèi)置方法。另外還有一個功能非常實(shí)用,可以根據(jù)方法名自動生產(chǎn) SQL,比如 findByUserName 會自動生產(chǎn)一個以 userName 為參數(shù)的查詢方法,比如 findAll 會自動查詢表里面的所有數(shù)據(jù)等。
public interface UserRepository extends JpaRepository<User,Long> { ? ? User findByUserName(String userName); ? ? User findByUserNameOrEmail(String username,String email); }
我們只需要在對應(yīng)的 Repository 中創(chuàng)建好方法,使用的時候直接將接口注入到類中調(diào)用即可。在 IDEA 中打開類 UserRepository,在這個類的大括號內(nèi)的區(qū)域右鍵單擊,選擇 Diagrams | Show Diagram 選項(xiàng),即可打開類圖,如下:
通過上圖我們發(fā)現(xiàn) JpaRepository 繼承 PagingAndSortingRepository 和 QueryByExampleExecutor,PagingAndSortingRepository 類主要負(fù)責(zé)排序和分頁內(nèi)容,QueryByExampleExecutor 提供了很多示例的查詢方法,如下:
public interface QueryByExampleExecutor<T> {? ? ? <S extends T> S findOne(Example<S> example); ? //根據(jù)“實(shí)例”查找一個對象 ? ? <S extends T> Iterable<S> findAll(Example<S> example); ? ? //根據(jù)“實(shí)例”查找一批對象 ? ? <S extends T> Iterable<S> findAll(Example<S> example,Sort sort); ?//根據(jù)“實(shí)例”查找一批對象,且排序 ? ? <S extends T> Page<S> findAll(Example<S> example,Pageable pageable); ?//根據(jù)“實(shí)例”查找一批對象,且排序和分頁 ? ? <S extends T> long count(Example<S> example); ?//根據(jù)“實(shí)例”查找,返回符合條件的對象個數(shù) ? ? <S extends T> boolean exists(Example<S> example); ?//根據(jù)“實(shí)例”判斷是否有符合條件的對象 }
因此,繼承 JpaRepository 的會自動擁有上述這些方法和排序、分頁功能。查看源碼我們發(fā)現(xiàn) PagingAndSortingRepository 又繼承了 CrudRepository。CrudRepository 的源碼如下:
@NoRepositoryBean public interface CrudRepository<T,ID> extends Repository<T,ID> { ? ? <S extends T> S save(S entity); ? ? <S extends T> Iterable<S> saveAll(Iterable<S> entities); ? ? Optional<T> findById(ID id); ? ? boolean existsById(ID id); ? ? Iterable<T> findAll(); ? ? Iterable<T> findAllById(Iterable<ID> ids); ? ? long count(); ? ? void deleteById(ID id); ? ? void delete(T entity); ? ? void deleteAll(Iterable<? extends T> entities); ? ? void deleteAll(); }
從 CrudRepository 的源碼可以看出 CrudRepository 內(nèi)置了我們最常用的增、刪、改、查的方法,方便我們?nèi)ナ褂?,因?yàn)?JpaRepository 繼承了 PagingAndSortingRepository,PagingAndSortingRepository 繼承了 CrudRepository,所以繼承 JpaRepository 的類也默認(rèn)擁有了上述方法。
因此使用 JPA 操作數(shù)據(jù)庫時,只需要構(gòu)建的 Repository 繼承了 JpaRepository,就會擁有了很多常用的數(shù)據(jù)庫操作方法。
5.測試
創(chuàng)建好 UserRepository 之后,當(dāng)業(yè)務(wù)代碼中需要使用時直接將此接口注入到對應(yīng)的類中,在 Spring Boot 啟動時,會自動根據(jù)注解內(nèi)容創(chuàng)建實(shí)現(xiàn)類并注入到目標(biāo)類中。
@RunWith(SpringRunner.class) @SpringBootTest public class UserRepositoryTests { ? ? @Resource ? ? private UserRepository userRepository; ? ? @Test ? ? public void test() ?{ ? ? ? ? Date date = new Date(); ? ? ? ? DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG); ? ? ? ? ? ? ? ? String formattedDate = dateFormat.format(date); ? ? ? ? userRepository.save(new User("aa","aa@126.com","aa","aa123456",formattedDate)); ? ? ? ? userRepository.save(new User("bb","bb@126.com","bb","bb123456",formattedDate)); ? ? ? ? userRepository.save(new User("cc","cc@126.com","cc","cc123456",formattedDate)); ? ? ? ? Assert.assertEquals(9,userRepository.findAll().size()); ? ? ? ? Assert.assertEquals("bb",userRepository.findByUserNameOrEmail("bb","cc@126.com").getNickName()); ? ? ? ? userRepository.delete(userRepository.findByUserName("aa1")); ? ? } }
6.基本查詢
我們可以將 Spring Data JPA 查詢分為兩種,一種是 Spring Data JPA 默認(rèn)實(shí)現(xiàn)的,另一種是需要根據(jù)查詢的情況來自行構(gòu)建。
預(yù)生成方法
預(yù)生成方法就是我們上面看到的那些方法,因?yàn)槔^承了 JpaRepository 而擁有了父類的這些內(nèi)容。
(1)繼承 JpaRepository
public interface UserRepository extends JpaRepository<User,Long> { }
(2)使用默認(rèn)方法
//所有父類擁有的方法都可以直接調(diào)用,根據(jù)方法名也可以看出它的含義。 @Test public void testBaseQuery() { userRepository.findAll(); userRepository.findById(1l); userRepository.save(user); userRepository.delete(user); userRepository.count(); userRepository.existsById(1l); // ... ?? ?}
7.自定義查詢
Spring Data JPA 可以根據(jù)接口方法名來實(shí)現(xiàn)數(shù)據(jù)庫操作,主要的語法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 后面跟屬性名稱,利用這個功能僅需要在定義的 Repository 中添加對應(yīng)的方法名即可,使用時 Spring Boot 會自動幫我們實(shí)現(xiàn),示例如下。
根據(jù)用戶名查詢用戶:
User findByUserName(String userName);
也可以加一些關(guān)鍵字 And、or:
User findByUserNameOrEmail(String username,String email);
修改、刪除、統(tǒng)計也是類似語法:
Long deleteById(Long id); Long countByUserName(String userName)
基本上 SQL 體系中的關(guān)鍵詞都可以使用,如 Like 、IgnoreCase、OrderBy:
List<User> findByEmailLike(String email); User findByUserNameIgnoreCase(String userName); List<User> findByUserNameOrderByEmailDesc(String email);
可以根據(jù)查詢的條件不斷地添加和拼接,Spring Boot 都可以正確解析和執(zhí)行,其他使用示例可以參考下表。
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1(parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1(parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1(parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> age) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
Count | countByFirstName | select count(*) from ... where x.firstName = ?1 |
Exists | existsByFirstName | like the dao.exists(Example),judge by attribution of firstName:select keyindex0_.id as col_0_0_ from key_index keyindex0_ where keyindex0_.name=? limit ? |
3、小結(jié)
第一部分講解了使用 JPA 大大解放了我們對數(shù)據(jù)庫的操作,經(jīng)常使用的 SQL 大部分都已經(jīng)被預(yù)生成,直接使用即可。另外 JPA 還有一個特點(diǎn),那就是再也不用關(guān)心數(shù)據(jù)庫的表結(jié)構(gòu)了,需要更改的時候只需要修改對應(yīng) Model 的屬性即可。在微服務(wù)架構(gòu)中,因?yàn)榉?wù)拆分得越來越小,微服務(wù)內(nèi)部只關(guān)心自己的業(yè)務(wù),需要復(fù)雜查詢的場景會越來越少,在微服務(wù)架構(gòu)中更推薦使用 JPA 技術(shù)。
二、Spring Data JPA高級用法
第一部分介紹了 Spring Data JPA 的使用方式和基本查詢,常用的增、刪、改、查需求 Spring Data JPA 已經(jīng)實(shí)現(xiàn)了。但對于復(fù)雜的數(shù)據(jù)庫場景,動態(tài)生成方法不能滿足,對此 Spring Data JPA 提供了其他的解決方案。
1、自定義 SQL 查詢
使用 Spring Data 大部分的 SQL 都可以根據(jù)方法名定義的方式來實(shí)現(xiàn),但是由于某些原因必須使用自定義的 SQL 來查詢,Spring Data 也可以完美支持。
在 SQL 的查詢方法上面使用 @Query 注解,在注解內(nèi)寫 Hql 來查詢內(nèi)容。
@Query("select u from User u") Page<User> findALL(Pageable pageable);
當(dāng)然如果感覺使用原生 SQL 更習(xí)慣,它也是支持的,需要再添加一個參數(shù) nativeQuery = true。
@Query("select * from user u where u.nick_name = ?1", nativeQuery = true) Page<User> findByNickName(String nickName, Pageable pageable);
@Query 上面的 1 代表的是方法參數(shù)里面的順序,如果有多個參數(shù)也可以按照這個方式添加 1、2、3....。除了按照這種方式傳參外,還可以使用 @Param 來支持。
@Query("select u from User u where u.nickName = :nickName") Page<User> findByNickName(@Param("nickName") String nickName, Pageable pageable);
如涉及到刪除和修改需要加上 @Modifying,也可以根據(jù)需要添加 @Transactional 對事務(wù)的支持、操作超時設(shè)置等。
@Transactional(timeout = 10) @Modifying @Query("update User set userName = ?1 where id = ?2") int modifyById(String ?userName, Long id); @Transactional @Modifying @Query("delete from User where id = ?1") void deleteById(Long id);
使用已命名的查詢
除了使用 @Query 注解外,還可以預(yù)先定義好一些查詢,并為其命名,然后再 Repository 中添加相同命名的方法,定義命名的 Query:
@Entity @NamedQueries({ ? ? ? ? @NamedQuery(name = "User.findByPassWord", query = "select u from User u where u.passWord = ?1"), ? ? ? ? @NamedQuery(name = "User.findByNickName", query = "select u from User u where u.nickName = ?1"), }) public class User { ? ? …… }
通過 @NamedQueries 注解可以定義多個命名 Query,@NamedQuery 的 name 屬性定義了 Query 的名稱,注意加上 Entity 名稱 . 作為前綴,query 屬性定義查詢語句,定義對應(yīng)的方法:
List<User> findByPassWord(String passWord); List<User> findByNickName(String nickName);
Query 查找策略
到此,我們有了三種方法來定義 Query:(1)通過方法名自動創(chuàng)建 Query,(2)通過 @Query 注解實(shí)現(xiàn)自定義 Query,(3)通過 @NamedQuery 注解來定義 Query。那么,Spring Data JPA 如何來查找這些 Query 呢?
通過配置 @EnableJpaRepositories 的 queryLookupStrategy 屬性來配置 Query 查找策略,有如下定義。
CREATE
:嘗試從查詢方法名構(gòu)造特定于存儲的查詢。一般的方法是從方法名中刪除一組已知的前綴,并解析方法的其余部分。USE_DECLARED_QUERY
:嘗試查找已聲明的查詢,如果找不到,則拋出異常。查詢可以通過某個地方的注釋定義,也可以通過其他方式聲明。CREATE_IF_NOT_FOUND(默認(rèn))
:CREATE 和 USE_DECLARED_QUERY 的組合,它首先查找一個已聲明的查詢,如果沒有找到已聲明的查詢,它將創(chuàng)建一個自定義方法基于名稱的查詢。它允許通過方法名進(jìn)行快速查詢定義,還可以根據(jù)需要引入聲明的查詢來定制這些查詢調(diào)優(yōu)。
2、分頁查詢Spring Data JPA 已經(jīng)幫我們內(nèi)置了分頁功能
在查詢的方法中,需要傳入?yún)?shù) Pageable,當(dāng)查詢中有多個參數(shù)的時候 Pageable 建議作為最后一個參數(shù)傳入。
@Query("select u from User u") Page<User> findALL(Pageable pageable); Page<User> findByNickName(String nickName, Pageable pageable);
Pageable 是 Spring 封裝的分頁實(shí)現(xiàn)類,使用的時候需要傳入頁數(shù)、每頁條數(shù)和排序規(guī)則,Page 是 Spring 封裝的分頁對象,封裝了總頁數(shù)、分頁數(shù)據(jù)等。返回對象除使用 Page 外,還可以使用 Slice 作為返回值。
Slice<User> findByNickNameAndEmail(String nickName, String email,Pageable pageable);
Page 和 Slice 的區(qū)別如下:
Page 接口繼承自 Slice 接口,而 Slice 繼承自 Iterable 接口。
Page 接口擴(kuò)展了 Slice 接口,添加了獲取總頁數(shù)和元素總數(shù)量的方法,因此,返回 Page 接口時,必須執(zhí)行兩條 SQL,一條復(fù)雜查詢分頁數(shù)據(jù),另一條負(fù)責(zé)統(tǒng)計數(shù)據(jù)數(shù)量。
返回 Slice 結(jié)果時,查詢的 SQL 只會有查詢分頁數(shù)據(jù)這一條,不統(tǒng)計數(shù)據(jù)數(shù)量。
用途不一樣:Slice 不需要知道總頁數(shù)、總數(shù)據(jù)量,只需要知道是否有下一頁、上一頁,是否是首頁、尾頁等,比如前端滑動加載一頁可用;而 Page 知道總頁數(shù)、總數(shù)據(jù)量,可以用于展示具體的頁數(shù)信息,比如后臺分頁查詢。
@Test public void testPageQuery() ?{ int page=1,size=2; Sort sort = new Sort(Sort.Direction.DESC, "id"); Pageable pageable = PageRequest.of(page, size, sort); userRepository.findALL(pageable); userRepository.findByNickName("aa", pageable); ?? ?}
Sort
,控制分頁數(shù)據(jù)的排序,可以選擇升序和降序。PageRequest
,控制分頁的輔助類,可以設(shè)置頁碼、每頁的數(shù)據(jù)條數(shù)、排序等。
限制查詢:有時候我們只需要查詢前 N 個元素,或者只取前一個實(shí)體。
User findFirstByOrderByLastnameAsc(); User findTopByOrderByAgeDesc(); Page<User> queryFirst10ByLastname(String lastname, Pageable pageable); List<User> findFirst10ByLastname(String lastname, Sort sort); List<User> findTop10ByLastname(String lastname, Pageable pageable);
3、復(fù)雜查詢
我們可以通過 AND 或者 OR 等連接詞來不斷拼接屬性來構(gòu)建多條件查詢,但如果參數(shù)大于 6 個時,方法名就會變得非常的長,并且還不能解決動態(tài)多條件查詢的場景。到這里就需要給大家介紹另外一個利器 JpaSpecificationExecutor 了。
JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使用封裝,可以用于動態(tài)生成 Query 來滿足我們業(yè)務(wù)中的各種復(fù)雜場景。Spring Data JPA 為我們提供了 JpaSpecificationExecutor 接口,只要簡單實(shí)現(xiàn) toPredicate 方法就可以實(shí)現(xiàn)復(fù)雜的查詢。
我們來看一下 JpaSpecificationExecutor 的源碼:
public interface JpaSpecificationExecutor<T> { ? ?//根據(jù) Specification 條件查詢單個對象,注意的是,如果條件能查出來多個會報錯 ? ?T findOne(@Nullable Specification<T> spec); ? ?//根據(jù) Specification 條件查詢 List 結(jié)果 ? ?List<T> findAll(@Nullable Specification<T> spec); ? ?//根據(jù) Specification 條件,分頁查詢 ? ?Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable); ? ?//根據(jù) Specification 條件,帶排序的查詢結(jié)果 ? ?List<T> findAll(@Nullable Specification<T> spec, Sort sort); ? ?//根據(jù) Specification 條件,查詢數(shù)量 ? ?long count(@Nullable Specification<T> spec); }
JpaSpecificationExecutor 的源碼很簡單,根據(jù) Specification 的查詢條件返回 List、Page 或者 count 數(shù)據(jù)。在使用 JpaSpecificationExecutor 構(gòu)建復(fù)雜查詢場景之前,我們需要了解幾個概念:
Root root
,代表了可以查詢和操作的實(shí)體對象的根,開一個通過 get("屬性名") 來獲取對應(yīng)的值。CriteriaQuery query
,代表一個 specific 的頂層查詢對象,它包含著查詢的各個部分,比如 select 、from、where、group by、order by 等。CriteriaBuilder cb
,來構(gòu)建 CritiaQuery 的構(gòu)建器對象,其實(shí)就相當(dāng)于條件或者是條件組合,并以 Predicate 的形式返回。
使用案例: 首先定義一個 UserDetail 對象,作為演示的數(shù)據(jù)模型。
@Entity public class UserDetail { ? ? @Id ? ? @GeneratedValue ? ? private Long id; ? ? @Column(nullable = false, unique = true) ? ? private Long userId; ? ? private Integer age; ? ? private String realName; ? ? private String status; ? ? private String hobby; ? ? private String introduction; ? ? private String lastLoginIp; }
創(chuàng)建 UserDetail 對應(yīng)的 Repository:
public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>,JpaRepository<UserDetail, Long> ?{ }
定義一個查詢 Page<UserDetail> 的接口:
public interface UserDetailService { ? ? public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable); }
在 UserDetailServiceImpl 中,我們來演示 JpaSpecificationExecutor 的具體使用。
@Service public class UserDetailServiceImpl implements ?UserDetailService{ ? ? @Resource ? ? private UserDetailRepository userDetailRepository; ? ? @Override ? ? public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable){ ? ? ? ? return userDetailRepository.findAll((root, query, cb) -> { ? ? ? ? ? ? List<Predicate> predicates = new ArrayList<Predicate>(); ? ? ? ? ? ? //equal 示例 ? ? ? ? ? ? if (!StringUtils.isNullOrEmpty(detailParam.getIntroduction())){ ? ? ? ? ? ? ? ? predicates.add(cb.equal(root.get("introduction"),detailParam.getIntroduction())); ? ? ? ? ? ? } ? ? ? ? ? ? //like 示例 ? ? ? ? ? ? if (!StringUtils.isNullOrEmpty(detailParam.getRealName())){ ? ? ? ? ? ? ? ? predicates.add(cb.like(root.get("realName"),"%"+detailParam.getRealName()+"%")); ? ? ? ? ? ? } ? ? ? ? ? ? //between 示例 ? ? ? ? ? ? if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null) { ? ? ? ? ? ? ? ? Predicate agePredicate = cb.between(root.get("age"), detailParam.getMinAge(), detailParam.getMaxAge()); ? ? ? ? ? ? ? ? predicates.add(agePredicate); ? ? ? ? ? ? } ? ? ? ? ? ? //greaterThan 大于等于示例 ? ? ? ? ? ? if (detailParam.getMinAge()!=null){ ? ? ? ? ? ? ? ? predicates.add(cb.greaterThan(root.get("age"),detailParam.getMinAge())); ? ? ? ? ? ? } ? ? ? ? ? ? return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction(); ? ? ? ? }, pageable); ? ? } }
上面的示例是根據(jù)不同條件來動態(tài)查詢 UserDetail 分頁數(shù)據(jù),UserDetailParam 是參數(shù)的封裝,示例中使用了常用的大于、like、等于等示例,根據(jù)這個思路我們可以不斷擴(kuò)展完成更復(fù)雜的動態(tài) SQL 查詢。
@RunWith(SpringRunner.class) @SpringBootTest public class JpaSpecificationTests { ? ? @Resource ? ? private UserDetailService userDetailService; ? ? @Test ? ? public void testFindByCondition() ?{ ? ? ? ? int page=0,size=10; ? ? ? ? Sort sort = new Sort(Sort.Direction.DESC, "id"); ? ? ? ? Pageable pageable = PageRequest.of(page, size, sort); ? ? ? ? UserDetailParam param=new UserDetailParam(); ? ? ? ? param.setIntroduction("程序員"); ? ? ? ? param.setMinAge(10); ? ? ? ? param.setMaxAge(30); ? ? ? ? Page<UserDetail> page1=userDetailService.findByCondition(param,pageable); ? ? ? ? for (UserDetail userDetail:page1){ ? ? ? ? ? ? System.out.println("userDetail: "+userDetail.toString()); ? ? ? ? } ? ? } }
4、多表查詢
多表查詢在 Spring Data JPA 中有兩種實(shí)現(xiàn)方式,第一種是利用 Hibernate 的級聯(lián)查詢來實(shí)現(xiàn),第二種是創(chuàng)建一個結(jié)果集的接口來接收連表查詢后的結(jié)果,這里主要介紹第二種方式。
我們還是使用上面的 UserDetail 作為數(shù)據(jù)模型來使用,定義一個結(jié)果集的接口類,接口類的內(nèi)容來自于用戶表和用戶詳情表。
public interface UserInfo { ? ? String getUserName(); ? ? String getEmail(); ? ? String getAddress(); ? ? String getHobby(); }
在運(yùn)行中 Spring 會給接口(UserInfo)自動生產(chǎn)一個代理類來接收返回的結(jié)果,代碼中使用 getXX 的形式來獲取。
在 UserDetailRepository 中添加查詢的方法,返回類型設(shè)置為 UserInfo,特別注意這里的 SQL 是 HQL,需要寫類的名和屬性,這塊很容易出錯
@Query("select u.userName as userName, u.email as email, d.introduction as introduction , d.hobby as hobby from User u , UserDetail d " + ? ? ? ? ? ? "where u.id=d.userId ?and ?d.hobby = ?1 ") List<UserInfo> findUserInfo(String hobby);
測試驗(yàn)證
@Test public void testUserInfo() ?{ ? ? List<UserInfo> userInfos=userDetailRepository.findUserInfo("釣魚"); ? ? for (UserInfo userInfo:userInfos){ ? ? ? ? System.out.println("userInfo: "+userInfo.getUserName()+"-"+userInfo.getEmail()+"-"+userInfo.getHobby()+"-"+userInfo.getIntroduction()); ? ? } }
測試后返回
userInfo: aa-aa@126.com-釣魚-程序員
5、小結(jié)
Spring Data JPA 使用動態(tài)注入的原理,根據(jù)方法名動態(tài)生成方法的實(shí)現(xiàn),因此根據(jù)方法名實(shí)現(xiàn)數(shù)據(jù)查詢,即可滿足日常絕大部分使用場景。除了這種查詢方式之外,Spring Data JPA 還支持多種自定義查詢來滿足更多復(fù)雜場景的使用,兩種方式相結(jié)合可以靈活滿足項(xiàng)目對 Orm 層的需求。
通過學(xué)習(xí) Spring Data JPA 也可以看出 Spring Boot 的設(shè)計思想,80% 的需求通過默認(rèn)、簡單的方式實(shí)現(xiàn),滿足大部分使用場景,對于另外 20% 復(fù)雜的場景,提供另外的技術(shù)手段來解決。Spring Data JPA 中根據(jù)方法名動態(tài)實(shí)現(xiàn) SQL,組件環(huán)境自動配置等細(xì)節(jié),都是將 Spring Boot 約定優(yōu)于配置的思想體現(xiàn)的淋淋盡致。
三、Spring Data JPA多數(shù)據(jù)源使用
1、前言
項(xiàng)目中使用多個數(shù)據(jù)源在以往工作中比較常見,微服務(wù)架構(gòu)中不建議一個項(xiàng)目使用多個數(shù)據(jù)源。在微服務(wù)架構(gòu)下,一個微服務(wù)擁有自己獨(dú)立的一個數(shù)據(jù)庫,如果此微服務(wù)要使用其他數(shù)據(jù)庫的數(shù)據(jù),需要調(diào)用對應(yīng)庫的微服務(wù)接口來調(diào)用,而不是在一個項(xiàng)目中連接使用多個數(shù)據(jù)庫,這樣微服務(wù)更獨(dú)立、更容易水平擴(kuò)展。
雖然在微服務(wù)架構(gòu)下,不提倡一個項(xiàng)目擁有多個數(shù)據(jù)源,但在 Spring Boot 體系中,項(xiàng)目實(shí)現(xiàn)多數(shù)據(jù)源調(diào)用卻是一件很容易的事情,本節(jié)課將介紹 Spring Data JPA 多數(shù)據(jù)源的使用。
Spring Data JPA 使用多數(shù)據(jù)源的整體思路是,配置不同的數(shù)據(jù)源,在啟動時分別加載多個數(shù)據(jù)源配置,并且注入到不同的 repository 中。這樣不同的 repository 包就有不同的數(shù)據(jù)源,使用時注入對應(yīng)包下的 repository,就會使用對應(yīng)數(shù)據(jù)源的操作。
項(xiàng)目結(jié)構(gòu)如下:
onfig 啟動時加載、配置多數(shù)據(jù)源;
model 存放數(shù)據(jù)操作的實(shí)體類;
repository 目錄下有兩個包路徑 test1 和 test2 ,分別代表兩個不同數(shù)據(jù)源下的倉庫,這兩個包下的 repository 可以相同也可以不同。
2、多數(shù)據(jù)源的支持
配置 Spring Data JPA 對多數(shù)據(jù)源的使用,一般分為以下幾步:
- 創(chuàng)建數(shù)據(jù)庫 test1 和 test2
- 配置多數(shù)據(jù)源
- 不同源的 repository 放入不同包路徑
- 聲明不同的包路徑下使用不同的數(shù)據(jù)源、事務(wù)支持
- 不同的包路徑下創(chuàng)建對應(yīng)的 repository
- 測試使用
上面的一些步驟我們在前面兩課中已經(jīng)講過了,這里只補(bǔ)充不同的內(nèi)容。
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.primary.username=root spring.datasource.primary.password=root spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.secondary.username=root spring.datasource.secondary.password=root spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver # 設(shè)置將項(xiàng)目中的 SQL 格式化后打印出來,方便在開發(fā)過程中調(diào)試跟蹤。 #SQL 輸出 spring.jpa.show-sql=true spring.jpa.properties.hibernate.hbm2ddl.auto=create spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect #format 一下 SQL 進(jìn)行輸出 spring.jpa.properties.hibernate.format_sql=true
創(chuàng)建 DataSourceConfig 添加 @Configuration 注解,在項(xiàng)目啟動時運(yùn)行初始化數(shù)據(jù)庫資源。
@Configuration public class DataSourceConfig { }
在 DataSourceConfig 類中加載配置文件,利用 ConfigurationProperties 自動裝配的特性加載兩個數(shù)據(jù)源。
加載第一個數(shù)據(jù)源,數(shù)據(jù)源配置以 spring.datasource.primary 開頭,注意當(dāng)有多個數(shù)據(jù)源時,需要將其中一個標(biāo)注為 @Primary,作為默認(rèn)的數(shù)據(jù)源使用。
@Bean(name = "primaryDataSource") @Primary @ConfigurationProperties("spring.datasource.primary") public DataSource firstDataSource() { ? ? return DataSourceBuilder.create().build(); }
加載第二個數(shù)據(jù)源,數(shù)據(jù)源配置以 spring.datasource.secondary 為開頭。
@Bean(name = "secondaryDataSource") @ConfigurationProperties("spring.datasource.secondary") public DataSource secondDataSource() { ? ? return DataSourceBuilder.create().build(); }
加載 JPA 的相關(guān)配置信息,JpaProperties 是 JPA 的一些屬性配置信息,構(gòu)建 LocalEntityManagerFactoryBean 需要參數(shù)信息注入到方法中。
@Autowired private JpaProperties jpaProperties; @Autowired private HibernateProperties hibernateProperties; @Bean(name = "vendorProperties") public Map<String, Object> getVendorProperties() { ? ? return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings()); }
第一個數(shù)據(jù)源的加載配置過程
首先來看第一個數(shù)據(jù)源的加載配置過程,創(chuàng)建 PrimaryConfig 類,將上面創(chuàng)建好的第一個數(shù)據(jù)源注入到類中,添加 @Configuration 和 @EnableTransactionManagement 注解,第一個代表啟動時加載,第二個注
解表示啟用事務(wù),同時將第一個數(shù)據(jù)源和 JPA 配置信息注入到類中。
@Configuration @EnableTransactionManagement public class PrimaryConfig { ? ? @Autowired ? ? @Qualifier("primaryDataSource") ? ? private DataSource primaryDataSource; ? ? @Autowired ? ? @Qualifier("vendorProperties") ? ? private Map<String, Object> vendorProperties; }
LocalEntityManagerFactoryBean 負(fù)責(zé)創(chuàng)建一個適合于僅使用 JPA 進(jìn)行數(shù)據(jù)訪問的環(huán)境的 EntityManager,構(gòu)建的時候需要指明提示實(shí)體類的包路徑、數(shù)據(jù)源和 JPA 配置信息。
@Bean(name = "entityManagerFactoryPrimary") @Primary public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) { ? ? return builder ? ? ? ? ? ? .dataSource(primaryDataSource) ? ? ? ? ? ? .properties(vendorProperties) ? ? ? ? ? ? .packages("com.neo.model") //設(shè)置實(shí)體類所在位置 ? ? ? ? ? ? .persistenceUnit("primaryPersistenceUnit") ? ? ? ? ? ? .build(); }
利用上面的 entityManagerFactoryPrimary() 方法構(gòu)建好最終的 EntityManager。
@Bean(name = "entityManagerPrimary") @Primary public EntityManager entityManager(EntityManagerFactoryBuilder builder) { ? ? return entityManagerFactoryPrimary(builder).getObject().createEntityManager(); }
EntityManager 是 JPA 中用于增、刪、改、查的接口,它的作用相當(dāng)于一座橋梁,連接內(nèi)存中的 Java 對象和數(shù)據(jù)庫的數(shù)據(jù)存儲。使用 EntityManager 中的相關(guān)接口對數(shù)據(jù)庫實(shí)體進(jìn)行操作的時候, EntityManager
會跟蹤實(shí)體對象的狀態(tài),并決定在特定時刻將對實(shí)體的操作映射到數(shù)據(jù)庫操作上面,同時給數(shù)據(jù)源添加上 JPA 事務(wù)。
@Bean(name = "transactionManagerPrimary") @Primary PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) { ? ? return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject()); }
最后一步最為關(guān)鍵,將我們在類中配置好的 EntityManager 和事務(wù)信息注入到對應(yīng)數(shù)據(jù)源的 repository 目錄下,這樣此目錄下的 repository 就會擁有對應(yīng)數(shù)據(jù)源和事務(wù)的信息。
@Configuration @EnableTransactionManagement @EnableJpaRepositories( ? ? ? ? entityManagerFactoryRef="entityManagerFactoryPrimary", ? ? ? ? transactionManagerRef="transactionManagerPrimary", ? ? ? ? basePackages= { "com.neo.repository.test1" })//設(shè)置dao(repo)所在位置 public class PrimaryConfig {}
其中,basePackages 支持設(shè)置多個包路徑,例如,basePackages= { "com.neo.repository.test1","com.neo.repository.test3" }
第二個數(shù)據(jù)源的加載配置過程
第二個數(shù)據(jù)源配置和第一個數(shù)據(jù)源配置類似,只是方法上去掉了注解:@Primary,第二個數(shù)據(jù)源數(shù)據(jù)源加載配置類 SecondaryConfig 完整代碼如下:
@Configuration @EnableTransactionManagement @EnableJpaRepositories( ? ? ? ? entityManagerFactoryRef="entityManagerFactorySecondary", ? ? ? ? transactionManagerRef="transactionManagerSecondary", ? ? ? ? basePackages= { "com.neo.repository.test2" }) public class SecondaryConfig { ? ? @Autowired ? ? @Qualifier("secondaryDataSource") ? ? private DataSource secondaryDataSource; ? ? @Autowired ? ? @Qualifier("vendorProperties") ? ? private Map<String, Object> vendorProperties; ? ? @Bean(name = "entityManagerFactorySecondary") ? ? public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) { ? ? ? ? return builder ? ? ? ? ? ? ? ? .dataSource(secondaryDataSource) ? ? ? ? ? ? ? ? .properties(vendorProperties) ? ? ? ? ? ? ? ? .packages("com.neo.model") ? ? ? ? ? ? ? ? .persistenceUnit("secondaryPersistenceUnit") ? ? ? ? ? ? ? ? .build(); ? ? } ? ? @Bean(name = "entityManagerSecondary") ? ? public EntityManager entityManager(EntityManagerFactoryBuilder builder) { ? ? ? ? return entityManagerFactorySecondary(builder).getObject().createEntityManager(); ? ? } ? ? @Bean(name = "transactionManagerSecondary") ? ? PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) { ? ? ? ? return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject()); ? ? } }
到此多數(shù)據(jù)源的配置就完成了,項(xiàng)目中使用哪個數(shù)據(jù)源的操作,就注入對應(yīng)包下的 repository 進(jìn)行操作即可,接下來我們對上面配置好的數(shù)據(jù)源進(jìn)行測試。
創(chuàng)建 UserRepositoryTests 測試類,將兩個包下的 repository 都注入到測試類中:
@RunWith(SpringRunner.class) @SpringBootTest public class UserRepositoryTests { ? ? @Resource ? ? private UserTest1Repository userTest1Repository; ? ? @Resource ? ? private UserTest2Repository userTest2Repository; }
首先測試兩個數(shù)據(jù)庫中都存入數(shù)據(jù),數(shù)據(jù)源1插入 2 條用戶信息,數(shù)據(jù)源2插入 1 條用戶信息。
@Test public void testSave() throws Exception { ? ? Date date = new Date(); ? ? DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); ? ? String formattedDate = dateFormat.format(date); ? ? userTest1Repository.save(new User("aa", "aa123456","aa@126.com", "aa", ?formattedDate)); ? ? userTest1Repository.save(new User("bb", "bb123456","bb@126.com", "bb", ?formattedDate)); ? ? userTest2Repository.save(new User("cc", "cc123456","cc@126.com", "cc", ?formattedDate)); }
執(zhí)行完測試用例后查看數(shù)據(jù)庫,發(fā)現(xiàn) test1 庫有兩條數(shù)據(jù),test2 有一條,證明兩個數(shù)據(jù)源均保存數(shù)據(jù)正常。下面繼續(xù)測試刪除功能,使用兩個數(shù)據(jù)源的 repository 將用戶信息全部刪除。
@Test public void testDelete() throws Exception { ? ? userTest1Repository.deleteAll(); ? ? userTest2Repository.deleteAll(); }
執(zhí)行完測試用例后,發(fā)現(xiàn) test1 庫和 test2 庫用戶表的信息已經(jīng)被清空,證明多數(shù)據(jù)源刪除成功。
3、小結(jié)
Spring Data JPA 通過在啟動時加載不同的數(shù)據(jù)源,并將不同的數(shù)據(jù)源注入到不同的 repository 包下,從而實(shí)現(xiàn)項(xiàng)目多數(shù)據(jù)源操作,在項(xiàng)目中使用多數(shù)據(jù)源時,需要用到哪個數(shù)據(jù)源,只需要將對應(yīng)包下的 repository 注入操作即可。本課示例中以兩個數(shù)據(jù)源作為演示,但其實(shí)三個或者更多數(shù)據(jù)源配置、操作,都可以按照上面方法進(jìn)行配置使用。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
- SpringBoot+Spring?Data?JPA整合H2數(shù)據(jù)庫的示例代碼
- 解決Spring Data Jpa 實(shí)體類自動創(chuàng)建數(shù)據(jù)庫表失敗問題
- Springboot使用Spring Data JPA實(shí)現(xiàn)數(shù)據(jù)庫操作
- Spring Data JPA進(jìn)行數(shù)據(jù)分頁與排序的方法
- springboot使用spring-data-jpa操作MySQL數(shù)據(jù)庫
- Spring boot中使用Spring-data-jpa方便快捷的訪問數(shù)據(jù)庫(推薦)
- 詳解基于Spring Boot與Spring Data JPA的多數(shù)據(jù)源配置
- Spring?Data?JPA框架的核心概念與Repository接口詳解
相關(guān)文章
SpringBoot 返回Json實(shí)體類屬性大小寫的解決
這篇文章主要介紹了SpringBoot 返回Json實(shí)體類屬性大小寫的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Springboot內(nèi)嵌tomcat應(yīng)用原理深入分析
懂得SpringBoot的童鞋應(yīng)該很清楚,不管應(yīng)用程序是屬于何種類型,都是一個Main方法走遍天下,對于web應(yīng)用,只需要引入spring-boot-starter-web中這個依賴,應(yīng)用程序就好像直接給我們來了個tomcat一樣,對于嵌入式Tomcat,其實(shí)也非常簡單,就是調(diào)用Tomcat提供的外部類2022-09-09Springboot利于第三方服務(wù)進(jìn)行ip定位獲取省份城市
本文主要介紹了Springboot利于第三方服務(wù)進(jìn)行ip定位獲取省份城市,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07java基礎(chǔ)學(xué)習(xí)JVM中GC的算法
這篇文章主要介紹了java基礎(chǔ)學(xué)習(xí)JVM中GC的算法,通過圖文加深對GC算法思路的理解。2017-11-11Spring+SpringMVC+MyBatis深入學(xué)習(xí)及搭建(二)之MyBatis原始Dao開發(fā)和mapper代理開發(fā)
這篇文章主要介紹了Spring+SpringMVC+MyBatis深入學(xué)習(xí)及搭建(二)之MyBatis原始Dao開發(fā)和mapper代理開發(fā),需要的朋友可以參考下2017-05-05Java調(diào)用JavaScript實(shí)現(xiàn)字符串計算器代碼示例
這篇文章主要介紹了Java調(diào)用JavaScript實(shí)現(xiàn)字符串計算器代碼示例,具有一定參考價值,需要的朋友可以了解下。2017-12-12