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 将根据给定的策略为其生成实现代码.

步骤 :

  1. 导入 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

  1. 编写 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>
  1. 编写 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);
}
  1. 测试
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);

关键字 :

spring 数据库驱动 spring database_1024程序员节


spring 数据库驱动 spring database_jar_02

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都添加自实现的方法 :

  1. 声明一个接口, 在该接口中声明需要自定义的方法, 且该接口需要继承 Spring Data 的 Repository.
  2. 提供 1) 所声明的接口的实现类. 且继承 SimpleJpaRepository, 并提供方法的实现
  3. 定义 JpaRepositoryFactoryBean 的实现类, 使其生成 1) 定义的接口实现类的对象
  4. 修改 <jpa:repositories /> 节点的 factory-class 属性指向 3) 的全类名
  5. 注意: 全局的扩展实现类不要用 Imp 作为后缀名, 或为全局扩展接口添加 @NoRepositoryBean 注解告知 Spring Data: Spring Data 不把其作为 Repository