Spring Data

1.使用 Spring Data Repositories

1.1核心概念

Spring Data Repository的核心接口是Repository。这个接口需要领域类(Domain Class)跟领域类的ID类型作为参数。这个接口主要是让你能知道继承这个类的接
口的类型。CrudRepository提供了对被管理的实体类的一些常用CRUD方法。

CrudRepository接口

public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity);①
T findOne(ID primaryKey);②
Iterable<T> findAll();③
Long count();④
void delete(T entity);⑤
boolean exists(ID primaryKey);⑥
// … 省略其他方法
}

① 保存给定的实体。
②返回指定ID的实体。
③返回全部实体。
④返回实体的总数。
⑤删除指定的实体。
⑥判断给定的ID是否存在。

通常我们要扩展功能的方法,那么我们就需要在接口上做子接口。那么我们要添加功能的时候,就在CrudRepository的基础上去增加。

PagingAndSortingRepository 是一个继承CrudRepository的接口,他扩展了分页与排序的功能。

public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}

如果我们需要查询第二页的用户数据(每页包含20条数据),那么我们可以简单的这么做:

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));

1.2查询方法

一般的增删改查功能都会有一些查询语句去查询数据库,在Spring Data,你只需要简单的做四个步骤即可实现!
- 1.声明一个继承与Repository或者它的子接口的接口,并且输入类型参数,如下:

public interface PersonRepository extends Repository<User, Long> { … }
  • 2.声明查询的方法在接口上
List<Person> findByLastname(String lastname);
  • 3.在Spring上配置
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<repositories base-package="com.acme.repositories" />
</beans>
  • 4.在业务中使用
public class SomeClient {
Autowired
private PersonRepository repository;
public void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}

1.2.1 声明Repository接口

我们的Repository会继承Repository, CrudRepository 或者PagingAndSortingRepository中的一个。但是你如果不想用SpringData的接口的话,你也可以把自己的接口声明
Repository即可。继承CrudRepository接口可以让你暴露出很多方法去操作你的实体类。如果你仅仅想暴露几个接口给其他人使用,那么你只需要从CrudRepository中拷贝几个需要的方法到自己的Repository中。

interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
T save(T entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}

在这里我们只暴露出findOne(…)跟save(…)两个方法出来。对于UserRepository,他除了有根据ID查询的方法、保存实体的方法之外,还有根据Email地址查询用户的方法。

1.2.2 定义查询方法

SpringData通过方法名有两种方式去解析出用户的查询意图:
一种是直接通过方法的命名规则去解析,第二种是通过Query去解析,那么当同时存在几种方式时,SpringData怎么去选择这两种方式呢?SpringData有一个策略去决定到底使用哪种方式:

查询策略:
接下来我们将介绍策略的信息,你可以通过配置<repository>的query-lookup-strategy属性来决定。

CREATE
通过解析方法名字来创建查询。这个策略是删除方法中固定的前缀,然后再来解析其余的部分。

USE_DECLARED_QUERY
它会根据已经定义好的语句去查询,如果找不到,则会抛出异常信息。这个语句可以在某个注解或者方法上定义。根据给定的规范来查找可用选项,如果在方法被调用时没有找到定义的查询,那么会抛出异常。

CREATE_IF_NOT_FOUND(默认)
这个策略结合了以上两个策略。他会优先查询是否有定义好的查询语句,如果没有,就根据方法的名字去构建查询。这是一个默认策略,如果不特别指定其他策略,那么这个策略会在项目中沿用。

构建查询

查询构造器是内置在SpringData中的,他是非常强大的,这个构造器会从方法名中剔除掉类似find…By, read…By, 或者get…By的前缀,然后开始解析其余的名字。
你可以在方法名中加入更多的表达式,例如你需要Distinct的约束,那么你可以在方法名中加入Distinct即可。在方法中,第一个By表示着查询语句的开始,你也可以用And或者Or来关联多个条件。

public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// 需要在语句中使用Distinct关键字,你需要做的是如下
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// 如果你需要忽略大小写,那么你要用IgnoreCase关键字,你需要做的是如下
List<Person> findByLastnameIgnoreCase(String lastname);
// 所有属性都忽略大小写呢?AllIgnoreCase可以帮到您
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// 同样的,如果需要排序的话,那你需要:OrderBy
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

根据方法名解析的查询结果跟数据库是相关,但是,还有几个问题需要注意:
- 多个属性的查询可以通过连接操作来完成,例如And,Or。当然还有其他的,例如Between,LessThan,GreaterThan,Like。这些操作时跟数据库相关的,当然你还需要看看相关的数据库文档是否支持这些操作。
- 你可以使用IngoreCase来忽略被标记的属性的大小写,也可以使用AllIgnoreCase来忽略全部的属性,当然这个也是需要数据库支持才允许的。
- 你可以使用OrderBy来进行排序查询,排序的方向是Asc跟Desc,如果需要动态排序,请看后面的章节。

1.3 自定义Repository实现

1.3.1 在repository中添加自定义方法

为了丰富我们的接口我们通常会自定义自己的接口以及对应的实现类。
自定义接口

interface UserRepositoryCustom {
public void someCustomMethod(User user);
}

自定义接口的实现类

class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod(User user) {
// 实现
  }
}

扩展CRUDRepository

public interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
// 声明查询方法
}

这样的话,就能够在常用的Repository中实现自己的方法。

Web分页
当你在写页面分页的时候,你需要填写大量的重复的代码区完成这个功能,可是现在你只需要传递一个HttpServletRequest就可以完成了,下面的例子省略了让你程序更加茁壮的异常处理代码

@Controller
@RequestMapping("/users")
public class UserController {
// DI code omitted
@RequestMapping
public String showUsers(Model model, HttpServletRequest request) {
int page = Integer.parseInt(request.getParameter("page"));
int pageSize = Integer.parseInt(request.getParameter("pageSize"));
Pageable pageable = new PageRequest(page, pageSize);
model.addAttribute("users", userService.getUsers(pageable));
return "users";
}
}

2.JPA Repositories

2.1.1Spring命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories" />
</beans>

2.1.2 基于注解的配置

@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}

上面的配置中,我们设置了一个内嵌的HSQL数据库,我们也配置了EntityManagerFactory,并且使用Hibernate作为持久层。最后也定义了JPATransactionManager。最上面我们还使用了@EnableJpaRepositories注解。

2.2 持久实体

SpringData JPA提供三种策略去监测实体是否存在:

ID属性检测(默认)默认的会通过ID来监测是否新数据,如果ID属性是空的,则认为是新数据,反则认为旧数据
实现Persistable如果实体实现了Persistable接口,那么就会通过isNew的方法来监测。详细看JavaDoc
实现EntityInfomation这个是很少用的,详细请查看JavaDoc

2.3 查询方法

声明查询语句

虽然说方法名查询的方式很方便,可是你可能会遇到方法名查询规则不支持你所要查询的关键字或者方法名写的很长,不方便,或者很丑陋。那么你就需要通过命名查询或者在方法上使用@Query来解决

2.3.2 查询创建器

通常我们可以使用方法名来解析查询语句,例如:

public interface UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}

这个查询方法相当于如下的语句:

select u from User u where u.emailAddress = ?1 and u.lastname = ?2

表格 支持的关键字

关键字例子JPQL片段
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
BetweenfindByStartDateBetween… where x.startDate between 1? and ?2
LessThanfindByAgeLessThan… where x.age < ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1(parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false

2.3.3 使用JPA命名查询

注解方式
使用注解方式,你不再需要使用配置文件,减少你的开发成本,接下来我们用注解的方式来做:

@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);
}

2.3.4 使用@Query

命名查询适合用于小数量的查询,我们可以使用@Query来替代:

public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}

LIKE查询
在表达式中使用Like查询,例子如下:

public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}

原生查询
我们可以在@Query中使用本地查询,当然,你需要设置nativeQuery=true,必须说明的是,这样的话,就不再支持分页以及排序。

public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?0", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}

2.3.5 使用命名参数

使用命名查询,我们需要用到@Param来注释到指定的参数上,如下:

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);
}

2.3.6 修改语句

之前我们演示了如何去声明查询语句,当然我们还有修改语句。修改语句的实现,我们只需要在加多一个注解@Modify:

@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

2.4 事务

默认的CRUD操作在Repository里面都是事务性的。如果是只读操作,只需要设置事务readOnly为true,其他的操作则配置为@Transaction。如果你想修改一个Repository的事务性,你只需
要在子接口中重写并且修改他的事务:
自定义事务

public interface UserRepository extends JpaRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// 其他方法
}

这样会让findAll方法在10秒内执行否则会超时的非只读事务中。
另一种修改事务行为的方式在于使用门面或者服务层中,他们包含了多个repository。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值