文章目录
1. 什么是Spring Data JPA
Spring Data提供了针对数据库(包括SQL和NOSQL)的整合方案,对Hibernate、JPA、Jedis等工具的api进行高级的封装,为我们提供简单方便地操作接口,其主要目标是使得数据库的访问变得方便快捷。
Spring Data JPA是Spring Data项目下的一个模块,可以极大的简化JPA的写法,让我们可以更方便地操作数据库。如果单独使用JPA开发,你会发现和使用JDBC开发一样很繁琐,所以Spring Data JPA的出现就是为了简化JPA的写法,让你只需要编写一个接口继承一个类就能实现CRUD操作了。
Spring Data JPA与JPA和Hibernate之间的关系:
- JPA是一套规范,内部是由接口和抽象类组成的。
- Hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)。
- Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。
2. Spring Data JPA快速入门
2.1 创建表结构
CREATE TABLE cst_customer (
cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
cust_name varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
cust_source varchar(32) DEFAULT NULL COMMENT '客户信息来源',
cust_industry varchar(32) DEFAULT NULL COMMENT '客户所属行业',
cust_level varchar(32) DEFAULT NULL COMMENT '客户级别',
cust_address varchar(128) DEFAULT NULL COMMENT '客户联系地址',
cust_phone varchar(64) DEFAULT NULL COMMENT '客户联系电话',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2.2 编写实体类
@Entity //声明实体类
@Table(name="cst_customer") //建立实体类和表的映射关系
public class Customer {
@Id //声明当前私有属性为主键
@GeneratedValue(strategy=GenerationType.IDENTITY) //配置主键的生成策略
@Column(name="cust_id") //指定和表中cust_id字段的映射关系
private Long custId;
@Column(name="cust_name") //指定和表中cust_name字段的映射关系
private String custName;
@Column(name="cust_source") //指定和表中cust_source字段的映射关系
private String custSource;
@Column(name="cust_industry") //指定和表中cust_industry字段的映射关系
private String custIndustry;
@Column(name="cust_level") //指定和表中cust_level字段的映射关系
private String custLevel;
@Column(name="cust_address") //指定和表中cust_address字段的映射关系
private String custAddress;
@Column(name="cust_phone") //指定和表中cust_phone字段的映射关系
private String custPhone;
// 省略 get和 set方法
}
2.3 整合Spring Data JPA与Spring
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- 1.创建数据库连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
<property name="jdbcUrl" value="jdbc:mysql:///joker" ></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>
<!-- 2.创建 entityManagerFactory对象交给 spring容器管理 -->
<bean id="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 配置的扫描的包(实体类所在的包) -->
<property name="packagesToScan" value="cn.joker.domain" />
<!-- jpa的实现厂家 -->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!-- jpa的供应商适配器 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!-- 配置是否自动创建数据库表 -->
<property name="generateDdl" value="false" />
<!-- 指定数据库类型 -->
<property name="database" value="MYSQL" />
<!-- 数据库方言:支持的特有语法 -->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
<!-- 是否显示sql -->
<property name="showSql" value="true" />
</bean>
</property>
<!-- jpa的方言:高级的特性 -->
<property name="jpaDialect" >
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
</property>
</bean>
<!--3.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactoty"></property>
</bean>
<!-- 4.整合Spring Data JPA -->
<jpa:repositories base-package="cn.joker.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactoty" ></jpa:repositories>
<!-- 5.开启 spring对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 6. 配置包扫描-->
<context:component-scan base-package="cn.joker" ></context:component-scan>
</beans>
2.4 编写符合Spring Data JPA规范的Dao层接口
Spring Data JPA是Spring提供的一款对于数据访问层(Dao层)的框架,使用Spring Data JPA,只需要按照框架的规范提供Dao接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,极大的简化了我们的开发过程。
在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:
- 创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor
- 提供相应的泛型
/**
* JpaRepository<实体类类型,主键类型>:用来完成基本 CRUD操作
* JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
*/
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}
2.5 编写测试代码
完成了Spring Data JPA的环境搭建,并且编写了符合Spring Data JPA规范的Dao层接口之后,就可以使用定义好的Dao层接口进行客户的基本CRUD操作了。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class CustomerDaoTest {
@Autowired
private CustomerDao customerDao;
@Test
public void testSave() {
Customer c = new Customer();
c.setCustName("joker");
customerDao.save(c);
}
@Test
public void testUpdate() {
Customer customer = customerDao.findOne(1l);
customer.setCustName("张三");
//对于 save方法的解释:如果执行此方法是对象中存在 id属性,即为更新操作
//如果执行此方法中对象中不存在 id属性,即为保存操作
customerDao.save(customer);
}
@Test
public void testFindById() {
Customer customer = customerDao.findOne(1l);
System.out.println(customer);
}
@Test
public void testDelete() {
customerDao.delete(1l);
}
}
3. Spring Data JPA的原理详解
在上面的案例中,我们发现在自定义的CustomerDao中,并没有提供任何方法就可以使用其中的很多方法,那么这些方法究竟是怎么来的呢?答案很简单,对于我们自定义的Dao接口,由于继承了JpaRepository和JpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。
在使用Spring Data JPA时,一般实现JpaRepository和JpaSpecificationExecutor接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在Spring Data JPA中它又是怎么实现的呢?
3.1 Spring Data JPA的实现过程
通过对上面案例,以debug断点调试的方式,分析Spring Data JPA的原来来分析程序的执行过程,我们以findOne方法为例进行分析。
代理子类的实现过程:断点执行到方法上时,我们可以发现注入的customerDao对象,本质上是通过JdkDynamicAopProxy生成的一个代理对象
代理对象中方法调用的分析:当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对customerDao对象生成动态代理对象。根据对Spring Data JPA介绍而知,要想进行findOne查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository。
通过SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?
带着问题继续查找em对象,我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论:Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发。
3.2 Spring Data JPA的调用过程
通过上面对Spring Data JPA的实现过程分析,我们不难发现Spring Data JPA的调用过程如下图所示: