Spring Data概述
SpringData是SpringSource下的一个子项目,旨在简化对于数据库或者数据存储的持久化操作,包括存储和访问,并且不拘泥于SQL,它也支持NoSQL数据库如MongoDB。
Spring data下包含多个子项目如
- Spring Data JPA
- Spring Data MongoDB
- Spring Data Redis
- Spring Data Solr(搜索)
- …
我们通过传统的方式,即原生JDBC或者Spring封装的JDBCTemplate的方式来访问数据库时,有这么一些缺点:
- Dao层需要过多的代码
- DaoImpl中有许多重复的代码
- 我们进行分页或扩展其他功能的时候需要重写dao层
- 等等等等…
那么使用Spring Data来访问数据库,我们所需的代码量极少,它能够大大提高我们的开发效率。
接下来介绍Spring Data持久化的操作
Spring Data操作实例
首先进行开发环境搭建,基于Spring、Maven的环境搭建这里不再详述,这里我们需要在xml配置文件当中,配置我们的JPA信息。
首先配置数据源:
<!--配置数据源文件,使用DBCP2连接池-->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${userName}"/>
<property name="password" value="${password}"/>
</bean>
配置实体类管理工厂如下:
<!--配置实体管理工厂-->
<bean id="entityManageFactory" 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.lucky"></property>
<property name="jpaProperties">
<props>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
这里我们将上述数据源引入,配置JPA适配器,因为我们需要使用注解进行操作,那么我们设置扫描包的路径,这里根据自己项目路径即可。接下来配置JPA一系列属性,包括命名策略,自动生成表(我们根据实体类生成表时需要用到),显示sql语句,格式化sql语句等。
接下来创建实体类如下:
package com.lucky.entity;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
/**
* @Description 用户主体信息实体类
*
* @Author zhenxing.dong
* @Date 2019/8/1 15:24
*/
@Entity
@Table(name = "t_user_main")
public class User {
/**
* 用户id
*/
private int id;
/**
* 用户名
*/
private String userName;
/**
* 用户邮箱
*/
private String email;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户角色
*/
private int role;
//定义主键自增
@Id
@GenericGenerator(name = "generator", strategy = "increment")
@GeneratedValue(generator = "generator")
@Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name = "user_name")
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Column(name = "email")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name = "nick_name")
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Column(name = "role")
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", email='" + email + '\'' +
", nickName='" + nickName + '\'' +
", role=" + role +
'}';
}
}
实体类中@Table name,@Column name可对应表名和列名,也可不设置,让其自动生成,在id上添加注解,使用自增策略。
项目运行时便可在数据库中生成该表。
自动生成的表中字段会按照其默认策略生成,如varchar字段会自动生成255长度的字段,我们在这里也可同过@Column字段对其进行设置,如在getUserName()方法上添加注释@Column(length = 20),便可生成长度为20的varchar字段。
接下来配置事务管理器
<!--配置事务管理器-->
<bean id="transcationManage" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManageFactory"></property>
</bean>
具体事务管理器配置可见我另一篇博客
Spring事务管理机制
添加支持注解的事务
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
配置Spring Data
<jpa:repositories base-package="com.lucky"></jpa:repositories>
到这里我们的开发环境就已经搭建完成了。
(以上步骤,在使用SpringBoot的情况下,几乎均可省略!!!仅需配置数据源例如:
)
那么我们开始编写我们的持久层接口:
例如:
package com.hx.kindergarten.mapper;
import com.hx.kindergarten.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
*
* @param
* @return
* @author zhenxing.dong
*/
public interface UserDao extends Repository<User,Integer> {
/**
*
* @param userName
* @return
*/
List<User> findAllByUserName(String userName);
/**
* 根据用户名查找用户
* @param userName
* @return user
*/
User findUserByUserName(String userName);
}
可以看到,我们在使用JPA的时候,所需要做的仅仅是继承它的一个类JpaRepository,其后两个参数,第一个是我们要操作的对象类型,这里我们操作User的实体类,那么第一个参数就为User,第二个参数是操作对象对应的主键类型,我们在User中使用Integer类型作为主键,那么这里便使用Integer。
接下来声明方法,例如我们通过用户名查询User对象,那么如上,我们仅需声明这个接口,甚至不需要写任何的实现类,框架底层会通过我们的方法名通过一些规则能帮我们生成好。
Spring Data 进阶
Repository接口详解
- Repository接口是Spring Data的核心接口,它不提供任何方法
- public interface JpaRepository<T, ID extend Serializable>{}
- @RepositoryDefintion 的使用
Repository是一个空接口,也就是一个标记接口,当我们声明的接口实现该接口的时候,他会自动将我们的接口归入Spring容器中进行管理,当我们的声明类没有继承该接口时,我们其实还有另一种方法来声明该接口
使用注解的方式:
package com.hx.kindergarten.mapper;
import com.hx.kindergarten.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
import java.util.List;
/**
*
* @param
* @return
* @author zhenxing.dong
*/
@RepositoryDefinition(domainClass = User.class,idClass = Integer.class)
public interface UserDao //extends Repository<User,Integer>
{
/**
*
* @param userName
* @return
*/
List<User> findAllByUserName(String userName);
/**
* 根据用户名查找用户
* @param userName
* @return user
*/
User findUserByUserName(String userName);
}
如上,@RepositoryDefinition(domainClass = User.class,idClass = Integer.class)这一行注解,也可以定义我们的持久化类,@RepositoryDefinition中有两个参数:
如上,domainClass定义我们的实体类,idClass定义实体类的主键类型。
Repository接口的子接口
Repository接口下具有许多的子接口,包括:
我们常用的几个:
- CrudRepository<T, ID> —— 封装了简单的增删改查操作
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.data.repository;
import java.util.Optional;
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> saveAll(Iterable<S> var1);
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable<? extends T> var1);
void deleteAll();
}
- PagingAndSortingRepository<T, ID>——继承自CrudRepository<T, ID>,实现了分页与排序功能,我们在业务中需要进行分页和排序的时候,可直接使用该接口。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.data.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort var1);
Page<T> findAll(Pageable var1);
}
- JpaRepository<T, ID> ——继承了PagingAndSortingRepository<T, ID>接口,同时它实现了JPA相关的一些规范。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.data.jpa.repository;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
//JPA或Hibernate中都对应有会话中的状态之类的标记,这里可刷新
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
Spring Data中查询方法的规范与规则
我们上面说了,我们在使用Spring Data时,仅需继承Repository类或其子接口,在声明方法时按照一定规则来声明,那么Spring框架底层会自动帮助我们来生成实现类执行这些方法,那么它既然能够帮助我们生成我们的具体方法实现,必然是有一定的规则的,上面的表格就是,我们在声明方法时需要遵循的一些命名规则,框架底层会帮助我们将方法名翻译为对应的SQL语句来与数据库进行交互。我们在需要的时候查表即可。
当然,这样的方式实现会造成两个问题:
- 方法名过于冗长
- 对应复杂查询我们很难通过方法名来构造
那么如何解决这些问题呢?
答案就是通过@Query注解的使用,来避免写过于冗长或复杂方法。
@Query注解
- 在Repository方法中使用,不需要遵循上述规则
- 使用时只要将@Query定义在方法之上即可。
- 支持命名参数以及索引参数的使用。
- 支持本地查询。
例如我们要查询id最大的User对象:
@Query("select user from User u where u.userId = (select max(userId) from User)")
User getMaxUserById();
通过占位符的方式,例如根据姓名和年龄查询:
@Query("select user from User u where u.userName=?1 and u.age=?2 ")
User getUserByUserNameAndAge2(String userName,Integer age);
通过参数的方式如下:
@Query("select user from User u where u.userName=:userName and u.age=:age ")
User getUserByUserNameAndAge2(@Param(value = "userName") String userName, @Param(value = "age")Integer age);
更新及删除操作
在删除更新操作中,我们仅用@Query注解是不够的,需要多加一个@Modifying注解来允许我们的修改操作。
例如:
@Modifying
@Query("UPDATE User u set u.userName =: userName where u.age =:age")
void update(@Param(value = "userName") String userName, @Param(value = "age")Integer age);
当然,在执行更新或者删除时时需要设置事务的,一般在service方法中,添加@Transactional注解即可。