spring data jpa使用详解(推荐)
使用SpringdataJPA开发已经有一段时间了,这期间学习了一些东西,也遇到了一些问题,在这里和大家分享一下。
前言:
Springdata简介:
SpringData是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务。SpringData包含多个子项目:
Commons-提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化
JPA-简化创建JPA数据访问层和跨存储的持久层功能
Hadoop-基于Spring的Hadoop作业配置和一个POJO编程模型的MapReduce作业
Key-Value -集成了Redis和Riak,提供多个常用场景下的简单封装
Document-集成文档数据库:CouchDB和MongoDB并提供基本的配置映射和资料库支持
Graph-集成Neo4j提供强大的基于POJO的编程模型
GraphRooAddOn-RoosupportforNeo4j
JDBCExtensions-支持OracleRAD、高级队列和高级数据类型
Mapping-基于Grails的提供对象映射框架,支持不同的数据库
Examples-示例程序、文档和图数据库
Guidance-高级文档
一、SpringdataJPA简介
SpringdataJPA是Spring在ORM框架,以及JPA规范的基础上,封装的一套JPA应用框架,并提供了一整套的数据访问层解决方案。
二、SpringdataJPA的功能
SpringdataJPA的功能非常的强大,这里我们先跳过环境搭建这一步,来一睹SpringdataJPA的“芳容”。
SpringdataJPA提供给用户使用的,主要有以下几个接口:
- Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别
- CrudRepository:继承Repository,实现了一组CRUD相关的方法
- PagingAndSortingRepository:继承CrudRepository,实现了一组分页排序相关的方法
- JpaRepository:继承PagingAndSortingRepository,实现一组JPA规范相关的方法
- JpaSpecificationExecutor:比较特殊,不属于Repository体系,实现一组JPACriteria查询相关的方法。
三、SpringdataJPA的接口
1、CrudRepository接口
建立一个Entity类:
@Entity @Table(name="USER") publicclassUser{ @Id @GeneratedValue privateIntegerid; //账号 privateStringaccount; //姓名 privateStringname; //密码 privateStringpassword; //邮箱 privateStringemail; }
编写接口,并继承CrudRepository接口:
publicinterfaceUserRepositoryextendsCrudRepository{ }
编写测试类(为了更直观的看到效果,所有测试类都没有使用断言,直接使用的打印语句):
publicclassUserRepositoryTest{ @Autowired privateUserRepositorydao; @Test//保存 publicvoidtestSave(){ Useruser=newUser(); user.setName("chhliu"); user.setAccount("10000"); user.setEmail("chhliu@.com"); user.setPassword("123456"); dao.save(user); } @Test//批量保存 publicvoidtestSave1(){ Listusers=newArrayList (); Useruser=newUser(); user.setName("tanjie"); user.setAccount("10000"); user.setEmail("tanjie@.com"); user.setPassword("123456"); users.add(user); user=newUser(); user.setName("esdong"); user.setAccount("10000"); user.setEmail("esdong@.com"); user.setPassword("123456"); users.add(user); user=newUser(); user.setName("qinhongfei"); user.setAccount("10000"); user.setEmail("qinhongfei@.com"); user.setPassword("123456"); users.add(user); user=newUser(); user.setName("huizhang"); user.setAccount("10000"); user.setEmail("huizhang@.com"); user.setPassword("123456"); users.add(user); user=newUser(); user.setName("caican"); user.setAccount("10000"); user.setEmail("caican@.com"); user.setPassword("123456"); users.add(user); dao.save(users); } @Test//更新 publicvoidtestUpdate(){ Useruser=dao.findOne(1); user.setPassword("123890");//要想这样实现更新的功能,需要在service层加上@Transaction事物注解 } @Test//删除 publicvoidtestDelete(){ dao.delete(2); } @Test//查询所有 publicvoidtestFindAll(){ List users=(List )dao.findAll(); System.out.println(JSON.toJSONString(users)); } @Test//判断指定的id对象是否存在 publicvoidtestIsExist(){ booleanisExist=dao.exists(8); System.out.println(isExist); } @Test//通过id列表来查询 publicvoidtestFindUserByIds(){ List listIds=newArrayList (); listIds.add(2); listIds.add(4); listIds.add(7); List users=(List )dao.findAll(listIds); System.out.println(JSON.toJSONString(users)); } }
大家可以看出,到这里,我就只写了一个接口类,并没有实现这个接口类,就可以完成基本的CRUD操作。因为这个接口会自动为域对象创建增删改查方法,供业务层直接使用。
该接口的定义如下,总共提供了11个方法,基本上可以满足简单的CRUD操作以及批量操作:
@NoRepositoryBean publicinterfaceCrudRepositoryextendsRepository { Ssave(Sentity);//保存 Iterable save(Iterableentities);//批量保存 TfindOne(IDid);//根据id查询一个对象 booleanexists(IDid);//判断对象是否存在 IterablefindAll();//查询所有的对象 Iterable findAll(Iterable ids);//根据id列表查询所有的对象 longcount();//计算对象的总个数 voiddelete(IDid);//根据id删除 voiddelete(Tentity);//删除对象 voiddelete(Iterableentities);//批量删除 voiddeleteAll();//删除所有 }
2、PagingAndSortingRepository接口
PagingAndSortingRepository接口继承了CrudRepository接口。
编写接口,并继承PagingAndSortingRepository接口
publicinterfaceUserRepositoryWithOrderextends PagingAndSortingRepository{ }
编写测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext-config.xml"}) @TransactionConfiguration(defaultRollback=false) @Transactional publicclassUserRepositoryWithOrderTest{ @Autowired privateUserRepositoryWithOrderdao; @Test publicvoidtestOrder(){ Sortsort=newSort(Direction.DESC,"id"); Pageablepageable=newPageRequest(0,5,sort); Pagepage=dao.findAll(pageable); System.out.println(JSON.toJSONString(page)); System.out.println(page.getSize()); } }
只要继承了这个接口,SpringdataJPA就已经为你提供了分页和排序的功能了。该接口的定义如下,主要提供了两个方法,供使用,其中T是要操作的实体类,ID是实体类主键的类型
@NoRepositoryBean publicinterfacePagingAndSortingRepositoryextendsCrudRepository { Iterable findAll(Sortsort);//不带分页的排序 Page findAll(Pageablepageable);//带分页的排序 }
3、JpaRepository接口
如果业务需要即提供CRUD操作,又需要提供分页以及排序功能,那么就可以直接继承这个接口。该接口继承了PagingAndSortingRepository接口。
接口定义如下:
publicinterfaceJpaRepositoryextendsPagingAndSortingRepository { List findAll();//查询所有对象,不排序 List findAll(Sortsort);//查询所有对象,并排序 List save(Iterableentities);//批量保存 voidflush();//强制缓存与数据库同步 TsaveAndFlush(Tentity);//保存并强制同步 voiddeleteInBatch(Iterableentities);//批量删除 voiddeleteAllInBatch();//删除所有 }
4、JpaSpecificationExecutor接口
该接口提供了对JPACriteria查询的支持。注意,这个接口很特殊,不属于Repository体系,而SpringdataJPA不会自动扫描识别,所以会报找不到对应的Bean,我们只需要继承任意一个继承了Repository的子接口或直接继承Repository接口,SpringdataJPA就会自动扫描识别,进行统一的管理。
编写接口如下:
publicinterfaceSpecificationExecutorRepositoryextendsCrudRepository, JpaSpecificationExecutor { }
Service类:
@Service publicclassSpecificationExecutorRepositoryManager{ @Autowired privateSpecificationExecutorRepositorydao; /** *描述:根据name来查询用户 */ publicUserfindUserByName(finalStringname){ returndao.findOne(newSpecification(){ @Override publicPredicatetoPredicate(Root root,CriteriaQuery>query, CriteriaBuildercb){ Predicatepredicate=cb.equal(root.get("name"),name); returnpredicate; } }); } /** *描述:根据name和email来查询用户 */ publicUserfindUserByNameAndEmail(finalStringname,finalStringemail){ returndao.findOne(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ List list=newArrayList (); Predicatepredicate1=cb.equal(root.get("name"),name); Predicatepredicate2=cb.equal(root.get("email"),email); list.add(predicate1); list.add(predicate2); //注意此处的处理 Predicate[]p=newPredicate[list.size()]; returncb.and(list.toArray(p)); } }); } /** *描述:组合查询 */ publicUserfindUserByUser(finalUseruserVo){ returndao.findOne(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ Predicatepredicate=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())); returnpredicate; } }); } /** *描述:范围查询in方法,例如查询用户id在[2,10]中的用户 */ publicList findUserByIds(finalList ids){ returndao.findAll(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ returnroot.in(ids); } }); } /** *描述:范围查询gt方法,例如查询用户id大于9的所有用户 */ publicList findUserByGtId(finalintid){ returndao.findAll(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ returncb.gt(root.get("id").as(Integer.class),id); } }); } /** *描述:范围查询lt方法,例如查询用户id小于10的用户 */ publicList findUserByLtId(finalintid){ returndao.findAll(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ returncb.lt(root.get("id").as(Integer.class),id); } }); } /** *描述:范围查询between方法,例如查询id在3和10之间的用户 */ publicList findUserBetweenId(finalintstart,finalintend){ returndao.findAll(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ returncb.between(root.get("id").as(Integer.class),start,end); } }); } /** *描述:排序和分页操作 */ publicPage findUserAndOrder(finalintid){ Sortsort=newSort(Direction.DESC,"id"); returndao.findAll(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ returncb.gt(root.get("id").as(Integer.class),id); } },newPageRequest(0,5,sort)); } /** *描述:只有排序操作 */ publicList findUserAndOrderSecondMethod(finalintid){ returndao.findAll(newSpecification (){ @Override publicPredicatetoPredicate(Root root, CriteriaQuery>query,CriteriaBuildercb){ cb.gt(root.get("id").as(Integer.class),id); query.orderBy(cb.desc(root.get("id").as(Integer.class))); returnquery.getRestriction(); } }); } }
测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext-config.xml"}) @TransactionConfiguration(defaultRollback=false) @Transactional publicclassSpecificationExecutorRepositoryManagerTest{ @Autowired privateSpecificationExecutorRepositoryManagermanager; @Test publicvoidtestFindUserByName(){ Useruser=manager.findUserByName("chhliu"); System.out.println(JSON.toJSONString(user)); } @Test publicvoidtestFindUserByNameAndEmail(){ Useruser=manager.findUserByNameAndEmail("chhliu","chhliu@.com"); System.out.println(JSON.toJSONString(user)); } @Test publicvoidtestFindUserByUserVo(){ Useruser=newUser(); user.setName("chhliu"); user.setEmail("chhliu@.com"); Useru=manager.findUserByUser(user); System.out.println(JSON.toJSONString(u)); } @Test publicvoidtestFindUserByIds(){ Listusers=manager.findUserByIds(newArrayList (Arrays.asList(1,3,5,6))); System.out.println(JSON.toJSONString(users)); } @Test publicvoidtestFindUserByGtId(){ List users=manager.findUserByGtId(5); System.out.println(JSON.toJSONString(users)); } @Test publicvoidtestFindUserByLtId(){ List users=manager.findUserByLtId(5); System.out.println(JSON.toJSONString(users)); } @Test publicvoidtestFindUserBetweenId(){ List users=manager.findUserBetweenId(4,9); System.out.println(JSON.toJSONString(users)); } @Test publicvoidtestFindUserAndOrder(){ Page users=manager.findUserAndOrder(1); System.out.println(JSON.toJSONString(users)); } @Test publicvoidtestFindUserAndOrderSecondMethod(){ List users=manager.findUserAndOrderSecondMethod(1); System.out.println(JSON.toJSONString(users)); } }
5、Repository接口
这个接口是最基础的接口,只是一个标志性的接口,没有定义任何的方法,那这个接口有什么用了?既然SpringdataJPA提供了这个接口,自然是有它的用处,例如,我们有一部分方法是不想对外提供的,比如我们只想提供增加和修改方法,不提供删除方法,那么前面的几个接口都是做不到的,这个时候,我们就可以继承这个接口,然后将CrudRepository接口里面相应的方法拷贝到Repository接口就可以了。
总结:上述五个接口,开发者到底该如何选择?其实依据很简单,根据具体的业务需求,选择其中之一。因为各个接口之间并不存在功能强弱的问题。
四、SpringdataJPA的查询
1、使用@Query创建查询
@Query注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个JPQL查询语句即可。很多开发者在创建JPQL时喜欢使用命名参数来代替位置编号,@Query也对此提供了支持。JPQL语句中通过":变量"的格式来指定参数,同时在方法的参数前面使用@Param将方法参数与JPQL中的命名参数对应。此外,开发者也可以通过使用@Query来执行一个更新操作,为此,我们需要在使用@Query的同时,用@Modifying来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询操作。
编写接口,如下:
/** *描述:自定义查询,当SpringDataJPA无法提供时,需要自定义接口,此时可以使用这种方式 */ publicinterfaceUserDefineBySelfextendsJpaRepository{ /** *命名参数 *描述:推荐使用这种方法,可以不用管参数的位置 */ @Query("selectufromUseruwhereu.name=:name") UserfindUserByName(@Param("name")Stringname); /** *索引参数 *描述:使用?占位符 */ @Query("selectufromUseruwhereu.email=?1")//1表示第一个参数 UserfindUserByEmail(Stringemail); /** *描述:可以通过@Modifying和@Query来实现更新 *注意:Modifyingqueries的返回值只能为void或者是int/Integer */ @Modifying @Query("updateUserusetu.name=:namewhereu.id=:id") intupdateUserById(@Param("name")Stringname,@Param("id")intid); }
注:@Modifying注解里面有一个配置clearAutomatically
它说的是可以清除底层持久化上下文,就是entityManager这个类,我们知道jpa底层实现会有二级缓存,也就是在更新完数据库后,如果后面去用这个对象,你再去查这个对象,这个对象是在一级缓存,但是并没有跟数据库同步,这个时候用clearAutomatically=true,就会刷新hibernate的一级缓存了,不然你在同一接口中,更新一个对象,接着查询这个对象,那么你查出来的这个对象还是之前的没有更新之前的状态
测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext-config.xml"}) @TransactionConfiguration(defaultRollback=false) @Transactional publicclassUserDefineBySelfTest{ @Autowired privateUserDefineBySelfdao; @Test publicvoidtestFindUserByName(){ Useruser=dao.findUserByName("chhliu"); Assert.assertEquals("chhliu",user.getName()); System.out.println(user.getName()); } @Test publicvoidtestFindUserByEmail(){ Useruser=dao.findUserByEmail("chhliu@.com"); Assert.assertEquals("chhliu",user.getName()); System.out.println(user.getName()); } @Test publicvoidtestUpdateUserById(){ dao.updateUserById("tanjie",4); } }
从测试代码可以看出,我们同样只定义了接口,没有任何的实现类,但是却实现了我们所需要的功能。
2、使用@NamedQueries创建查询
命名查询是JPA提供的一种将查询语句从方法体中独立出来,以供多个方法共用的功能。SpringDataJPA对命名查询也提供了很好的支持。用户只需要按照JPA规范在orm.xml文件或者在代码中使用@NamedQuery(或@NamedNativeQuery)定义好查询语句,唯一要做的就是为该语句命名时,需要满足”DomainClass.methodName()”的命名规则。
编写接口:
publicinterfaceFindUserByNamedQueryRepositoryextendsJpaRepository{ UserfindUserWithName(@Param("name")Stringname); }
编写类:
@Entity @NamedQueries(value={ @NamedQuery(name="User.findUserWithName",query="selectufromUseruwhereu.name=:name") }) //注意:此处如果是多个方法,那么需要使用@NamedQueries,如果只有一个方法,则可以使用@NamedQuery,写法如下:@NamedQuery(name="User.findUserWithName",query="selectufromUseruwhereu.name=:name") publicclassFindUserByNamedQuery{ /** *注意:此处必须要给这个实体类定义一个唯一标识,否则会报异常 */ @Id @GeneratedValue privateIntegerid; }
注意:文中标记为红色的部分,需要一一对应,否则不满足JPA的规范。
测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext-config.xml"}) @TransactionConfiguration(defaultRollback=false) @Transactional publicclassFindUserByNamedQueryRepositoryTest{ @Autowired privateFindUserByNamedQueryRepositorydao; @Test publicvoidtestFindUserByName(){ Useruser=dao.findUserWithName("caican"); System.out.println(JSON.toJSONString(user)); } }
3、通过解析方法名创建查询
顾名思义,就是根据方法的名字,就能创建查询,也许初听起来,感觉很不可思议,等测试后才发现,原来一切皆有可能。
编写接口:
publicinterfaceSimpleConditionQueryRepositoryextendsJpaRepository{ /** *说明:按照Springdata定义的规则,查询方法以find|read|get开头 *涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写 */ /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.name=:nameandu.email=:email *参数名大写,条件名首字母大写,并且接口名中参数出现的顺序必须和参数列表中的参数顺序一致 */ UserfindByNameAndEmail(Stringname,Stringemail); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.name=?1oru.password=?2 */ List findByNameOrPassword(Stringname,Stringpassword); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.idbetween?1and?2 */ List findByIdBetween(Integerstart,Integerend); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.id1 */ List findByIdLessThan(Integerend); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.id>?1 */ List findByIdGreaterThan(Integerstart); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.nameisnull */ List findByNameIsNull(); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.nameisnotnull */ List findByNameIsNotNull(); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.namelike?1 */ List findByNameLike(Stringname); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.namenotlike?1 */ List findByNameNotLike(Stringname); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.password=?1orderbyu.iddesc */ List findByPasswordOrderByIdDesc(Stringpassword); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.name<>?1 */ List findByNameNot(Stringname); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.idin?1 */ List findByIdIn(List ids); /** *注:此处这个接口相当于发送了一条SQL:selectufromUseruwhereu.idnotin?1 */ List findByIdNotIn(List ids); }
测试类(注释部分为实际发送的sql语句):
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext-config.xml"}) @TransactionConfiguration(defaultRollback=false) @Transactional publicclassSimpleConditionQueryRepositoryTest{ @Autowired privateSimpleConditionQueryRepositorydao; /** *select user0_.idasid0_, user0_.accountasaccount0_, user0_.emailasemail0_, user0_.nameasname0_, user0_.passwordaspassword0_ from USERuser0_ where user0_.name=? anduser0_.email=?limit? */ @Test publicvoidtestFindUserByNameAndEmail(){ Useruser=dao.findByNameAndEmail("chhliu","chhliu@.com"); System.out.println(JSON.toJSONString(user)); } /** *select user0_.idasid1_, user0_.accountasaccount1_, user0_.emailasemail1_, user0_.nameasname1_, user0_.passwordaspassword1_ from USERuser0_ where user0_.name=? oruser0_.password=? */ @Test publicvoidtestFindUserByNameOrPassword(){ Listusers=dao.findByNameOrPassword("chhliu","123456"); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid1_, user0_.accountasaccount1_, user0_.emailasemail1_, user0_.nameasname1_, user0_.passwordaspassword1_ from USERuser0_ where user0_.idbetween?and? */ @Test publicvoidtestFindByIdBetween(){ List users=dao.findByIdBetween(5,8); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid1_, user0_.accountasaccount1_, user0_.emailasemail1_, user0_.nameasname1_, user0_.passwordaspassword1_ from USERuser0_ where user0_.id */ @Test publicvoidtestFindByIdLessThan(){ List users=dao.findByIdLessThan(4); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid0_, user0_.accountasaccount0_, user0_.emailasemail0_, user0_.nameasname0_, user0_.passwordaspassword0_ from USERuser0_ where user0_.id>? */ @Test publicvoidtestFindByIdGreaterThan(){ List users=dao.findByIdGreaterThan(6); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid0_, user0_.accountasaccount0_, user0_.emailasemail0_, user0_.nameasname0_, user0_.passwordaspassword0_ from USERuser0_ where user0_.nameisnull */ @Test publicvoidtestFindByNameIsNull(){ List users=dao.findByNameIsNull(); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid1_, user0_.accountasaccount1_, user0_.emailasemail1_, user0_.nameasname1_, user0_.passwordaspassword1_ from USERuser0_ where user0_.nameisnotnull */ @Test publicvoidtestFindByNameIsNotNull(){ List users=dao.findByNameIsNotNull(); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid1_, user0_.accountasaccount1_, user0_.emailasemail1_, user0_.nameasname1_, user0_.passwordaspassword1_ from USERuser0_ where user0_.namelike? */ @Test publicvoidtestFindByNameLike(){ List users=dao.findByNameLike("chhliu"); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid0_, user0_.accountasaccount0_, user0_.emailasemail0_, user0_.nameasname0_, user0_.passwordaspassword0_ from USERuser0_ where user0_.namenotlike? */ @Test publicvoidtestFindByNameNotLike(){ List users=dao.findByNameNotLike("chhliu"); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid0_, user0_.accountasaccount0_, user0_.emailasemail0_, user0_.nameasname0_, user0_.passwordaspassword0_ from USERuser0_ where user0_.password=? orderby user0_.iddesc */ @Test publicvoidtestFindByPasswordOrderByIdDesc(){ List users=dao.findByPasswordOrderByIdDesc("123456"); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid1_, user0_.accountasaccount1_, user0_.emailasemail1_, user0_.nameasname1_, user0_.passwordaspassword1_ from USERuser0_ where user0_.name<>? */ @Test publicvoidtestFindByNameNot(){ List users=dao.findByNameNot("chhliu"); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid1_, user0_.accountasaccount1_, user0_.emailasemail1_, user0_.nameasname1_, user0_.passwordaspassword1_ from USERuser0_ where user0_.idin( ?,?,?,? ) */ @Test publicvoidtestFindByIdIn(){ List users=dao.findByIdIn(newArrayList (Arrays.asList(3,4,6,8))); System.out.println(JSON.toJSONString(users)); } /** *select user0_.idasid0_, user0_.accountasaccount0_, user0_.emailasemail0_, user0_.nameasname0_, user0_.passwordaspassword0_ from USERuser0_ where user0_.idnotin( ?,?,?,? ) */ @Test publicvoidtestFindByIdNotIn(){ List users=dao.findByIdNotIn(newArrayList (Arrays.asList(3,4,6,8))); System.out.println(JSON.toJSONString(users)); } }
这里,我们只定义了一个接口,接口里面只有方法,但是没有任何的实现,却完成了各种操作。
看到这里,估计很多人都会问,SpringdataJPA是怎么做到的了?原来,框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是Sort或者Pageable类型,也会提取相关的信息,以便按规则进行排序或者分页查询。在创建查询时,我们通过在方法名中使用属性名称来表达,比如findByIdIn()。框架在解析该方法时,首先剔除findBy,然后对剩下的属性进行解析。
在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),SpringDataJPA为此提供了一些表达条件查询的关键字,大致如下:
And---等价于SQL中的and关键字,比如findByUsernameAndPassword(Stringuser,Striangpwd)
Or---等价于SQL中的or关键字,比如findByUsernameOrAddress(Stringuser,Stringaddr)
Between---等价于SQL中的between关键字,比如findBySalaryBetween(intmax,intmin)
LessThan---等价于SQL中的"<",比如findBySalaryLessThan(intmax)
GreaterThan---等价于SQL中的">",比如findBySalaryGreaterThan(intmin)
IsNull---等价于SQL中的"isnull",比如findByUsernameIsNull()
IsNotNull---等价于SQL中的"isnotnull",比如findByUsernameIsNotNull()
NotNull---与IsNotNull等价
Like---等价于SQL中的"like",比如findByUsernameLike(Stringuser)
NotLike---等价于SQL中的"notlike",比如findByUsernameNotLike(Stringuser)
OrderBy---等价于SQL中的"orderby",比如findByUsernameOrderBySalaryAsc(Stringuser)
Not---等价于SQL中的"!=",比如findByUsernameNot(Stringuser)
In---等价于SQL中的"in",比如findByUsernameIn(Collection
NotIn---等价于SQL中的"notin",比如findByUsernameNotIn(Collection
五、创建查询的顺序
SpringDataJPA在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?为此,
create---通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过@Query指定的查询语句,都将会被忽略。
create-if-not-found---如果方法通过@Query指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方法名字来创建查询。这是query-lookup-strategy属性的默认值。
use-declared-query---如果方法通过@Query指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常。
六、SpringDataJPA对事务的支持
细心的读者也许从上面的代码中看出了一些端倪,我们在使用SpringdataJPA的时候,只是定义了接口,在使用的时候,直接注入就可以了,并没有做与事物相关的任何处理,但实际上,事物已经起到效果了,这又是为什么了?
默认情况下,SpringDataJPA实现的方法都是使用事务的。针对查询类型的方法,其等价于@Transactional(readOnly=true);增删改类型的方法,等价于@Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。
如果用户觉得有必要,可以在接口方法上使用@Transactional显式指定事务属性,该值覆盖SpringDataJPA提供的默认值。同时,开发者也可以在业务层方法上使用@Transactional指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。