本文摘译自官方文档第四章《JPA Repositories》。版本:2.0.3.RELEASE
基本配置
这里是Spring Data JPA的注解风格的配置类示例。(为便于描述,后文直接称Spring Data JPA为框架)。
@Configuration @EnableJpaRepositories @EnableTransactionManagement class ApplicationConfig { @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); return builder.setType(EmbeddedDatabaseType.HSQL).build(); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setGenerateDdl(true); LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan("com.acme.domain"); factory.setDataSource(dataSource()); return factory; } @Bean public PlatformTransactionManager transactionManager() { JpaTransactionManager txManager = new JpaTransactionManager(); txManager.setEntityManagerFactory(entityManagerFactory()); return txManager; } }
上面这个例子展示了使用Spring的JDBC API - EmbeddedDatabaseBuilder
设置嵌入式HSQL数据库。然后用Hibernate实现持久化机制。这里使用了LocalContainerEntityManagerFactoryBean
而不是EntityManagerFactory
,是因为前者可以更好的处理异常。还有一个基础组件就是JpaTransactionManager
。最后使用@EnableJpaRepositories
注解保证每一个注解了@Repository
的仓储类抛出的异常可以转入到Spring的DataAccessException
异常体系。如果没有指定基础package,就默认为配置类所在的package。
持久化对象
存储持久化对象可以使用CrudRepository.save
方法。这个方法将持久化对象的持久化(persist)和合并(merge)抽象为一个方法。如果对象还没有持久化,就会调用entityManager.persist
方法。如果已经持久化,就会调用entityManager.merge
方法。
如何检查实体类的状态
- 框架默认会检查实体类的主键属性的值,如果为null就表示尚未持久化。
- 如果实体类实现了
Persistable
接口,框架会调用isNew
方法。 - 还可以实现
EntityInformation
接口,但这个方法比较复杂,一般不怎么用,详细请研究文档。
查询方法
框架支持函数命名的查询方法定义,也支持注解方式。
函数命名的关键字,可以看文档。
NamedQuery
@NamedQuery
注解可以自定义查询语句。这个注解使用在实体类上。
@Entity @NamedQuery(name = "User.findByEmailAddress", query = "select u from User u where u.emailAddress = ?1") public class User { ... }
仓储接口的定义。
public interface UserRepository extends JpaRepository<User, Long> { List<User> findByLastname(String lastname); User findByEmailAddress(String emailAddress); }
当调用接口方法时,框架首先根据实体类查找是否注解了方法名对应的自定义查询语句。例如,调用findByEmailAddress
的时候,找到了实体类注解的方法select u from User u where u.emailAddress = ?1
。
Query
上面那个方法多少有点不直观。@Query
注解可以直接在接口方法上注明自定义的查询语句。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
在实际应用中,相比@NamedQuery
注解,@Query
注解有更高的优先级。
如果@Query
注解的native
值为true
,方法就可以直接执行SQL语句查询了。
不过,对于这种SQL语句,文档声称目前不支持动态排序查询。对于分页,用于需要指定计数查询语句.
public interface UserRepository extends JpaRepository<User, Long> { @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1", countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1", nativeQuery = true) Page<User> findByLastname(String lastname, Pageable pageable); }
排序
Sort
和@Query
配合使用比较方便。Sort
构造器参数必须是查询结果返回的字段,不接受SQL函数。要使用SQL函数,应该用JpaSort.unsafe
。
public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.lastname like ?1%") List<User> findByAndSort(String lastname, Sort sort); @Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%") List<Object[]> findByAsArrayAndSort(String lastname, Sort sort); } repo.findByAndSort("lannister", new Sort("firstname")); // 1 repo.findByAndSort("stark", new Sort("LENGTH(firstname)")); // 2 repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); // 3 repo.findByAsArrayAndSort("bolton", new Sort("fn_len")); // 4
上面第二个调用是会抛出异常的,应该像第三个方法那样调用。
如何使用命名参数
框架默认使用的占位符是按照参数顺序,这样不太直观。使用命名参数,代码能更直观。
public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname") User findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname); }
SpEL表达式
框架还吃支持在@Query
注解中使用SpEL表达式。
SpEL表达式中可以使用#{#entityName}
特指实体类的名称。这个与实体类的@Entity
注解的name
属性参数一致。
@Entity public class User { @Id @GeneratedValue Long id; String lastname; } public interface UserRepository extends JpaRepository<User,Long> { @Query("select u from #{#entityName} u where u.lastname = ?1") List<User> findByLastname(String lastname); }
这种定义方式通常用于定义范型仓储接口。
@MappedSuperclass public abstract class AbstractMappedType { … String attribute } @Entity public class ConcreteType extends AbstractMappedType { … } @NoRepositoryBean public interface MappedTypeRepository<T extends AbstractMappedType> extends Repository<T, Long> { @Query("select t from #{#entityName} t where t.attribute = ?1") List<T> findAllByAttribute(String attribute); } public interface ConcreteRepository extends MappedTypeRepository<ConcreteType> { … }
修改式查询
对于update
或者delete
这样的修改式查询,需要在@Query
注解上增加@Modifying
注解。执行过查询之后,EntityManager
有可能会存在过时的实体对象。但是,EntityManager
默认不会自动更新,因为调用EntityManager.clear
方法会抹去EntityManager
所有的未提交修改。如果确认要自动更新,需要将@Modifying
注解的clearAutomatically
属性设置为true
。
框架支持命名式删除语句,也支持注解式。
interface UserRepository extends Repository<User, Long> { void deleteByRoleId(long roleId); @Modifying @Query("delete from User u where user.role.id = ?1") void deleteInBulkByRoleId(long roleId); }
两者在运行时有一个很大的区别。后者仅仅执行JPQL查询,不会触发任何生命周期回调。而前者会在执行完查询之后,调用CrudRepository.delete(Iterable<User> users)
方法,从而触发@PreRemove
回调。
QueryHints
@QueryHints
注解支持对查询语句进行微调。例如,设置缓存、设置锁超时等等。
可以看看这篇文章,讲的不错。
public interface UserRepository extends Repository<User, Long> { @QueryHints(value = { @QueryHint(name = "name", value = "value")}, forCounting = false) Page<User> findByLastname(String lastname, Pageable pageable); }
@QueryHints
的value
项是一组@QueryHint
,另一个forCounting
表示是否为可能的聚合查询应用这些微调。例子中,分页查询回去查询总页数,这个子查询不会应用微调。
配置加载计划
@EntityGraph
和@NamedEntityGraph
配合使用可以实现懒加载多级关联对象。
@NamedEntityGraph
注解在实体类上,表示的是加载计划。
@Entity @NamedEntityGraph(name = "GroupInfo.detail", attributeNodes = @NamedAttributeNode("members")) public class GroupInfo { // default fetch mode is lazy. @ManyToMany List<GroupMember> members = new ArrayList<GroupMember>(); ... }
@EntityGraph
表示要执行的加载计划。
@Repository public interface GroupRepository extends CrudRepository<GroupInfo, String> { @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD) GroupInfo getByGroupName(String name); }
也可以不用@NamedEntityGraph
注解,而是直接使用属性attributePaths
临时设置查询计划。
@Repository public interface GroupRepository extends CrudRepository<GroupInfo, String> { @EntityGraph(attributePaths = { "members" }) GroupInfo getByGroupName(String name); }
这个说起来很多内容,具体研究一下JPA 2.1规范的3.7.4章节。
存储过程的调用
假设数据库中有这样的存储过程。
/; DROP procedure IF EXISTS plus1inout /; CREATE procedure plus1inout (IN arg int, OUT res int) BEGIN ATOMIC set res = arg + 1; END /;
这是一个原子加一的方法。
首先要在实体类上声明过程。
@Entity @NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) }) public class User {}
然后再仓储接口中声明方法。以下四种方式是等效的。
@Procedure("plus1inout") Integer explicitlyNamedPlus1inout(Integer arg);
@Procedure(procedureName = "plus1inout") Integer plus1inout(Integer arg);
@Procedure(name = "User.plus1") Integer entityAnnotatedCustomNamedProcedurePlus1(@Param("arg") Integer arg);
@Procedure Integer plus1(@Param("arg") Integer arg);
Specification
JPA 2.0 引入了criteria
API能够以代码的方式构建查询。criteria
API其实就是为领域类的查询操作构建where子句。退一步来看,其实criteria
也就是一种谓词(predicate)。Spring Data JPA框架接受了Eric Evans的《Domain Driven Design》一书的Specification概念,拥有与criteria
相似的API。
首先,仓储接口必须继承JpaSpecificationExecutor
接口。
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor { … }
该接口定义了一系列方法,可以实现谓词的可变性。
List<T> findAll(Specification<T> spec);
实际上,Specification
也是一个接口。
public interface Specification<T> { Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder); }
Specification
可以很方便的构建新谓词。看个例子。
先定义基础的Specification
。
public class CustomerSpecs { public static Specification<Customer> isLongTermCustomer() { return new Specification<Customer>() { public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder builder) { LocalDate date = new LocalDate().minusYears(2); return builder.lessThan(root.get(_Customer.createdAt), date); } }; } public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) { return new Specification<Customer>() { public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) { // build query here } }; } }
这时使用方法。
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
这样可以构建新的复杂谓词。
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR); List<Customer> customers = customerRepository.findAll( where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
事务
仓储接口对象的CRUD方法均默认具备事务性。读取查询的readonly
属性默认为true
。具体可看文档SimpleJpaRepository。要想修改事务配置,需要覆盖原来的方法。
public interface UserRepository extends CrudRepository<User, Long> { @Override @Transactional(timeout = 10) public List<User> findAll(); // Further query method declarations }
上面这个例子设置了10s超时。
还有一种方法是在service层进行调整。
@Service class UserManagementImpl implements UserManagement { private final UserRepository userRepository; private final RoleRepository roleRepository; @Autowired public UserManagementImpl(UserRepository userRepository, RoleRepository roleRepository) { this.userRepository = userRepository; this.roleRepository = roleRepository; } @Transactional public void addRoleToAllUsers(String roleName) { Role role = roleRepository.findByName(roleName); for (User user : userRepository.findAll()) { user.addRole(role); userRepository.save(user); } }
上面这个例子实现了addRoleToAllUsers
方法的事务性,而方法内部调用的事务性会被忽视。如果想要在facade里面配置事务性,需要增加注解@EnableTransactionManagement
。
接口定义处也可以注解@Transactional
,但是优先级低于方法定义处的同类注解。
锁
框架支持为查询操作加锁。
interface UserRepository extends Repository<User, Long> { // Plain query method @Lock(LockModeType.READ) List<User> findByLastname(String lastname); }
来源:https://www.cnblogs.com/rim99/p/8672912.html