1.SpringData方法定义规范
通过上面的QucikStart的案例,了解到在使用SpringData时只需要定义Dao层接口及定义方法就可以操作数据库。但是,这个Dao层接口中的方法也是有定义规范的,只有按这个规范来,SpringData才能识别并实现该方法。下面来说说方法定义的规范。
1.1.简单的条件查询的方法定义规范
方法定义规范如下:
简单条件查询:查询某一个实体或者集合
按照SpringData规范,查询方法于find|read|get开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:属性首字母需要大写。
- 示例1:定义一个通过id和name查询的方法
/**
* JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
* JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
*/
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
//定义一个通过id和name查询的方法
Customer findByCustIdAndCustName(Long id, String name);
}
- 测试
@Test
public void test2(){
Customer c = customerDao.findByCustIdAndCustName(1L, "测试");
System.out.println(c);
}
1.2.支持的关键字
直接在接口中定义方法,如果符合规范,则不用写实现。目前支持的关键字写法如下:
- CustomerDao接口新增代码如下:
/**
* JpaRepository<实体类类型,实体类的主键类型>:用来完成基本CRUD操作
*/
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
//SpringData怕实现了那么接口,方法不够!还允许自己来写方法!!
//如果自己来定义方法,那么方法名必须满足一个规范!
// SpringData会根据方法名自动生成SQL!
// 带着镣铐跳舞!
public Customer findCustomerByCustIdOrCustName(Long custId, String custName);
//select * from cust_cusmtomet where cust_id>?1
public List<Customer> findCustomerByCustIdGreaterThan(Long custId);
/*
Hibernate:
select
customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from
cst_customer customer0_
where
customer0_.cust_id<?
*/
List<Customer> findCustomerByCustIdLessThan(Long id);
/*
select
customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from
cst_customer customer0_
where
customer0_.cust_address like ?
*/
List<Customer> findCustomerByCustAddressLike(String address);
/*
select
count(customer0_.cust_id) as col_0_0_
from
cst_customer customer0_
where
customer0_.cust_address like ?
*/
long countCustomerByCustAddressLike(String address);
}
- 测试
@Test
public void test3(){
List<Customer> list= customerDao.findByCustIdLessThan(1L);
System.out.println(list);
}
@Test
public void test4(){
List<Customer> list= customerDao.findByCustAddressLike( "123");
System.out.println(list);
}
@Test
public void test5(){
long l = customerDao.countByCustAddressLike( "123");
System.out.println(l);
}
1.3.方法解析流程
通过以上的学习,掌握了在接口中定义方法的规则,我们就可以定义出很多不用写实现的方法了。这里再介绍下查询方法的解析的流程,掌握了这个流程,对于定义方法有更深的理解。
/*
select
customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from
cst_customer customer0_
where
customer0_.cust_id=?
and customer0_.cust_name=?
*/
Customer findCustomerByCustIdAndCustName(Long id, String name);
框架在解析该方法时,流程如下:
首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Customer ,先判断CustIdAndCustName(根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续往下走从右往左截取第一个大写字母开头的字符串(此处为Name),然后检查剩下的字符串(CustIdAnd)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复这一步,继续从右往左截取;最后(CustId)为查询实体的一个属性
接着处理剩下部分(AndCustName),先判断 Id所对应的类型是否有AndCustName属性,如果有,则表示该方法最终是根据 “customer.CustId.AndCustName” 的取值进行查询;否则继续按照步骤3的规则从右往左截取,最终表示根据 “customer.CustId.And.CustName” 的值进行查询。
2.@Query注解
通过上面的学习,在dao层接口按照规则来定义方法就可以不用写方法的实现也能操作数据库。但是如果一个条件查询有多个条件时,写出来的方法名字就太长了,所以就想着不按规则来定义方法名。我们可以使用@Query这个注解来实现这个功能,在定义的方法上加上@Query这个注解,将查询语句声明在注解中,也可以查询到数据库的数据。
2.1.使用Query结合jpql语句实现自定义查询
在CustomerDao接口中声明方法,放上面加上Query注解,注解里面写jpql语句,代码如下:
// 自定义的查询,直接写jpql语句; 查询id<? 或者 名字 like?的person集合
@Query("from Customer where custId < ?1 or custName like ?2")
List<Customer> testCustomer(Long custId, String custName);
// 自定义查询之子查询,直接写jpql语句; 查询出id最大的person
@Query("from Customer where custId = (select max(c.custName) from Customer as c)")
Customer testSubquery();
测试:
@Test
public void test6(){
List<Customer> list= customerDao.testCustomer(1L,"测试");
System.out.println(list);
}
@Test
public void test7(){
Customer c= customerDao.testSubquery();
System.out.println(c);
}
2.2.索引参数和命名参数
在写jpql语句时,查询条件的参数的表示有以下2种方式:
- 索引参数方式如下图所示,索引值从1开始,查询中’?x’的个数要和方法的参数个数一致,且顺序也要一致
/*
Hibernate:
select
customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from
cst_customer customer0_
where
customer0_.cust_id<?
or customer0_.cust_name like ?
*/
@Query("from Customer where custId<?1 or custName like ?2")
List<Customer> testCustomer1(Long custId,String custName);
- 命名参数方式(推荐使用这种方式)如下图所示,可以用’:参数名’的形式,在方法参数中使用@Param(“参数名”)注解,这样就可以不用按顺序来定义形参
/*
Hibernate:
select
customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from
cst_customer customer0_
where
customer0_.cust_id<?
or customer0_.cust_name like ?
*/
@Query("from Customer where custId<:custId or custName like :custName")
List<Customer> testCustomer2(@Param("custId") Long custId, @Param("custName") String custName);
特殊情况,那就是自定义的Query查询中jpql语句有like查询时,可以直接把%号写在参数的前后,这样传参数就不用把%号拼接进去了。使用案例如下,调用该方法时传递的参数直接传。
/*
Hibernate:
select
customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from
cst_customer customer0_
where
customer0_.cust_id<?
or customer0_.cust_name like ?
*/
@Query("from Customer where custId<?1 or custName like %?2%")
List<Customer> testCustomer4(Long custId,String custName);
/*
Hibernate:
select
customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from
cst_customer customer0_
where
customer0_.cust_id<?
or customer0_.cust_name like ?
*/
@Query("from Customer where custId<:custId or custName like %:custName%")
List<Customer> testCustomer5(@Param("custId") Long custId, @Param("custName") String custName);
2.3.使用@Query来指定使用本地SQL查询(了解)
如果不熟悉jpql语句,你也可以写sql语句查询,只需要在@Query注解中设置nativeQuery=true。直接来看案例吧。dao层接口写法如下图所示
- 示例1:把id小于3的person的name都改为’testname’
//可以通过自定义的 JPQL完成 UPDATE 和 DELETE 操作. 注意:JPQL不支持使用 INSERT
//在 @Query 注解中编写 SQL语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作
//UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作.
//默认情况下, SpringData的每个方法上有事务, 但都是一个只读事务.他们不能完成修改操作!
@Modifying
@Query("UPDATE Customer c SET c.custName = :custName WHERE c.custId < :custId")
int updateCustomerById(@Param("custId")Long custId, @Param("custName")String updateName);
由于这个更新操作,只读事务是不能实现的,因此新建CustomerService类,在Service方法中添加事务注解。CustomerService的代码如下图所示
/**
* @author bruceliu
* @create 2019-07-08 15:21
* @description
*/
@Service
public class CustomerService {
@Autowired
CustomerDao customerDao;
@Transactional(readOnly = false)
public int updateCustomerById(Long custId,String custName){
return customerDao.updateCustomerById(custId,custName);
}
}
- 测试
/**
* @author bruceliu
* @create 2019-07-06 17:29
* @description
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class CustomerDaoTest {
@Autowired
CustomerService customerService;
@Test
public void updateCustomerById(){
int count = customerService.updateCustomerById(2L, "新的Name");
System.out.println(count);
}
}
- 使用@Modifying+@Query时的注意事项:
方法返回值是int,表示影响的行数
在调用的地方必须加事务,没事务不执行,报异常!
2.4 事务
Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。
对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明
进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。