使用Spring data JPA開發已經有一段時間了,這期間學習了一些東西,也遇到了一些問題,在這里和大家分享一下。
前言:
Spring data簡介:
Spring Data是一個用於簡化數據庫訪問,並支持雲服務的開源框架。其主要目標是使得對數據的訪問變得方便快捷,並支持map-reduce框架和雲計算數據服務。 Spring Data 包含多個子項目:
Commons - 提供共享的基礎框架,適合各個子項目使用,支持跨數據庫持久化
JPA - 簡化創建JPA 數據訪問層和跨存儲的持久層功能
Hadoop - 基於Spring 的Hadoop 作業配置和一個POJO 編程模型的MapReduce 作業
Key-Value - 集成了Redis 和Riak ,提供多個常用場景下的簡單封裝
Document - 集成文檔數據庫:CouchDB 和MongoDB 並提供基本的配置映射和資料庫支持
Graph - 集成Neo4j 提供強大的基於POJO 的編程模型
Graph Roo AddOn - Roo support for Neo4j
JDBC Extensions - 支持Oracle RAD、高級隊列和高級數據類型
Mapping - 基於Grails 的提供對象映射框架,支持不同的數據庫
Examples - 示例程序、文檔和圖數據庫
Guidance - 高級文檔
一、Spring data JPA簡介
Spring data JPA是Spring在ORM框架,以及JPA規範的基礎上,封裝的一套JPA應用框架,並提供了一整套的數據訪問層解決方案。
二、Spring data JPA的功能
Spring data JPA的功能非常的強大,這裡我們先跳過環境搭建這一步,來一睹Spring data JPA的“芳容”。
Spring data JPA提供給用戶使用的,主要有以下幾個接口:
三、Spring data JPA的接口
1、CrudRepository接口
建立一個Entity類:
@Entity @Table(name="USER") public class User { @Id @GeneratedValue private Integer id; //賬號private String account; //姓名private String name; //密碼private String password; // 郵箱private String email; }編寫接口,並繼承CrudRepository接口:
public interface UserRepository extends CrudRepository<User, Integer> { }編寫測試類(為了更直觀的看到效果,所有測試類都沒有使用斷言,直接使用的打印語句):
public class UserRepositoryTest { @Autowired private UserRepository dao; @Test//保存public void testSave(){ User user = new User(); user.setName("chhliu"); user.setAccount("10000"); user.setEmail("[email protected]"); user.setPassword("123456"); dao.save(user); } @Test//批量保存public void testSave1(){ List<User> users = new ArrayList<User>(); User user = new User(); user.setName("tanjie"); user.setAccount("10000"); user.setEmail("[email protected]"); user.setPassword("123456"); users.add(user); user = new User(); user.setName("esdong"); user.setAccount("10000"); user.setEmail("[email protected]"); user.setPassword("123456"); users.add(user); user = new User(); user.setName("qinhongfei"); user.setAccount("10000"); user.setEmail("[email protected]"); user.setPassword("123456"); users.add(user); user = new User(); user.setName("huizhang"); user.setAccount("10000"); user.setEmail("[email protected]"); user.setPassword("123456"); users.add(user); user = new User(); user.setName("caican"); user.setAccount("10000"); user.setEmail("[email protected]"); user.setPassword("123456"); users.add(user); dao.save(users); } @Test//更新public void testUpdate(){ User user = dao.findOne(1); user.setPassword("123890");// 要想這樣實現更新的功能,需要在service層加上@Transaction事物註解} @Test//刪除public void testDelete(){ dao.delete(2); } @Test//查詢所有public void testFindAll(){ List<User> users = (List<User>) dao.findAll(); System.out.println(JSON.toJSONString(users)); } @Test//判斷指定的id對像是否存在public void testIsExist(){ boolean isExist = dao.exists(8); System.out.println(isExist); } @Test//通過id列表來查詢public void testFindUserByIds(){ List<Integer> listIds = new ArrayList<Integer>(); listIds.add(2); listIds.add(4); listIds.add(7); List<User> users = (List<User>) dao.findAll(listIds); System.out.println(JSON.toJSONString(users)); } }大家可以看出,到這裡,我就只寫了一個接口類,並沒有實現這個接口類,就可以完成基本的CRUD操作。因為這個接口會自動為域對象創建增刪改查方法,供業務層直接使用。
該接口的定義如下,總共提供了11個方法,基本上可以滿足簡單的CRUD操作以及批量操作:
@NoRepositoryBean public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { <S extends T> S save(S entity);//保存<S extends T> Iterable<S> save(Iterable<S> entities);//批量保存T findOne(ID id);//根據id查詢一個對象boolean exists(ID id);//判斷對像是否存在Iterable<T> findAll();//查詢所有的對象Iterable<T> findAll(Iterable<ID> ids);//根據id列表查詢所有的對象long count();//計算對象的總個數void delete(ID id);//根據id刪除void delete(T entity);//刪除對象void delete(Iterable<? extends T> entities);//批量刪除void deleteAll();//刪除所有} 2、PagingAndSortingRepository接口
PagingAndSortingRepository接口繼承了CrudRepository接口。
編寫接口,並繼承PagingAndSortingRepository接口
public interface UserRepositoryWithOrder extends PagingAndSortingRepository<User, Integer> { }編寫測試類:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" }) @TransactionConfiguration(defaultRollback = false) @Transactional public class UserRepositoryWithOrderTest { @Autowired private UserRepositoryWithOrder dao; @Test public void testOrder(){ Sort sort = new Sort(Direction.DESC, "id"); Pageable pageable = new PageRequest(0, 5, sort); Page<User> page = dao.findAll(pageable); System.out.println(JSON.toJSONString(page)); System.out.println(page.getSize()); } }只要繼承了這個接口,Spring data JPA就已經為你提供了分頁和排序的功能了。該接口的定義如下,主要提供了兩個方法,供使用,其中T是要操作的實體類,ID是實體類主鍵的類型
@NoRepositoryBean public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort sort);// 不帶分頁的排序Page<T> findAll(Pageable pageable);// 帶分頁的排序} 3、JpaRepository接口
如果業務需要即提供CRUD操作,又需要提供分頁以及排序功能,那麼就可以直接繼承這個接口。該接口繼承了PagingAndSortingRepository接口。
接口定義如下:
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> { List<T> findAll();//查詢所有對象,不排序List<T> findAll(Sort sort);//查詢所有對象,並排序<S extends T> List<S> save(Iterable<S> entities);//批量保存void flush();//強制緩存與數據庫同步T saveAndFlush(T entity);//保存並強制同步void deleteInBatch(Iterable<T> entities);//批量刪除void deleteAllInBatch();//刪除所有} 4、JpaSpecificationExecutor接口
該接口提供了對JPA Criteria查詢的支持。注意,這個接口很特殊,不屬於Repository體系,而Spring data JPA不會自動掃描識別,所以會報找不到對應的Bean,我們只需要繼承任意一個繼承了Repository的子接口或直接繼承Repository接口,Spring data JPA就會自動掃描識別,進行統一的管理。
編寫接口如下:
public interface SpecificationExecutorRepository extends CrudRepository<User, Integer>, JpaSpecificationExecutor<User> { } Service類:
@Service public class SpecificationExecutorRepositoryManager { @Autowired private SpecificationExecutorRepository dao; /** * 描述:根據name來查詢用戶*/ public User findUserByName(final String name){ return dao.findOne(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Predicate predicate = cb.equal(root.get("name"), name); return predicate; } }); } /** * 描述:根據name和email來查詢用戶*/ public User findUserByNameAndEmail(final String name, final String email){ return dao.findOne(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { List<Predicate> list = new ArrayList<Predicate>(); Predicate predicate1 = cb.equal(root.get("name"), name); Predicate predicate2 = cb.equal(root.get("email"), email); list.add(predicate1); list.add(predicate2); // 注意此處的處理Predicate[] p = new Predicate[list.size()]; return cb.and(list.toArray(p)); } }); } /** * 描述:組合查詢*/ public User findUserByUser(final User userVo){ return dao.findOne(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Predicate predicate = cb.equal(root.get("name"), userVo.getName()); cb.and(predicate, cb.equal(root.get("email"), userVo.getEmail())); cb.and(predicate, cb.equal(root.get("password"), userVo.getPassword())); return predicate; } }); } /** * 描述:範圍查詢in方法,例如查詢用戶id在[2,10]中的用戶*/ public List<User> findUserByIds(final List<Integer> ids){ return dao.findAll(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return root.in(ids); } }); } /** * 描述:範圍查詢gt方法,例如查詢用戶id大於9的所有用戶*/ public List<User> findUserByGtId(final int id){ return dao.findAll(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.gt(root.get("id").as(Integer.class), id); } }); } /** * 描述:範圍查詢lt方法,例如查詢用戶id小於10的用戶*/ public List<User> findUserByLtId(final int id){ return dao.findAll(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.lt(root.get("id").as(Integer.class), id); } }); } /** * 描述:範圍查詢between方法,例如查詢id在3和10之間的用戶*/ public List<User> findUserBetweenId(final int start, final int end){ return dao.findAll(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.between(root.get("id").as(Integer.class), start, end); } }); } /** * 描述:排序和分頁操作*/ public Page<User> findUserAndOrder(final int id){ Sort sort = new Sort(Direction.DESC, "id"); return dao.findAll(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.gt(root.get("id").as(Integer.class), id); } }, new PageRequest(0, 5, sort)); } /** * 描述:只有排序操作*/ public List<User> findUserAndOrderSecondMethod(final int id){ return dao.findAll(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { cb.gt(root.get("id").as(Integer.class), id); query.orderBy(cb.desc(root.get("id").as(Integer.class))); return query.getRestriction(); } }); } }測試類:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" }) @TransactionConfiguration(defaultRollback = false) @Transactional public class SpecificationExecutorRepositoryManagerTest { @Autowired private SpecificationExecutorRepositoryManager manager; @Test public void testFindUserByName(){ User user = manager.findUserByName("chhliu"); System.out.println(JSON.toJSONString(user)); } @Test public void testFindUserByNameAndEmail(){ User user = manager.findUserByNameAndEmail("chhliu", "[email protected]"); System.out.println(JSON.toJSONString(user)); } @Test public void testFindUserByUserVo(){ User user = new User(); user.setName("chhliu"); user.setEmail("[email protected]"); User u = manager.findUserByUser(user); System.out.println(JSON.toJSONString(u)); } @Test public void testFindUserByIds(){ List<User> users = manager.findUserByIds(new ArrayList<Integer>(Arrays.asList(1,3,5,6))); System.out.println(JSON.toJSONString(users)); } @Test public void testFindUserByGtId(){ List<User> users = manager.findUserByGtId(5); System.out.println(JSON.toJSONString(users)); } @Test public void testFindUserByLtId(){ List<User> users = manager.findUserByLtId(5); System.out.println(JSON.toJSONString(users)); } @Test public void testFindUserBetweenId(){ List<User> users = manager.findUserBetweenId(4, 9); System.out.println(JSON.toJSONString(users)); } @Test public void testFindUserAndOrder(){ Page<User> users = manager.findUserAndOrder(1); System.out.println(JSON.toJSONString(users)); } @Test public void testFindUserAndOrderSecondMethod(){ List<User> users = manager.findUserAndOrderSecondMethod(1); System.out.println(JSON.toJSONString(users)); } } 5、Repository接口
這個接口是最基礎的接口,只是一個標誌性的接口,沒有定義任何的方法,那這個接口有什麼用了?既然Spring data JPA提供了這個接口,自然是有它的用處,例如,我們有一部分方法是不想對外提供的,比如我們只想提供增加和修改方法,不提供刪除方法,那麼前面的幾個接口都是做不到的,這個時候,我們就可以繼承這個接口,然後將CrudRepository接口裡面相應的方法拷貝到Repository接口就可以了。
總結:上述五個接口,開發者到底該如何選擇?其實依據很簡單,根據具體的業務需求,選擇其中之一。因為各個接口之間並不存在功能強弱的問題。
四、Spring data JPA的查詢
1、使用@Query 創建查詢
@Query 註解的使用非常簡單,只需在聲明的方法上面標註該註解,同時提供一個JP QL 查詢語句即可。很多開發者在創建JP QL 時喜歡使用命名參數來代替位置編號,@Query 也對此提供了支持。 JP QL 語句中通過": 變量"的格式來指定參數,同時在方法的參數前面使用@Param 將方法參數與JP QL 中的命名參數對應。此外,開發者也可以通過使用@Query 來執行一個更新操作,為此,我們需要在使用@Query 的同時,用@Modifying 來將該操作標識為修改查詢,這樣框架最終會生成一個更新的操作,而非查詢操作。
編寫接口,如下:
/** * 描述:自定義查詢,當Spring Data JPA無法提供時,需要自定義接口,此時可以使用這種方式*/ public interface UserDefineBySelf extends JpaRepository<User, Integer> { /** * 命名參數* 描述:推薦使用這種方法,可以不用管參數的位置*/ @Query("select u from User u where u.name = :name") User findUserByName(@Param("name") String name); /** * 索引參數* 描述:使用?佔位符*/ @Query("select u from User u where u.email = ?1")// 1表示第一個參數User findUserByEmail(String email); /** * 描述:可以通過@Modifying和@Query來實現更新* 注意:Modifying queries的返回值只能為void或者是int/Integer */ @Modifying @Query("update User u set u.name = :name where u.id = :id") int updateUserById(@Param("name") String name, @Param("id") int id); }注:@Modifying註解裡面有一個配置clearAutomatically
它說的是可以清除底層持久化上下文,就是entityManager這個類,我們知道jpa底層實現會有二級緩存,也就是在更新完數據庫後,如果後面去用這個對象,你再去查這個對象,這個對像是在一級緩存,但是並沒有跟數據庫同步,這個時候用clearAutomatically=true,就會刷新hibernate的一級緩存了, 不然你在同一接口中,更新一個對象,接著查詢這個對象,那麼你查出來的這個對像還是之前的沒有更新之前的狀態
測試類:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" }) @TransactionConfiguration(defaultRollback = false) @Transactional public class UserDefineBySelfTest { @Autowired private UserDefineBySelf dao; @Test public void testFindUserByName(){ User user = dao.findUserByName("chhliu"); Assert.assertEquals("chhliu", user.getName()); System.out.println(user.getName()); } @Test public void testFindUserByEmail(){ User user = dao.findUserByEmail("[email protected]"); Assert.assertEquals("chhliu", user.getName()); System.out.println(user.getName()); } @Test public void testUpdateUserById(){ dao.updateUserById("tanjie", 4); } }從測試代碼可以看出,我們同樣只定義了接口,沒有任何的實現類,但是卻實現了我們所需要的功能。
2、使用@NamedQueries創建查詢
命名查詢是JPA 提供的一種將查詢語句從方法體中獨立出來,以供多個方法共用的功能。 Spring Data JPA 對命名查詢也提供了很好的支持。用戶只需要按照JPA 規範在orm.xml 文件或者在代碼中使用@NamedQuery(或@NamedNativeQuery)定義好查詢語句,唯一要做的就是為該語句命名時,需要滿足”DomainClass.methodName()”的命名規則。
編寫接口:
public interface FindUserByNamedQueryRepository extends JpaRepository<User, Integer> { User findUserWithName(@Param("name") String name); }編寫類:
@Entity @NamedQueries(value={ @NamedQuery(name="User.findUserWithName",query="select u from User u where u.name = :name") }) // 注意:此處如果是多個方法,那麼需要使用@NamedQueries,如果只有一個方法,則可以使用@NamedQuery,寫法如下:@NamedQuery(name="User.findUserWithName",query="select u from User u where u.name = :name") public class FindUserByNamedQuery { /** * 注意:此處必須要給這個實體類定義一個唯一標識,否則會報異常*/ @Id @GeneratedValue private Integer id; }注意:文中標記為紅色的部分,需要一一對應,否則不滿足JPA 的規範。
測試類:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" }) @TransactionConfiguration(defaultRollback = false) @Transactional public class FindUserByNamedQueryRepositoryTest { @Autowired private FindUserByNamedQueryRepository dao; @Test public void testFindUserByName(){ User user = dao.findUserWithName("caican"); System.out.println(JSON.toJSONString(user)); } } 3、通過解析方法名創建查詢
顧名思義,就是根據方法的名字,就能創建查詢,也許初聽起來,感覺很不可思議,等測試後才發現,原來一切皆有可能。
編寫接口:
public interface SimpleConditionQueryRepository extends JpaRepository<User, Integer> { /** * 說明:按照Spring data 定義的規則,查詢方法以find|read|get開頭* 涉及條件查詢時,條件的屬性用條件關鍵字連接,要注意的是:條件屬性首字母需大寫*/ /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.name = :name and u.email = :email * 參數名大寫,條件名首字母大寫,並且接口名中參數出現的順序必須和參數列表中的參數順序一致*/ User findByNameAndEmail(String name, String email); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.name = ?1 or u.password = ?2 */ List<User> findByNameOrPassword(String name, String password); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.id between ?1 and ?2 */ List<User> findByIdBetween(Integer start, Integer end); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.id < ?1 */ List<User> findByIdLessThan(Integer end); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.id > ?1 */ List<User> findByIdGreaterThan(Integer start); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.name is null */ List<User> findByNameIsNull(); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.name is not null */ List<User> findByNameIsNotNull(); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.name like ?1 */ List<User> findByNameLike(String name); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.name not like ?1 */ List<User> findByNameNotLike(String name); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.password = ?1 order by u.id desc */ List<User> findByPasswordOrderByIdDesc(String password); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.name <> ?1 */ List<User> findByNameNot(String name); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.id in ?1 */ List<User> findByIdIn(List<Integer> ids); /** * 注:此處這個接口相當於發送了一條SQL:select u from User u where u.id not in ?1 */ List<User> findByIdNotIn(List<Integer> ids); }測試類(註釋部分為實際發送的sql語句):
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" }) @TransactionConfiguration(defaultRollback = false) @Transactional public class SimpleConditionQueryRepositoryTest { @Autowired private SimpleConditionQueryRepository dao; /** * select user0_.id as id0_, user0_.account as account0_, user0_.email as email0_, user0_.name as name0_, user0_.password as password0_ from USER user0_ where user0_.name=? and user0_.email=? limit ? */ @Test public void testFindUserByNameAndEmail(){ User user = dao.findByNameAndEmail("chhliu", "[email protected]"); System.out.println(JSON.toJSONString(user)); } /** * select user0_.id as id1_, user0_.account as account1_, user0_.email as email1_, user0_.name as name1_, user0_.password as password1_ from USER user0_ where user0_.name=? or user0_.password=? */ @Test public void testFindUserByNameOrPassword(){ List<User> users = dao.findByNameOrPassword("chhliu", "123456"); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id1_, user0_.account as account1_, user0_.email as email1_, user0_.name as name1_, user0_.password as password1_ from USER user0_ where user0_.id between ? and ? */ @Test public void testFindByIdBetween(){ List<User> users = dao.findByIdBetween(5, 8); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id1_, user0_.account as account1_, user0_.email as email1_, user0_.name as name1_, user0_.password as password1_ from USER user0_ where user0_.id<? */ @Test public void testFindByIdLessThan(){ List<User> users = dao.findByIdLessThan(4); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id0_, user0_.account as account0_, user0_.email as email0_, user0_.name as name0_, user0_.password as password0_ from USER user0_ where user0_.id>? */ @Test public void testFindByIdGreaterThan(){ List<User> users = dao.findByIdGreaterThan(6); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id0_, user0_.account as account0_, user0_.email as email0_, user0_.name as name0_, user0_.password as password0_ from USER user0_ where user0_.name is null */ @Test public void testFindByNameIsNull(){ List<User> users = dao.findByNameIsNull(); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id1_, user0_.account as account1_, user0_.email as email1_, user0_.name as name1_, user0_.password as password1_ from USER user0_ where user0_.name is not null */ @Test public void testFindByNameIsNotNull(){ List<User> users = dao.findByNameIsNotNull(); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id1_, user0_.account as account1_, user0_.email as email1_, user0_.name as name1_, user0_.password as password1_ from USER user0_ where user0_.name like ? */ @Test public void testFindByNameLike(){ List<User> users = dao.findByNameLike("chhliu"); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id0_, user0_.account as account0_, user0_.email as email0_, user0_.name as name0_, user0_.password as password0_ from USER user0_ where user0_.name not like ? */ @Test public void testFindByNameNotLike(){ List<User> users = dao.findByNameNotLike("chhliu"); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id0_, user0_.account as account0_, user0_.email as email0_, user0_.name as name0_, user0_.password as password0_ from USER user0_ where user0_.password=? order by user0_.id desc */ @Test public void testFindByPasswordOrderByIdDesc(){ List<User> users = dao.findByPasswordOrderByIdDesc("123456"); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id1_, user0_.account as account1_, user0_.email as email1_, user0_.name as name1_, user0_.password as password1_ from USER user0_ where user0_.name<>? */ @Test public void testFindByNameNot(){ List<User> users = dao.findByNameNot("chhliu"); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id1_, user0_.account as account1_, user0_.email as email1_, user0_.name as name1_, user0_.password as password1_ from USER user0_ where user0_.id in ( ? , ? , ? , ? ) */ @Test public void testFindByIdIn(){ List<User> users = dao.findByIdIn(new ArrayList<Integer>(Arrays.asList(3,4,6,8))); System.out.println(JSON.toJSONString(users)); } /** * select user0_.id as id0_, user0_.account as account0_, user0_.email as email0_, user0_.name as name0_, user0_.password as password0_ from USER user0_ where user0_.id not in ( ? , ? , ? , ? ) */ @Test public void testFindByIdNotIn(){ List<User> users = dao.findByIdNotIn(new ArrayList<Integer>(Arrays.asList(3,4,6,8))); System.out.println(JSON.toJSONString(users)); } }這裡,我們只定義了一個接口,接口裡面只有方法,但是沒有任何的實現,卻完成了各種操作。
看到這裡,估計很多人都會問,Spring data JPA是怎麼做到的了?原來,框架在進行方法名解析時,會先把方法名多餘的前綴截取掉,比如find、findBy、read、readBy、get、getBy,然後對剩下部分進行解析。並且如果方法的最後一個參數是Sort 或者Pageable 類型,也會提取相關的信息,以便按規則進行排序或者分頁查詢。在創建查詢時,我們通過在方法名中使用屬性名稱來表達,比如findByIdIn()。框架在解析該方法時,首先剔除findBy,然後對剩下的屬性進行解析。
在查詢時,通常需要同時根據多個屬性進行查詢,且查詢的條件也格式各樣(大於某個值、在某個範圍等等),Spring Data JPA 為此提供了一些表達條件查詢的關鍵字,大致如下:
And --- 等價於SQL 中的and 關鍵字,比如findByUsernameAndPassword(String user, Striang pwd)
Or --- 等價於SQL 中的or 關鍵字,比如findByUsernameOrAddress(String user, String addr)
Between --- 等價於SQL 中的between 關鍵字,比如findBySalaryBetween(int max, int min)
LessThan --- 等價於SQL 中的"<",比如findBySalaryLessThan(int max)
GreaterThan --- 等價於SQL 中的">",比如findBySalaryGreaterThan(int min)
IsNull --- 等價於SQL 中的"is null",比如findByUsernameIsNull()
IsNotNull --- 等價於SQL 中的"is not null",比如findByUsernameIsNotNull()
NotNull --- 與IsNotNull 等價
Like --- 等價於SQL 中的"like",比如findByUsernameLike(String user)
NotLike --- 等價於SQL 中的"not like",比如findByUsernameNotLike(String user)
OrderBy ---等價於SQL 中的"order by",比如findByUsernameOrderBySalaryAsc(String user)
Not --- 等價於SQL 中的"! =",比如findByUsernameNot(String user)
In --- 等價於SQL 中的"in",比如findByUsernameIn(Collection<String> userList) ,方法的參數可以是Collection 類型,也可以是數組或者不定長參數
NotIn --- 等價於SQL 中的"not in",比如findByUsernameNotIn(Collection<String> userList) ,方法的參數可以是Collection 類型,也可以是數組或者不定長參數
五、創建查詢的順序
Spring Data JPA 在為接口創建代理對象時,如果發現同時存在多種上述情況可用,它該優先採用哪種策略呢?為此,<jpa:repositories> 提供了query-lookup-strategy 屬性,用以指定查找的順序。它有如下三個取值:
create --- 通過解析方法名字來創建查詢。即使有符合的命名查詢,或者方法通過@Query 指定的查詢語句,都將會被忽略。
create-if-not-found --- 如果方法通過@Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來創建查詢。這是query-lookup-strategy 屬性的默認值。
use-declared-query --- 如果方法通過@Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則拋出異常。
六、Spring Data JPA 對事務的支持
細心的讀者也許從上面的代碼中看出了一些端倪,我們在使用Spring data JPA的時候,只是定義了接口,在使用的時候,直接注入就可以了,並沒有做與事物相關的任何處理,但實際上,事物已經起到效果了,這又是為什麼了?
默認情況下,Spring Data JPA 實現的方法都是使用事務的。針對查詢類型的方法,其等價於@Transactional(readOnly=true);增刪改類型的方法,等價於@Transactional。可以看出,除了將查詢的方法設為只讀事務外,其他事務屬性均採用默認值。
如果用戶覺得有必要,可以在接口方法上使用@Transactional 顯式指定事務屬性,該值覆蓋Spring Data JPA 提供的默認值。同時,開發者也可以在業務層方法上使用@Transactional 指定事務屬性,這主要針對一個業務層方法多次調用持久層方法的情況。持久層的事務會根據設置的事務傳播行為來決定是掛起業務層事務還是加入業務層的事務。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。