使⽤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提供给⽤户使⽤的,主要有以下⼏个接⼝:
1. Repository:仅仅是⼀个标识,表明任何继承它的均为仓库接⼝类,⽅便Spring⾃动扫描识别2. CrudRepository:继承Repository,实现了⼀组CRUD相关的⽅法
3. PagingAndSortingRepository:继承CrudRepository,实现了⼀组分页排序相关的⽅法4. JpaRepository:继承PagingAndSortingRepository,实现⼀组JPA规范相关的⽅法
5. JpaSpecificationExecutor:⽐较特殊,不属于Repository体系,实现⼀组JPA Criteria查询相关的⽅法。三、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 编写测试类(为了更直观的看到效果,所有测试类都没有使⽤断⾔,直接使⽤的打印语句): public class UserRepositoryTest { @Autowired private UserRepository dao; @Test//保存 public void testSave(){ User user = new User(); user.setName(\"chhliu\"); user.setAccount(\"10000\"); user.setEmail(\"chhliu@.com\"); user.setPassword(\"123456\"); dao.save(user); } @Test//批量保存 public void testSave1(){ List user.setName(\"esdong\"); user.setAccount(\"10000\"); user.setEmail(\"esdong@.com\"); user.setPassword(\"123456\"); users.add(user); user = new User(); user.setName(\"qinhongfei\"); user.setAccount(\"10000\"); user.setEmail(\"qinhongfei@.com\"); user.setPassword(\"123456\"); users.add(user); user = new User(); user.setName(\"huizhang\"); user.setAccount(\"10000\"); user.setEmail(\"huizhang@.com\"); user.setPassword(\"123456\"); users.add(user); user = new User(); user.setName(\"caican\"); user.setAccount(\"10000\"); user.setEmail(\"caican@.com\"); 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 @Test//判断指定的id对象是否存在 public void testIsExist(){ boolean isExist = dao.exists(8); System.out.println(isExist); } @Test//通过id列表来查询 public void testFindUserByIds(){ List List ⼤家可以看出,到这⾥,我就只写了⼀个接⼝类,并没有实现这个接⼝类,就可以完成基本的CRUD操作。因为这个接⼝会⾃动为域对象创建增删改查⽅法,供业务层直接使⽤。该接⼝的定义如下,总共提供了11个⽅法,基本上可以满⾜简单的CRUD操作以及批量操作: @NoRepositoryBean public interface CrudRepository Iterable void delete(Iterable extends T> entities);//批量删除 void deleteAll();//删除所有 } 2、PagingAndSortingRepository接⼝ PagingAndSortingRepository接⼝继承了CrudRepository接⼝。编写接⼝,并继承PagingAndSortingRepository接⼝ public interface UserRepositoryWithOrder extends PagingAndSortingRepository 编写测试类: @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 System.out.println(JSON.toJSONString(page)); System.out.println(page.getSize()); } } 只要继承了这个接⼝,Spring data JPA就已经为你提供了分页和排序的功能了。该接⼝的定义如下,主要提供了两个⽅法,供使⽤,其中T是要操作的实体类,ID是实体类主键的类型 @NoRepositoryBean public interface PagingAndSortingRepository Page 3、JpaRepository接⼝ 如果业务需要即提供CRUD操作,⼜需要提供分页以及排序功能,那么就可以直接继承这个接⼝。该接⼝继承了PagingAndSortingRepository接⼝。接⼝定义如下: public interface JpaRepository List T saveAndFlush(T entity);//保存并强制同步 void deleteInBatch(Iterable 4、JpaSpecificationExecutor接⼝ 该接⼝提供了对JPA Criteria查询的⽀持。注意,这个接⼝很特殊,不属于Repository体系,⽽Spring data JPA不会⾃动扫描识别,所以会报找不到对应的Bean,我们只需要继承任意⼀个继承了Repository的⼦接⼝或直接继承Repository接⼝,Spring data JPA就会⾃动扫描识别,进⾏统⼀的管理。编写接⼝如下: public interface SpecificationExecutorRepository extends CrudRepository Service类: @Service public class SpecificationExecutorRepositoryManager { @Autowired private SpecificationExecutorRepository dao; /** * 描述:根据name来查询⽤户 */ public User findUserByName(final String name){ return dao.findOne(new Specification @Override public Predicate toPredicate(Root 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 @Override public Predicate toPredicate(Root List 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 @Override public Predicate toPredicate(Root 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 @Override public Predicate toPredicate(Root * 描述:范围查询gt⽅法,例如查询⽤户id⼤于9的所有⽤户 */ public List @Override public Predicate toPredicate(Root } /** * 描述:范围查询lt⽅法,例如查询⽤户id⼩于10的⽤户 */ public List @Override public Predicate toPredicate(Root * 描述:范围查询between⽅法,例如查询id在3和10之间的⽤户 */ public List @Override public Predicate toPredicate(Root return cb.between(root.get(\"id\").as(Integer.class), start, end); } }); } /** * 描述:排序和分页操作 */ public Page @Override public Predicate toPredicate(Root }, new PageRequest(0, 5, sort)); } /** * 描述:只有排序操作 */ public List @Override public Predicate toPredicate(Root 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\ System.out.println(JSON.toJSONString(user)); } @Test public void testFindUserByUserVo(){ User user = new User(); user.setName(\"chhliu\"); user.setEmail(\"chhliu@.com\"); User u = manager.findUserByUser(user); System.out.println(JSON.toJSONString(u)); } @Test public void testFindUserByIds(){ List @Test public void testFindUserByGtId(){ List @Test public void testFindUserByLtId(){ List @Test public void testFindUserBetweenId(){ List System.out.println(JSON.toJSONString(users)); } @Test public void testFindUserAndOrder(){ Page @Test public void testFindUserAndOrderSecondMethod(){ List 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 * 命名参数 * 描述:推荐使⽤这种⽅法,可以不⽤管参数的位置 */ @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\ System.out.println(user.getName()); } @Test public void testFindUserByEmail(){ User user = dao.findUserByEmail(\"chhliu@.com\"); Assert.assertEquals(\"chhliu\ System.out.println(user.getName()); } @Test public void testUpdateUserById(){ dao.updateUserById(\"tanjie\ } } 从测试代码可以看出,我们同样只定义了接⼝,没有任何的实现类,但是却实现了我们所需要的功能。2、使⽤@NamedQueries创建查询 命名查询是 JPA 提供的⼀种将查询语句从⽅法体中独⽴出来,以供多个⽅法共⽤的功能。Spring Data JPA 对命名查询也提供了很好的⽀持。⽤户只需要按照 JPA 规范在 orm.xml ⽂件或者在代码中使⽤ @NamedQuery(或 @NamedNativeQuery)定义好查询语句,唯⼀要做的就是为该语句命名时,需要满⾜”DomainClass.methodName()”的 命名规则。编写接⼝: public interface FindUserByNamedQueryRepository extends JpaRepository 编写类: @Entity @NamedQueries(value={ @NamedQuery(name=\"User.findUserWithName\}) // 注意:此处如果是多个⽅法,那么需要使⽤@NamedQueries,如果只有⼀个⽅法,则可以使⽤@NamedQuery,写法如下:@NamedQuery(name=\"User.findUserWithName\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 * 说明:按照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 * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.id between ?1 and ?2 */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.id < ?1 */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.id > ?1 */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.name is null */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.name is not null */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.name like ?1 */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.name not like ?1 */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.password = ?1 order by u.id desc */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.name <> ?1 */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.id in ?1 */ List * 注:此处这个接⼝相当于发送了⼀条SQL:select u from User u where u.id not in ?1 */ List 测试类(注释部分为实际发送的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\ 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 这⾥,我们只定义了⼀个接⼝,接⼝⾥⾯只有⽅法,但是没有任何的实现,却完成了各种操作。 看到这⾥,估计很多⼈都会问,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 NotIn --- 等价于 SQL 中的 \"not in\",⽐如 findByUsernameNotIn(Collection Spring Data JPA 在为接⼝创建代理对象时,如果发现同时存在多种上述情况可⽤,它该优先采⽤哪种策略呢?为此, 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指定事务属性,这主要针对⼀个业务层⽅法多次调⽤持久层⽅法的情况。持久层的事务会根据设置的事务传播⾏为来决定是挂起业务层事务还是加⼊业务层的事务。以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。 因篇幅问题不能全部显示,请点此查看更多更全内容 S save(S entity);//保存 Iterable save(Iterable entities);//批量保存 T findOne(ID id);//根据id查询⼀个对象 boolean exists(ID id);//判断对象是否存在 Iterable List save(Iterable entities);//批量保存 void flush();//强制缓存与数据库同步