上次有写到JPA的相关知识,接下来说一下Spring Data JPA
Spring Data JPA 是Spring基于ORM框架、JPA规范封装的一套JPA应用框架,开发者可以使用很简单的代码就进行数据库的访问与操作。
在日常使用中Spring Data JPA 开发让我们解脱了DAO层的操作,基本所有的CRUD都可以进行使用它实现;它的简单方式在于我们在书写代码时候只需要在Dao层实现两个接口就可以了,JPA框架已经给我封装好了方法直接使用。
Spring Data JPA的特点:
- 提供了统一的接口,我们不会重复的sql
- 我们可以通过方法名进行自动生成sql语句
- 通过接口自动注入实现类,实现非常简单
- 遵循数据库规范同时很灵活的访问数据库
了解了Spring Data JPA 和JPA还有Hibernate但是之间有什么关系:
我们前面说过JPA是一套规范,是由接口和抽象类进行完成的,而Hibernate是一款成熟的ORM框架,而且实现了JPA规范,使用JPA的API进行编程,就是意味着我们站在更改一层进行处理问题,直接通过接口进行处理面向接口编程;而Spring Data JPA 是对JPA进行了更高一步的封装,是在JPA规范下专门对数据持久化的解决方法。
使用Spring Data JPA就需要搭建它的环境,需要整合Spring和Spring Data JPA,并且还需要实现方式Hibernate的依赖,并且数据库驱动。
环境配置
我们需要配置的东西都有如下
- 操作数据库:配置数据库信息是必要的 DataSource
- 在JPA时候有操作JPA的EntityManagerFactory,所以需要实体管理工厂对象 EntityManagerFactory
- 扫描的包、数据库、JPA的实现方式、JPA实现方式适配器
- 事务的配置就是使用JPA的事务管理器 JpaTransactionManager
- Spring需要扫描实体对象扫描包 context Component-Scan
- Spring的事务:声明式或者编程式 tx
- 最后需要将配置的整合进去Spring Data JPA里 jpa:repositories
<!--数据库配置-->
<bean id="ds" class="xxxDataSource">
<property name="url" value=""></property>
<property name="Driver" value=""></property>
<property name="username" value=""></property>
<property name="password" value=""></property>
</bean>
<!--EntityManagerFactory配置-->
<bean id="MyEntitiManagerFactory" class="xxx.LocalContainerEntityManagerFactoryBean">
<!--数据库-->
<proerty name="dataSource" ref="ds"></proerty>
<!--扫描的包-->
<proerty name="PageToScan" value="xxx.xxx.model"></proerty>
<!--JPA的实现方式-->
<proerty name="persistenceProvicer">
<bean class="xxx.HibernatePersistenceProvider"></bean>
</proerty>
<!--实现方式的适配器-->
<proerty name="jpaVendorAdapter">
<bean class="xxx.HibernateJpaVendorAdapter">
<!--是否创建表-->
<property name="GenerateDdl" value=""></property>
<!--使用的数据库配置-->
<property name="database" value=""></property>
<!--数据库方言-->
<property name="databasePlatform" value="MySQLDialect"></property>
<!--是否显示sql语句-->
<property name="showSql" value=""></property>
</bean>
</proerty>
<!--JPA的高级特性 支持Hibernate的-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
</property>
</bean>
<!--JPa的事务管理器-->
<bean id="JpaTranscationManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="MyEntitiManagerFactory"></property>
</bean>
<!--Spring注解扫描的包-->
<context:component-scan base-package="xxxx"></context:component-scan>
<!--Spring事务-->
<!--整合Spring Data JPa-->
<jpa:repositories base-package="xxx" tansaction-manager-ref="JpaTranscationManager" entity-manager-factory-ref="MyEntitiManagerFactory"></jpa:repositories>
实现方数据库的value 大写
我觉得其实配置能看懂就行,也没必要全背过,
下面来看看Spring Data JPA是怎么样解脱我们的Dao层的
使用Spring Data JPA 只需要编写符合规范的接口就可以,有两点要求
- 创建一个Dao接口,并实现JpaRepository接口和JpaSpecificationExecutor接口
- 提供相应的泛型
/**
* Spring Data JPA 书写Dao层的规范
* 继承两个接口
* JpaRepository:封装了基本的CRUD操作
* 实体类
* 对应主键的类型
* JpaSpecificationExecutor:封装了复杂操作(分页查询等等)
* 实体类
*/
public interface StudentDao extends JpaRepository<Student,Long>, JpaSpecificationExecutor<Student>{
}
然后直接从容器获取bean对象就可以进行操作
可以看到我们没有写任何方法,就已经有封装好的基本方法
接下来看一下为什么能够这么方便,其实就是我们实现的两个接口里面的方法,追溯到顶层接口CrudRepository
和JpaSpecificationExecutor
已经封装了方法
Spring Data JPA的运行过程剖析
发现此时注入的bean对象是一个代理对象,本质是通过JDKDynamicAopProxy生成的代理对象
我们进入此对象看看:
主要是这个invoke方法,当前这个targetSource是一个SimpleJpaRepository对象,
进入方法实际就是调用em.find方法
而em就是EntityManager实体管理器
所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发
Spring Data JPA的CRUD基本和JPA的相同
-
save方法是保存或者更新方法,判断依据就是是否存在id
有id:更新操作,先查询后更新
没有id:保存操作 -
getOne使用JPA的getReference方法
findOne使用JAP的findOne方法 -
delete查询到了再进行删除
来看一下Spring Data JPA的JPQL查询语言
搭配使用@Query注解
-
value就是:JPQL语句
-
nativeQuery:是否使用本地sql查询 默认false 就是使用JPQL; true是使用sql语句查询
举个例子:
//还是使用对象而不是数据库表
@Query(value = "from Student where stutName = ? ")
public Student findByName(String name);
//多占位符设置 ?1代表参数的占位符,其中1对应方法中的参数索引
// 要么就是顺序写,要么就是根据占位符数组匹配
@Query(value="from Student where stuName = ?2 and stuAge = ?1")
public Student findByNameAndAge(Integer age,String name);
如果需要更新操作的话,还得搭配注解@Modifying
@Query(value = "update Student set stuId = ?2 where stuName = ?1")
@Modifying
public void updateById(String name,Long id);
并且在测试类需要添加事务的注解@Transaction和@Rollback(false)
SpringDataJPA 执行jqpl语句执行更新或者删除之后默认事务进行回滚,需要加上@Rollback(value=false)
输入想要使用sql查询,需要@query注解(value=“sql”,nativeQuery=true)
除此之外Spring Data JPA还有一个可以进行自定义查询方式:方法命名规则查询。顾名思义就是根据方法名字进行对应的查询,按照规则进行书写方法名称,就可以执行查询语句。
查询方法以findBy开头涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
例如:
public Student findByStuName(String stuName);//根据stuName进行查询
public Student findByStuNameLike(String stuName);//模糊查询
public Student findByStuNameLikeAndStuClassAndStuAge(String stuName,String stuClass,String stuAge);//多条件查询
具体的关键字,使用方法和生产成SQL如下表所示
*Keyword* | *Sample* | *JPQL* |
---|---|---|
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |