SpringData的基础使用

Spring Data

概述

  • Spring Data:Spring的一个子项目。用于简化数据库访问,支持NoSQL关系数据存储。其主要目标是使数据库的访问变得方便快捷
  • SpringData项目所支持的NoSQL存储
    • MongoDB:文档数据库
    • Neo4j:图形数据库
    • Redis:键/值存储
    • Hbase:列族数据库
  • SpringData项目所支持的关系数据存储技术
    • JDBC
    • JPA

JPA Spring Data 概述

  • JPA Spring Data:致力于减少数据访问层(DAO)的开发量。开发者唯一要做的,就只是声明持久层的接口,其他交给SpringDataJPA来完成;
  • Spring Data JPA使用规范方法名,根据符合规范的名字来确定方法需要实现什么样的逻辑。
    • 例如:UserDao.findUserById(),大致可以判断出这是根据给定条件的ID查询满足条件的User对象

Spring Data JPA的使用

使用步骤

  • 配置Spring整合JPA
  • 在Spring配置文件中配置SpringData:
    • 让Spring为声明的接口创建代理对象。配置了jpa:repositories后,Spring初始化容器时将会扫描base-package指定的包目录及其子目录,为继承Repository或其子接口的接口创建代理对象,并将代理对象注册为SpringBean,业务层便可以通过Spring自动封装的特性来直接使用该对象
  • 声明持久层的接口,该接口继承Repository
    • Repository是一个标记型接口,它不包含任何方法,如必要;Spring Data可实现Repository其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法
  • 在接口中声明需要的方法
    • Spring Data将根据给定的策略来为其生成实现代码

简单案例Helloworld

  • 1.创建java项目,导入jar包
  • 2.创建spring配置文件applicationContext.xml
    • src目录下新建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">

	<!-- 配置自动扫描的包 -->
	<context:component-scan base-package="cn.aixuxi.springdata"></context:component-scan>

	<!-- 1. 配置数据源 -->
	<context:property-placeholder location="classpath:db.properties"/>

	<bean id="dataSource"
		class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></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">
		<property name="dataSource" ref="dataSource"></property>
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
		</property>
		<property name="packagesToScan" value="com.atguigu.springdata"></property>
		<property name="jpaProperties">
			<props>
				<!-- 生成的数据表的列的映射策略 -->
				<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
				<!-- 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"/>

	<!-- 5. 配置 SpringData -->
	<!-- 加入  jpa 的命名空间 -->
	<jpa:repositories base-package="cn.aixuxi.springdata"
		entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>

</beans>
  • 新建db.properties
jdbc.user=root
jdbc.password=1230
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///jpa
  • 测试数据库连接
public class springDataTest {
	private ApplicationContext ctx = null;
    private PersonRepsotory personRepsotory = null;
    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        personRepsotory = ctx.getBean(PersonRepsotory.class);
    }

    @Test
    public void testDataSource() throws SQLException {
        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println(dataSource.getConnection());//com.mchange.v2.c3p0.impl.NewProxyConnection@71454b9d 表示连接成功
    }
}
  • 创建测试实体类
@Table(name = "JPA_PERSONS")
@Entity
public class Person {

    private Integer id;
    private String lastName;
    private String email;
    private Date birth;
    @GeneratedValue
    @Id
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }
}
  • 创建测试接口PersonRepsotory
import org.springframework.data.repository.Repository;

public interface PersonRepsotory extends Repository<Person,Integer> {
    /**
     * 根据lastName 来获取对应的Person
     * @param lastName
     * @return
     */
    Person getByLastName(String lastName);
}
  • 测试
	@Test
	public void testHandler(){
        PersonRepsotory personRepsotory = ctx.getBean(PersonRepsotory.class);
        Person person = personRepsotory.getByLastName("aa");
        System.out.println(person);//cn.aixuxi.springdata.test.Person@1a1d3c1a查找成功(未重写toString()方法)
	 }

Repsotory接口

  • 1.Repository是一个空接口,即是一个标记接口
  • 2.若定义的接口继承了Repository,则该接口会被IOC容器识别为Repository Bean,纳入到IOC容器中,进而可以在该接口中定义满足一定规范的方法
  • 3.也可以使用@RepositoryDefinition 注解来替代继承Repository接口,并为其指定domainClass和idClass属性

小案例:修改测试接口PersonRepository

import org.springframework.data.repository.RepositoryDefinition;
@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonRepsotory {
    /**
     * 根据lastName 来获取对应的Person
     * @param lastName
     * @return
     */
    Person getByLastName(String lastName);
}

Repository的子接口

基础的Repository提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。继承关系如下

  • Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类
  • CrudRepository:继承Repository,实现了一组CRUD相关的方法
  • PagingAndSortingRepository:继承CrudRepository,实现了一组分页排序相关的方法
  • JpaRepository:继承PagingAndSortingRepostiory,实现了一组JPA规范相关的方法
  • 自定义的XxxxRepository:需要继承JpaRepository,这样的XxxxRepository接口就具备了通用的数据访问控制层的能力
  • JpaSpecificationExecutor:不属于Repository体系,实现一组JPACriteria查询相关的方法

Repository声明方法的规范

在Repository子接口中声明方法,方法名需要符合Spring Data的规范

  • 查询方法以 find | read | get开头
  • 涉及条件查询时,条件的属性用条件关键字连接
    • 条件关键字,如 And 、Or 、Between 、LessThan 、GreaterThan 、IsNUll 、Like 、In 、OrderBy等
    • 案例:
类 User
class User{
	String name;
	Integer age;
}
使用And条件连接时,方法应是:
findByNameAndAge(String name,Integer age);
条件的属性名称与个数要与参数的位置与个数一一对应	
  • 注意:条件属性以首字母大写
  • 支持属性的级联查询。若当前类有符合条件的属性,则优先使用,而不使用级联属性;若需要使用级联属性,则属性之间使用 _ 进行连接
    • 案例:
创建实体类:Address

@Table(name = "JPA_ADDRESSES")
@Entity
public class Address {
   private Integer id;
   private String province;
   private String city;

   @GeneratedValue
   @Id
   public Integer getId() {
       return id;
   }

   public void setId(Integer id) {
       this.id = id;
   }

   public String getProvince() {
       return province;
   }

   public void setProvince(String province) {
       this.province = province;
   }

   public String getCity() {
       return city;
   }

   public void setCity(String city) {
       this.city = city;
   }
}
修改实体类:Person 添加Address

   private Address address;

   @JoinColumn(name = "ADDRESS_ID")
   @ManyToOne
   public Address getAddress() {
       return address;
   }

   public void setAddress(Address address) {
       this.address = address;
   }
修改接口:在PersonReposotory
   //WHERE person.address.id > ? 需要使用级联属性查询时用下划线连接
 		List<Person> getByAddress_IdGreaterThan(Integer id);
测试方法
    @Test
   public void testKeyWords(){
       List<Person> persons = personRepsotory.getByAddressIdGreaterThan(1);
       System.out.println(persons);
   }
查询方法解析流程:
  • 假如创建如下的查询:findByUserDepUuid(),框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc
    • 先判断 userDepUuid (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
    • 从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为查询实体的一个属性;
    • 接着处理剩下部分(DepUuid),先判断 user 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 “Doc.user.depUuid” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “Doc.user.dep.uuid” 的值进行查询。
    • 可能会存在一种特殊情况,比如 Doc包含一个 user 的属性,也有一个 userDep 属性,此时会存在混淆。可以明确在属性之间加上 “_” 以显式表达意图,比如 “findByUser_DepUuid()” 或者 “findByUserDep_uuid()”
  • 特殊的参数: 还可以直接在方法的参数上加入分页或排序的参数,比如:
    • Page findByName(String name, Pageable pageable);
    • List findByName(String name, Sort sort);

自定义查询@Query

这种查询可以声明在 Repository 方法中,摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰,这是 Spring data 的特有实现。

基本查询

案例
在PersonReposotory中添加查询方法

//查询id 值最大的那个Person
//使用@Query 注解可以自定义JPQL语句以实现更灵活的查询
@Query("SELECT p FROM Person p WHERE p.id = (SELECT MAX(p2.id) FROM Person p2)")
Person getMaxIdPerson();
测试

@Test
public void testQueryAnnotation(){
	Person person = personRepsotory.getMaxIdPerson();
	System.out.println(person);
}
传值查询

在PersonReposotory中添加查询方法

//为@Query 注解传递参数的方式1:使用占位符
@Query("SELECT p FROM Person p WHERE p.lastName=?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName,String eamil);//需要按照占位符的顺序


//为@Query 注解传递参数的方式2:命名参数的方式
@Query("SELECT p FROM Person p WHERE p.lastName= :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email,@Param("lastName") String lastName);

//Like 传参的小细节
//SpringData 允许在占位符中添加%
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List<Person> testQueryAnnotationParams3(String lastName,String email);

测试

@Test
public void testQueryAnnotationParams(){
	List<Person> list1 = personRepsotory.testQueryAnnotationParams1("aa","aa@aa.com");
	System.out.println(list);
	List<Person> list2 = personRepsotory.testQueryAnnotationParams2("aa@aa.com","aa");
	List<Person> list3 = personRepsotory.testQueryAnnotationParams1("a","a");
}

使用原生SQL查询

在PersonReposotory中添加查询方法

//设置nativeQuery = true,即可以使用原生SQL语句
@Query(value="SELECT count(*) FROM jpa_persons",nativeQuery = true)
long getTotalCount();

测试

@Test
public void testNativeQuery(){
	Long count = personRepsotory.getTotalCount();
	System.out.println(count);
}

@Modifying 注解和事务

在PersonReposotory中添加查询方法

//可以通过自定的JPQL完成UPDATE和DELETE操作,注意:JPQL不支持使用INSERT
//在@Query 注解中编写JPQL语句,必须使用@Modifying进行修饰,以通知SpringData,这是一个UPDATE或者DELETE操作
//UPDATE 或DELETE操作需要使用事务,此时需要定义Service层,在Service层的方法上添加事务操作	
//默认情况下,SpringData的每个方法是都有事务,但都只是只读事务
@Modifying
@Query("UPDATE Person p SET p.email = :email AND id = :id")
void updatePersonEmail(@Param("id") Integer id,@Param("email") String email);

新建PersonService层

@Service
public class PersonService(){
	@Autowired
	private PersonReposotory personReposotory;
	@Transactional 
	public void updatePersonEmail(Integer id,String email){
		personReposotory.updatePersonEmail(id,email);
	}
}	

测试

@Test
public void testUpdatePersonEmail(){
	PersonService personService = ctx.getBean(PersonService.class);
	personService.updatePersonEmail(1,"mm@mm.com");
}

CrudRepository接口

CurdRepository接口提供了最基本的对实体类的添删改查等操作

  • T save(T entity);//保存单个实体
  • Iterable save(Iterable<? extends T> entities>;//保存集合
  • T findOne(ID id);//根据id查找实体
  • boolean exists(ID id);//根据id判断实体是否存在
  • Iterable findAll();//查询所有实体,不用或慎用!
  • long count();//查询实体数量
  • void delete(ID id);//根据ID删除实体
  • void delete(T entity);//删除一个实体
  • void delete(Iterable<? extends T> entities);//删除一个实体的集合
  • void deleteAll();//删除所有实体,不用或慎用!

案例

接口继承CrudRepository

public interface PersonRepsotory extends CrudRepository<Person.class,Integer.class>{}
修改服务层
	@Transactional 
	public void updatePersonEmail(List<Person> persons){
		personReposotory.save(persons);
	}

测试:

@Test
public void testCrudReposiory(){
	PersonService personService = ctx.getBean(PersonService.class);
	List<Person> persons = new ArrayList<>();
	for(int i = 'a';i <= 'z';i++){
		Person person = new Person();
		person.setBirth(new Date());
		person.setEmail((char)i+"" + (char)i+"@aa.com");
		person.setLastName((char)i+"" + (char)i);
		persons.add(person);
	}
	personService.savePersons(persons);
}

其余方法使用方法类似

PagingAndSortingRepository接口

该接口提供了分页与排序功能

  • Iterable findAll(Sort sort);//排序
  • Page findAll(Pageable pageable);//分页查询(含排序功能)

案例

接口继承PagingAndSortingRepository

public interface PersonRepsotory extends PagingAndSortingRepository<Person.class,Integer.class>{}

测试

//分页与排序是只读操作,不需要修改服务层
@Test
public void testPagingAndSortingRepository(){
	//pageNo 从0开始
	int pageNo = 6-1;
	int pageSize = 5;
	//Pageable接口通常使用的其PageRequest实现类,其中封装了需要分页的信息
	//排序相关 Sort封装了排序的信息
	//Order 是具体针对于某一个属性进行升序还是降序
	Order order1 = new Order(Direction.DESC,"id");
	Order order2 = new Order(Direction.ASC,"email");
	Sort sort = new Sort(order1,order2);
	PageRequest pageable = new PageRequest(pageNo,pageSize,sort);
	Page<Person> page = personRepsotory.findAll(pageable);
	System.out.println("总记录数:"+page.getTotalElements());
	System.out.println("当前第几页:"+page.getNumber()+1);
	System.out.println("总页数:"+page.getTotalPages());
	System.out.println("当前页面的List:"+page.getContent());
	System.out.println("当前页面的记录数:"+page.getNumberOfElements());
}

JpaRepository接口

该接口提供了JPA的相关功能

  • List findAll();//查找所有实体
  • List findAll(Sort sort);//排序、查找所有实体
  • List save(Iterable<? extends T> entities);//保存集合
  • void flush();//执行缓存与数据库同步
  • T saveAndFlush(T entity);//强制执行持久化
  • void deleteInBatch(Iterable entities);;//删除一个集合

案例

接口继承JpaRepository

public interface PersonRepsotory extends JpaRepository<Person.class,Integer.class>{}

测试

@Test
public void testJpaRepository(){
	Person person = new Person();
	person.setLastName("bbb");
	person.setBirth(new Date());
	person.setEmail("bbb@bbb.com");
	person.setId(20);
	personRepsotory.saveAndFlush(person);
}

JpaSpecificationExecutor接口

不属于Repository体系,实现一组JPACriteria查询相关的方法

Sprcification:封装JPACriteria查询条件。通常使用匿名内部类的方式来创建该接口的对象

  • findOne(Specification):T 根据条件查询对象
  • findAll(Specification):List 查询符合条件的对象的集合
  • findAll(Specification,Pageable):Page 查询符合条件的对象的分页集合(包含排序)
  • findAll(Specification,Sort):List 查询符合条件的对象的排序集合
  • count(Specification):long 查询符合条件的对象的个数

案例:通用的、带有条件的查询

接口继承JpaRepository

public interface PersonRepsotory extends JpaRepository<Person.class,Integer.class>,JpaSpecificationExecutor<Prson>{}

测试

/**
    * 目标:实现带查询条件的分页  id > 5
    * 调用JpaSpecificationExecutor 的Page<T> findAll(Specification<T> spec,Pageable pageable);
    * Specification:封装了JPA Criteria查询的查询条件
    * Pageable:封装了请求分页的信息,列入pageNo,pageSize,Sort
    */
   @Test
   public void testJpaSpecification(){
       int pageNo = 3 - 1;
       int pageSize =5;
       PageRequest pageable = new PageRequest(pageNo,pageSize);
       //通常使用Specfication 的匿名内部类
       Specification<Person> specification = new Specification<Person>() {
           /**
            *
            * @param root           :代表查询的实体类
            * @param criteriaQuery  :可以从中可到Root对象,即告知JPA Criteria查询要查询哪一个实体类,还可以
            *                       来添加查询条件,还可以结合EntityManager 对象得到最终查询的TypeQuery对象
            * @param criteriaBuilder:CriteriaBuilder对象。用于创建Criteria相关对象的工厂。当然可以从中获取到Perdicate对象
            * @return               :Predicate类型,代表一个查询条件
            */
           @Override
           public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery,
                                        CriteriaBuilder criteriaBuilder) {
               Path path = root.get("id");
               Predicate predicate = criteriaBuilder.gt(path,5);
               return predicate;
           }
       };
       Page<Person> page = personRepsotory.findAll(specification,pageable);

       System.out.println("总记录数:"+page.getTotalElements());
       System.out.println("当前第几页:"+(page.getNumber()+1));
       System.out.println("总页数:"+page.getTotalPages());
       System.out.println("当前页面的List:"+page.getContent());
       System.out.println("当前页面的记录数:"+page.getNumberOfElements());

   }

自定义Repository方法

为某一个Repository上添加自定义方法

步骤:

  • 定义一个接口:声明要添加的,并自实现的方法
public interface PersonDao{
		public void test();
	}
  • 提供该接口的实现类:类名需在要声明的Repository后添加Impl,并实现方法
public class PersonRepsotoryImpl implements PersonDao{
		
		@PersistenceContext
		private EntityManager entityManager;
		@Override
		public void test(){
			Person person = entityManager.find(Person.class,11);
			System.out.println("-->"+person);
		}
	}
  • 声明Repository接口,并继承声明的接口
  • 使用
@Test
public void testCustomRepositoryMethod(){
	personRepsotory.test();
}	
  • 注意:默认情况下,Spring Data会在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

小结

这是关于SpringDataJPA的基础使用,更加进阶的以后会再整理一下吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ruozhuliufeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值