SpringData
SpringData 概述
SpringData : Spring 的一个子项目, 用于简化数据库访问, 支持 NOSQL 和 关系性数据库. 其主要目标是使数据库的访问变得方便快捷.
SpringData 所支持的 NOSQL 存储 :
- MongoDB (文档数据库)
- Neo4j (图形数据库)
- Redis(键值数据库)
- HBASE(列族数据库)
SpringData 所支持的关系型数据库 :
- JDBC
- JPA
SpringDataJPA 概述
SpringDataJPA : 致力于减少数据访问层的开发, 开发者唯一要做的就只是声明持久层的接口,其他的都交给 SpringDataJPA 来完成.
他如何代替开发者实现业务逻辑的?
比如 : 当有一个 UserDao.findUserById(Integer id) , 这样一个方法声明, 大致能判断这是根据给定条件的ID查询出满足查询条件的 User 对象, SpringDataJPA 做的便是规范方法的名字, 根据符合规范的名字来确定方法需要实现什么样的逻辑.
HelloWorld
使用 SpringDataJPA 进行持久化开发的四个步骤 :
- 配置 Spring 整合 JPA
- 在 Spring 配置文件中配置 SpringData , 让 Spring 为声明的接口创建代理对象, 配置了
<jpa:repositories>
后,Spring 初始化容器的时候会扫描 base-package 指定的包目录及其子目录, 为继承 Repository 或其子接口的接口创建代理对象, 并将代理对象注册为 SpringBean , 业务层便可以通过 Spring 自动封装的特性来直接使用该对象. - 声明持久化接口, 该接口继承 Repository , Repository 是一个标记性接口, 他不包含任何方法, 如必要, SpringData 可以实现 Repository 其他子接口, 其中定义了一些常用的 增删改查 以及 分页 相关的方法.
- 在接口中声明需要的方法 : SpringData 将根据给定的策略为其生成实现代码.
步骤 :
- 导入 jar 包
antlr-2.7.7.jar
c3p0-0.9.2.1.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.3.jar
dom4j-1.6.1.jar
hibernate-c3p0-4.2.4.Final.jar
hibernate-commons-annotations-4.0.2.Final.jar
hibernate-core-4.2.4.Final.jar
hibernate-entitymanager-4.2.4.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
javassist-3.15.0-GA.jar
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.1.Final.jar
mchange-commons-java-0.2.3.4.jar
mysql-connector-java-5.1.7-bin.jar
slf4j-api-1.6.1.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-data-commons-1.6.2.RELEASE.jar
spring-data-jpa-1.4.2.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar
- 编写 applicationContext.xml
- 引入 db.properties
- 配置数据源
- 配置 EntityManagerFactory
- 配置数据源
- 配置哪个实现类
- 配置扫描要映射的类
- 配置jpa的其他属性
- 二级缓存相关
- show_sql
- format_sql
- hbm2ddl
- 配置事务管理器
- 配置 EntityManagerFactory
- 配置支持注解式事务
- 配置 SpringData
- 配置要扫描的 Repository 包
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- ============ jpa 与 spring 整合开始 ============ -->
<!-- 1.1 引入配置文件 -->
<context:property-placeholder location="db.properties"/>
<!-- 1. 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
</bean>
<!-- 2. 配置 JPA 的 EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 2-1. 将数据源注入 EntityManagerFactory -->
<property name="dataSource" ref="dataSource"></property>
<!-- 2-2. 配置要扫描的类 -->
<property name="packagesToScan" value="com.lfy.springdata"></property>
<!-- 2-3. 配置实现类的适配器 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
</bean>
</property>
<!-- 2-4. jpa 的其他配置 -->
<property name="jpaProperties">
<props>
<!-- 2-4-1. 二级缓存相关 -->
<!--
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop>
-->
<!-- 2-4-2. 生成的数据表的列的映射策略 -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!-- 2-4-3. hibernate 基本属性 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 3. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 4. 配置支持注解的事务 -->
<context:component-scan base-package="com.lfy.springdata"></context:component-scan>
<!-- ============ JPA 与 spring 整合完成============ -->
<!-- 5. 整合 SpringData -->
<!-- 5-1. 加入 JPA 的命名空间 -->
<!-- 5-2.设置要扫描的 Repository 的包 -->
<jpa:repositories base-package="com.lfy.springdata"></jpa:repositories>
</beans>
- 编写 Dao repository
UserRepository
package com.lfy.springdata;
import org.springframework.data.repository.Repository;
public interface PersonRepository extends Repository<Person, Integer> {
//1. 根据lastName获取Person
Person findByLastName(String lastName);
}
- 测试
package com.lfy.springdata;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class SpringDataTest {
private static ApplicationContext ctx = null;
static {
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
void testPerson() {
PersonRepository personRepository = ctx.getBean(PersonRepository.class);
Person person = personRepository.findByLastName("AA");
System.out.println(person);
}
@Test
void testDataSource() throws SQLException {
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println(dataSource.getConnection());
}
}
Repository 接口
基础的 Repository 提供了最基本的数据访问功能, 其几个子接口则扩展了一些功能 :
- Repository : 仅仅是一个标识, 表明任何继承他的均为仓库接口类.
- CrudRepository : 继承 Repository , 实现了一组 CRUD 相关的方法.
- PagingAndSortRepository : 继承 CrudRepository, 实现了一组分页排序相关的方法.
- JpaRepository : 继承 PagingAndSortRepository,实现了一组 JPA 的规范的相关方法.
- 自定义的 Repository : 需要继承 JPARepository, 这样自定义的Repository就具备了通用的数据访问控制层的能力.
- JapSpecificationExecutor : 不属于 Repository 体系, 实现一组 JPA Criteria 查询相关的方法.
SpringData方法定义规范
- 不可以随便声明, 需要符合一定的规范
- 查询的方法以 find read get 开头
- 涉及条件查询时,条件的属性用条件关键字连接
- 条件属性的首字母大写
- 支持属性的级联查询,若当前类有符合条件的属性,而不使用级联属性,如果需要使用级联查询,则属性之间使用 _ 进行连接
- 特殊的参数: 还可以直接在方法的参数上加入分页或排序的参数,比如:
– Page findByName(String name, Pageable pageable);
– List findByName(String name, Sort sort);
关键字 :
public interface PersonRepository extends Repository<Person, Integer> {
//1. 根据lastName获取Person
Person findByLastName(String lastName);
//2. 查询以 lastName 开头而且 id 小于某个数的 Person 集合.
List<Person> findByLastNameStartingWithAndIdLessThan(String lastName,Integer id);
//3. 查询以 lastName 结尾并且 id 小于某个数的 Person 集合.
List<Person> findByLastNameEndingWithAndIdLessThan(String lastName,Integer id);
//4. 查询指定邮箱并且生日小于某个值的 Person 集合
List<Person> findByEmailInAndBirthLessThan(String[] emails,Date birth);
//5. 级联查询 address 的 id 为 指定值的 Person
List<Person> findByAddressIdGreaterThan(Integer id);
//6.如果Person表中也有一个属性为 addressId
//6-1. 这样查询的话不会查询出结果,因为先在自己的表中查询.
//List<Person> findByAddressIdGreaterThan(Integer id);
//6-2.若需要级联查询,则需要在属性之间加上一个_
List<Person> findByAddress_IdGreaterThan(Integer id);
}
@Query 注解
这种查询可以声明在 Repository 方法中, 摆脱命名查询那样的约束,将查询直接在响应的接口方法中声明,结构更为清晰,这是 SpringData 的特有实现.
索引参数和命名参数
索引参数的索引值从1开始, 查询中 “?x” 个数需要与方法定义的参数个数相一致,并且顺序也要一致.
命名参数(推荐使用这种方式) : 可以定义好参数名,赋值时采用 @Param(“参数名”) 而不用管顺序.
//使用 JPQL
//1.索引参数
@Query(value="SELECT p FROM Person p WHERE p.lastName=?1 AND p.id=?2")
Person testQuery01(String lastName,Integer id);
//2.命名参数
@Query(value="SELECT p FROM Person p WHERE p.lastName= :lastName AND p.id= :id")
Person testQuery02(@Param("id") Integer id,@Param("lastName") String lastName);
//3.测试like 索引参数
@Query(value="SELECT p FROM Person p WHERE p.lastName LIKE %?1%")
Person testLike01(String lastName);
//4.测试like 命名参数
@Query(value="SELECT p FROM Person p WHERE p.lastName LIKE %:lastName%")
Person testLike02(@Param("lastName") String lastName);
//使用原生SQL
//5.native原生查询
@Query(value = "select count(*) from tb_person",nativeQuery = true)
Long testNativeQuery();
@Modify注解和事务
@Query 与 @Modify 这两个 annotation 一起声明, 可以定义个性化的更新操作, 例如只涉及某些字段更新时最为常用.
- 方法的返回值应该是 int , 表示更新语句所影响的行数
- 在调用的地方必须要加上事务,没有事务不能正常执行.
- SpringData 提供了默认的事务处理方式, 即所有的查询均声明为只读事务.
- 对于自定义的方法,如果需要改变 SpringData 提供的事务默认方式,可以在方法上加上注解 @Transactional 声明
- 进行多个 Repository 操作时, 也应该使他们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在service层实现对多个Repository的调用,并在响应的方法上声明事务.
dao :
@Modifying
@Query(value="UPDATE Person p set p.lastName = ?1 where p.id = ?2")
int updatePersonName(String lastName, Integer id);
applicationContext.xml :
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 0. 配置自动扫描的包 -->
<context:component-scan
base-package="com.lfy.springdata">
</context:component-scan>
<!-- ============ jpa 与 spring 整合开始 ============ -->
<!-- 1.1 引入配置文件 -->
<context:property-placeholder location="db.properties"/>
<!-- 1. 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
</bean>
<!-- 2. 配置 JPA 的 EntityManagerFactory -->
<bean id="entityManagerFactory" class
="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 2-1. 将数据源注入 EntityManagerFactory -->
<property name="dataSource" ref="dataSource"></property>
<!-- 2-2. 配置要扫描的类 -->
<property name="packagesToScan" value="com.lfy.springdata"></property>
<!-- 2-3. 配置实现类的适配器 -->
<property name="jpaVendorAdapter">
<bean class=
"org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> </bean>
</property>
<!-- 2-4. jpa 的其他配置 -->
<property name="jpaProperties">
<props>
<!-- 2-4-1. 二级缓存相关 -->
<!--
<prop key="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory
</prop>
<prop key="net.sf.ehcache.configurationResourceName">
ehcache-hibernate.xml
</prop>
-->
<!-- 2-4-2. 生成的数据表的列的映射策略 -->
<prop key="hibernate.ejb.naming_strategy">
org.hibernate.cfg.ImprovedNamingStrategy
</prop>
<!-- 2-4-3. hibernate 基本属性 -->
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQL5InnoDBDialect
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 3. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"> </property>
</bean>
<!-- 4. 配置支持注解的事务 -->
<tx:annotation-driven transaction-manager="transactionManager"> </tx:annotation-driven>
<!-- ============ JPA 与 spring 整合完成============ -->
<!-- 5. 整合 SpringData -->
<!-- 5-1. 加入 JPA 的命名空间 -->
<!-- 5-2.设置要扫描的 Repository 的包 -->
<jpa:repositories base-package="com.lfy.springdata"></jpa:repositories>
</beans>
测试 :
@Test
public void testModify() {
PersonService personService = ctx.getBean(PersonService.class);
personService.updatePersonEmail("lifanyu", 1);
}
CRUDRepository 接口
CrudRepository 接口提供了最基本的对实体类的添删改查操作
– T save(T entity); //保存单个实体
– Iterable<T> save(Iterable<? extends T> entities);/ /保存集合
– T findOne(ID id); //根据id查找实体
– boolean exists(ID id);/ /根据id判断实体是否存在
– Iterable<T> findAll(); //查询所有实体,不用或慎用!
– long count(); //查询实体数量
– void delete(ID id); //根据Id删除实体
– void delete(T entity); //删除一个实体
– void delete(Iterable<? extends T> entities); //删除一个实体的集合
– void deleteAll(); //删除所有实体,不用或慎用!
PagingAndSortingRepository 接口
该接口实现了分页与排序的功能
//排序
Iterable<T> findAll(Sort sort);
//分页查询(含排序功能)
Page<T> findAll(Pageable pageable);
JpaRepository 接口
List<T> findAll(); //查找所有实体
List<T> findAll(Sort sort); //排序、查找所有实体
List<T> save(Iterable<? extends T> entities);//保存集合
void flush();//执行缓存与数据库同步
T saveAndFlush(T entity);//强制执行持久化
void deleteInBatch(Iterable<T> entities);//删除一个实体集合
JpaSpecificationExecutor 接口
不属于 Repository 体系, 实现一组 JPA CRiteria 查询相关的方法.
- JapSpecificationExecutor
- findOne(Specificaction) : T
- findAll(Specificaction) : List
- findAll(Specificaction,Pageable) :Page
- findAll(Specificaction,Sort) :List
- count(Specificaction) : long
Specificatioin : 封装 JPA Criteria 查询条件, 通常使用匿名内部类的方式来创建该接口的对象.
// 若想使用增删改查,可以继承 JpaRepository<Person, Integer> 接口
public interface PersonRepository extends
Repository<Person, Integer>,JpaSpecificationExecutor<Person>,PersonDao
,JpaRepository<Person, Integer>{
测试 :
/**
* 实现带查询条件的分页
*/
@Test
public void testJpaSpecification() {
PersonRepository personRepository = ctx.getBean(PersonRepository.class);
// 添加记录
List<Person> personList = new ArrayList<>();
int j = 0;
for(char i = 'a'; i<= 'z'; i++)
{
Person p = new Person();
p.setAge(18);
p.setBirth(new Date());
p.setEmail("@163.com");
p.setId(j++);
p.setLastName(i+"");
personList.add(p);
}
personRepository.save(personList);
// page 从 0 开始
int page = 7-1;
// 一页几条记录数
int size = 4;
PageRequest pageable = new PageRequest(page, size);
Specification<Person> specification = new Specification<Person>() {
/**
* root:代表查询的实体类
* query:可以从中获取Root对象,即告知Criteria查询哪一个实体类 还可以添加查询条件,
* 还可以结合EntityManager对象得到最终的查询Type的Query对象.
* cb:CriteriaBuilder对象,用于创建Criteria对象的工厂,可以从中获取 predicate 对 象.
* Predicate:代表一个查询条件
*/
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.gt(root.get("id"), 0);
return predicate;
}
};
Page<Person> PersonPage = personRepository.findAll(specification,pageable);
List<Person> persons = PersonPage.getContent();
System.out.println("内容为 ");
for (Person person : persons) {
System.out.println(person);
}
int number = PersonPage.getNumber();
int pageCount = PersonPage.getNumberOfElements();
int pageSize = PersonPage.getSize();
long totalElements = PersonPage.getTotalElements();
int totalPages = PersonPage.getTotalPages();
System.out.println("当前为第"+(number+1)+"页");
System.out.println("当前页有"+pageCount+"条记录");
System.out.println("一共有"+totalPages+"页");
System.out.println("一页有"+pageSize+"条记录");
System.out.println("一共有"+totalElements+"条记录");
}
自定义 Repository 方法
- 为某一个 Repository 上添加自定义的方法.
- 为所有的 Repository 都添加自实现的方法.
为某一个 Repository 上添加自定义的方法 :
- 定义一个接口 : 声明要添加的,并自实现的方法
public interface PersonDao {
void test();
}
- 提供该接口的实现类, 类名需要在要声明的Repository后加上impl, 并实现方法.
public class PersonRepositoryImpl implements PersonDao{
@Override
public void test() {
System.out.println("test方法....");
}
}
- 声明 Repository 接口, 并继承 1. 声明的接口
//必须继承 Repository 接口
public interface PersonRepository extends Repository<Person, Integer>,PersonDao {
}
- 使用
@Test
void testCustomizeMethod() {
PersonRepository personRepository = ctx.getBean(PersonRepository.class);
personRepository.test();
}
- 注意 : 默认情况下, SpringData 会在 base-package 中查找 接口名impl 作为实现类,也可以通过 repository-impl-postfix 声明后缀.
为所有的 Repository都添加自实现的方法 :
- 声明一个接口, 在该接口中声明需要自定义的方法, 且该接口需要继承 Spring Data 的 Repository.
- 提供 1) 所声明的接口的实现类. 且继承 SimpleJpaRepository, 并提供方法的实现
- 定义 JpaRepositoryFactoryBean 的实现类, 使其生成 1) 定义的接口实现类的对象
- 修改 <jpa:repositories /> 节点的 factory-class 属性指向 3) 的全类名
- 注意: 全局的扩展实现类不要用 Imp 作为后缀名, 或为全局扩展接口添加 @NoRepositoryBean 注解告知 Spring Data: Spring Data 不把其作为 Repository