一,spring-data-jpa的简单介绍
SpringData:Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。
SpringData的主要模块(子项目)
Spring Data JPA: 是springData的子项目,致力于减少数据访问层 (DAO) 的开发量, 开发者唯一要做的就只是声明持久层的接口,并继承Repository(或Repository的子接口即可)
,其他都交给 Spring Data JPA 来帮你完成!
二,spring-data-jpa的快速入门
创建一个maven java项目
导入的依赖:
spring-data-jpa
Hibernate相关依赖
mysql驱动
c3p0连接池
junit测试
pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.xiaogui.example</groupId>
<artifactId>springdatajpa-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springdatajpa-example</name>
<description>spring-data-jpa的简单使用</description>
<!-- 全局属性配置 -->
<properties>
<project.source.encoding>utf-8</project.source.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 防止控制输出台中文乱码 -->
<argLine>-Dfile.encoding=UTF-8</argLine>
<spring-data-jpa.version>1.10.4.RELEASE</spring-data-jpa.version>
<hibernate.version>5.0.11.Final</hibernate.version>
<junit.version>4.12</junit.version>
<mysql.version>5.1.41</mysql.version>
<c3p0.version>0.9.1.2</c3p0.version>
</properties>
<dependencies>
<!-- spring-data-jpa相关依赖
(这个依赖自动把一堆spring的东西依赖进来了,所有可以不需要再引入spring的包)-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa.version}</version>
</dependency>
<dependency>
<!-- 做测试使用 -->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<!-- 这个包只在测试使用 -->
<scope>test</scope>
</dependency>
<!-- hibernate相关依赖 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--c3p0连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<!--源码1.7 -->
<source>1.7</source>
<!-- 打包1.7 -->
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
由于spring-data-jpa依赖了一堆spring相关的依赖,会自动导入spring相关的一些依赖,所以在pom.xml中不需要引入spring相关的依赖
Spring整合springDataJpa,applicationContext.xml配置
1.首先新建db.properties文件,配置数据库相关的信息
jdbcUrl=jdbc:mysql://localhost:3306/springdatajpa?characterEncoding=utf8
driverClass=com.mysql.jdbc.Driver
user=root
password=root
initialPoolSize=10
maxPoolSize=30
2.创建applicationContext.xml文件,配置内容如下:
2.2配置包扫描,扫描@Service,@Contorller @Repository
2.3加载springdb.properties文件
2.4配置数据源dataSource
2.5配置JPA的EntityManagerFactory,这里面配置主要包括:
(1)需要配置数据源dataSource;
(2)配置包扫描packagesToScan,扫描实体类,找到实体类上有关JPA的注解;
(3)配置持久化提供者persistenceProvider;
(4)配置jpa网络适配器jpaVendorAdapter,这个里面主要是关于是否自动建表generateDdl
,使用的数据库database
,数据库方言databasePlatform
,在控制台上显示sql语句showSql
等配置
2.6配置事务管理器 transactionManager
2.7配置支持注解的事务
2.8配置SpringData这里包扫描是扫描dao层,扫描那些定义的接口
<?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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.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">
<!-- 配置自动扫描的包,扫描@service @controller @Repository注解-->
<context:component-scan base-package="cn.xiaogui"/>
<!-- 1. 配置数据源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbcUrl}"/>
<property name="driverClass" value="${driverClass}"/>
<property name="user" value="${user}"/>
<property name="password" value="${password}"/>
<property name="initialPoolSize" value="${initialPoolSize}"/>
<property name="maxPoolSize" value="${maxPoolSize}"/>
</bean>
<!-- 2. 配置 JPA 的 EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置包扫描,扫描实体 -->
<property name="packagesToScan" value="cn.xiaogui.entity"/>
<!-- 配置持久化提供者 -->
<property name="persistenceProvider">
<bean class="org.hibernate.ejb.HibernatePersistence" />
</property>
<!-- 配置jpa网络适配器 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!-- 是否自动建表 -->
<property name="generateDdl" value="true" />
<!-- 使用的数据库 -->
<property name="database" value="MYSQL" />
<!--数据库方言 -->
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<!-- 在控制台打印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="entityManagerFactory"/>
</bean>
<!-- 4. 配置支持注解的事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 5. 配置 SpringData,需要加入 jpa 的命名空间 -->
<!-- base-package: 扫描 Repository Bean 所在的 package -->
<jpa:repositories base-package="cn.xiaogui.dao" entity-manager-factory-ref="entityManagerFactory" />
</beans>
3.测试整合
3.1先测试spring容器是否整合成功
在cn.xiaogui.test包下,创建ConfigTest测试类,编写单元测试代码,主要内容:
通过静态代码块创建ClassPathXmlApplicationContext
对象,加载applicationContext.xml
文件,启动spring容器。通过Spring容器获取dataSource对象,如果成功获取,并且说明整合成功了。
测试代码
package cn.xiaogui.test;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.xiaogui.dao.UserRepository;
/**
*
* @author xiaogui
*
* Spring整合测试
*
*/
public class ConfigTest {
private static ApplicationContext applicationContext;
static{
//使用静态代码块,让程序来加载spring配置文件
applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
/** 测试spring容器是否实例化了数据源 。如果实例化了,说明Spring容器整合没问题 */
@Test
public void testDataSouce() throws SQLException {
DataSource dataSouce = (DataSource) applicationContext.getBean("dataSource");
System.out.println("数据源:"+ dataSouce);
System.out.println("连接:"+ dataSouce.getConnection());
}
}
输出结果:
说明spring整合成功
3.2测试spring整合JPA
整合JPA是否成功主要是看entityManagerFactory这个对象是否起作用,这个会起作用就会去扫描cn.xiaogui.entity包下的实体类,测试方法:
(1)数据库提前创建出来,使用到的数据库springdatajpa我已经创建
(2) 给实体类加上注解,然后开启Hibernate自动建表功能,启动Spring容器;
如果数据库自动建表成功,那就整合成功
实体类相对简单,只贴入关键部分
User类
@Entity //表明这个一个实体
@Table(name="t_user") //数据库中实体对应的表名
public class User {
@Id //id主键标识符
@GeneratedValue(strategy=GenerationType.IDENTITY) //主键生成策略,自动增长
private Integer id;
@Column //对应数据表中的字段名,不写默认与实体类的属性名一致
private String username;
@Column
private String city;
@OneToMany(mappedBy="users",fetch=FetchType.EAGER) //一对多,mappedBy:表示放弃主键维护,交给多的一方管理,一的一方fetch默认为FetchType.LAZY懒加载
private List<Car> cars = new ArrayList<Car>();
Car类
@Entity //表明这个一个实体
@Table(name="t_car") //数据库中实体对应的表名
public class Car {
@Id //id主键标识符
@GeneratedValue(strategy=GenerationType.IDENTITY)//主键生成策略,自动增长
@Column //对应数据表中的字段名,不写默认与实体类的属性名一致
private Integer id;
@Column
private String carName;
@Column
private Double price;
@ManyToOne //多对一
@JoinColumn(name="user_id") //外键在数据表中的字段
private User users;
再次执行ConfigTest类中的测试代码,如果数据库中创建了User和Car两张数据表,表明整合JPA成功!
4.在dao层声明接口
在cn.xiaogui.dao包下创建UserRepository
接口,并继承Repository
接口或者Repository的子接口,由于Repository是一个空接口,博主选择继承Repository的子接口JpaRepository
,在这个接口中提供了一些常规CRUD的操作。而JpaRepository的实现类SimpleJpaRepository
对这些CRUD的操作进行了具体的实现,所以我们编写的UserRepository接口可以直接使用这些方法,而不需要进行具体的实现。
JpaRepository定义的规范:
SimpleJpaRepository对JpaRepository的实现
UserRepository接口代码
package cn.xiaogui.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import cn.xiaogui.entity.User;
public interface UserRepository extends JpaRepository<User, Integer> {
}
在cn.xiaogui.test包下创建UserRepositoryTest测试类,通过这个类,我们进行springData的学习。
package cn.xiaogui.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.xiaogui.dao.UserRepository;
import cn.xiaogui.entity.User;
/**
*
* @author xiaogui
*
* Spring整合测试
*
*/
public class UserRepositoryTest {
private static ApplicationContext applicationContext;
private static UserRepository userRepository;
static{
applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); //使用静态代码块,让程序来加载spring配置文件
userRepository = applicationContext.getBean(UserRepository.class); //通过反射获取到UserRepository接口对象
}
/**
* 向t_user数据表中添加一条数据,并查询这条记录
*/
@Test
public void insertUser() {
User user = new User();
user.setUsername("张三");
user.setCity("北京");
//向数据表中保存一条数据
userRepository.save(user);
//通过用户名,查询用户的信息
user = userRepository.findOne(1);
System.out.println(user);
}
}
执行结果如下图所示,我们dao层接口UserReository接口只继承了JpaRepository接口,就可以完成数据添加到数据库,从数据库中查询数据操作,这就是SpringData的魅力所在,大大简化了我们持久层的开发
三,spring-data-jpa原理分析
springData 出现的目的:为了简化,统一持久层 各种实现技术API,所以,springData 提供了一套标准的API 和 不同持久层整合技术的实现。
spring-data-commons :一套标准的API
spring-data-jpa:基于整合jpa实现,还对spring-data-commons的一套标准API进行了扩展
画图表示上述接口间的继承关系
对上述接口中的API进行讲解,其中Repository是空接口,不作讲解
CrudRepository接口API
PagingAndSortingRepository接口API
JpaRepository接口API
JpaSpecificationExecutor接口API(当需要进行带条件分页查询,多条件查询实体时,Dao层继承这个接口)
由于SimpleJpaRepository这个类对上述的这些API进行了具体的实现,所以我们dao层接口只要继承这些接口即可直接使用这些API。
画图简单分析调用方法的执行流程
四、spring data Query使用 实现条件查询
第一种方式:基于方法命名规则,自动生成JPQL查询语句
上图是对方法命名规则的说明
在cn.xiaogui.test包下创建UserRepositoryCriteriaQuery类,完成条件查询。
案例说明
基于一列的等值查询 findBy列名 例如:findByUsername(String name)
package cn.xiaogui.test;
import java.util.List;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.xiaogui.dao.UserRepository;
import cn.xiaogui.entity.User;
public class UserRepositoryCriteriaQuery {
private static ApplicationContext applicationContext;
private static UserRepository userRepository; //UserRepository接口的代理对象
static{
//读取spring配置文件,加载spring容器
applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
userRepository = applicationContext.getBean(UserRepository.class);
}
/**
* 基于一列的等值查询
*/
@Test
public void findByUsername() {
List<User> list = userRepository.findByUsername("张三");
System.out.println(list);
}
}
dao层UserRepository接口只需要创建这个方法,不需要进行具体实现
package cn.xiaogui.dao;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import cn.xiaogui.entity.User;
public interface UserRepository extends JpaRepository<User, Integer> {
/**
* 基于一列的等值查询,不需要进行实现
* @param string
* @return
*/
List<User> findByUsername(String string);
}
控制台查询结果如下:
基于一列的模糊查询 findBy列名Like 例如:findByUsernameLike(String name)
由于在条件查询测试类UserRepositoryCriteriaQuery中,加载spring的配置文件,创建UserRepository接口的代理对象代码相同,以下的代码不再重复,只粘贴关键部分
UserRepositoryCriteriaQuery代码片段
@Test
public void findByUsernameLike() {
List<User> list = userRepository.findByUsernameLike("%张%"); //注意:传入参数 %参数%
System.out.println(list);
}
UserRepository接口代码片段,同样这个方法也不需要具体实现
/**
* 基于一列的模糊查询
* @param string
* @return
*/
List<User> findByUsernameLike(String string);
控制台输出结果
方法命名规则过多,自己可以尝试,不做过多赘述
第二种方式:不按命名规则我们自定义的查询方法,可以在dao层接口的执行方法上配置@Query注解,绑定JPQL语句或SQL语句
在UserRepository接口,自定义一个查询方法,由于没有按照方法命名规则,直接报错
可以在方法上配置@Query注解
,表明这是一个查询方法,value
绑定的是JPQL语句或SQL语句,这个有nativeQuery
参数决定。默认为false,表示绑定的是JPQL语句,设置为true,绑定的是SQL语句
/**
* 自定义方法,基于两列的等值查询
* @param city
* @param username
* @return
*/
@Query(value="from User where username = ? and city = ?",nativeQuery=false)
User queryUsernameAndCity(String city,String username);
UserRepositoryCriteriaQuery代码片段
/**
* 自定义的查询方法,基于两列的等值查询
*/
@Test
public void queryUsernameAndCity() {
User user = userRepository.queryUsernameAndCity("北京", "张三");
System.out.println(user);
}
控制台输出结果:
数据表中存在这条记录,却没有查询出来。检查UserRepository接口中定义的这个查询方法,原来传入的参数与绑定的JPQL语句中的占位符
没有对应上。
解决方法:
(1) 在占位符后面添加标注,与传入的参数进行对应
/**
* 自定义方法,基于两列的等值查询
* @param city
* @param username
* @return
*/
@Query(value="from User where username = ?2 and city = ?1",nativeQuery=false)
User queryUsernameAndCity(String city,String username);
(2)在该方法传入的参数前面配置@param注解,@param中的参数名称要和value中的 :后面的名字对应
/**
* 自定义方法,基于两列的等值查询
* @param city
* @param username
* @return
*/
@Query(value="from User where username = :userName and city = :city",nativeQuery=false) //@param中的参数名称要和value中的 :后面的名字对应
User queryUsernameAndCity(@Param("city") String city,@Param("userName") String username);
使用第二种解决方案,再次执行UserRepositoryCriteriaQuery中这个方法,查询到数据
第三种方式:不按命名规则我们自定义的查询方法,可以在dao层接口的执行方法上配置@Query注解,将JPQL语句或SQL语句绑定到实体类上
UserRepository接口代码片段
/**
* 自定义方法,基于两列的等值查询
* @param city
* @param username
* @return
*/
@Query
User queryUsernameAndCity2(@Param("city")String city, @Param("username")String username);
User实体类代码片段
@NamedQueries({
@NamedQuery(name="User.queryUsernameAndCity2",
query = "from User where username = :username and city = :city")})
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column
private String username;
@Column
private String city;
UserRepositoryCriteriaQuery条件查询测试类调用方法,可以查询到结果数据
上述三种条件查询方式,推荐使用第一种方法命名规则查询
,不建议使用第三种,过于繁琐
五,带条件的 修改和删除操作
使用@Query注解,搭配@Modifying注解完成 带条件的修改,删除操作
案例说明:根据id修改用户名
在UserRepository接口代码片段
/**
* 自定义修改操作,根据id修改用户名
* UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作.
* 默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!
*/
@Query(value="update User set username= ?2 where id = ?1")
@Modifying //表示更新操作(修改或删除)
void updateUsername(Integer id,String username);
由于修改和删除都是更新操作,只读事务是不能实现的,因此新建UserService类,在Service方法中添加事务注解
UserService代码片段
package cn.xiaogui.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.xiaogui.dao.UserRepository;
@Service
public class UserService {
@Autowired
private UserRepository userRepository; //注入dao层接口对象
/**
* 根据id修改用户名
*/
public void updateUsername() {
userRepository.updateUsername(1, "小鬼");
}
}
在UserRepositoryCriteriaUpdate测试类中调用UserService中的这个方法测试
package cn.xiaogui.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.xiaogui.service.UserService;
public class UserRepositoryCriteriaUpdate {
private static ApplicationContext applicationContext;
private static UserService userService;
static{
//读取spring配置文件,加载spring容器
applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
userService = applicationContext.getBean(UserService.class);
}
/**
* 根据id修改用户名
*/
@Test
public void updateUsername() {
userService.updateUsername();
}
}
控制台输出
根据控制台报出的错误,我们需要在UserService中设置事物
/**
* 根据id修改用户名
*/
@Transactional
public void updateUsername() {
userRepository.updateUsername(1, "小鬼");
}
再次在UserRepositoryCriteriaUpdate测试类中调用UserService中的这个方法测试,数据修改成功
查询数据表
删除操作不再赘述,大家可以自己进行尝试!
六,带条件的分页查询
首先在数据表t_user中添加数据
实现带条件的分页查询,主要使用到前面提到的JpaSpecificationExecutor
接口中定义的API,这个接口的主要作用就是进行带条件的查询
进行带条件的分页查询,使用的API是这个接口中的
Page findAll(Specification spec, Pageable pageable);
参数:
spec:构造Specification对象条件查询对象(类似hibernate的QBC查询),由于这个参数是一个接口对象,通过匿名对象创建
pageable:封装了请求分页的信息: page,size,sort三个参数,其中page索引从0开始,表示第一页
PageRequest类实现了Pageable接口
由于JpaSpecificationExecutor接口不属于Repository体系,所以我们要使用带条件的分页查询,dao层接口需要同时继承JpaRepository,JpaSpecificationExecutor这两个接口
在UserRepositoryCriteriaQuery测试类中编写带条件的分页查询代码
/**
* 实现带条件的分页查询
*/
@Test
public void findPageData() {
// 目标:查询id<5 并且city为北京的 的第1页的数据,每页显示为5条记录
//创建pageable对象
Pageable pageable = new PageRequest(1-1, 5); //0 表示从第一页开始 5 表示每页显示多少条数据
//根据查询条件,构造Specification对象条件查询对象(类似hibernate的QBC查询)
Specification<User> spec = new Specification<User>() {
/**
* 构造条件查询方法,如果返回值为null,表示无条件查询,查询所有
*
* root参数:代表查询的实体类,用来 获取条件表达式 username = ? city =? 等
* query参数:可以从中可到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
* 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象
* cb参数:构造Predicate对象 条件对象,构造复杂查询效果
* @return: Predicate 类型, 代表一个查询条件.
*/
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//在开发过程中,一般使用root和cb这两个参数
//定义一个list集合,用于存放多个条件
List<Predicate> predicates = new ArrayList<Predicate>();
//查询id小于5的数据
Predicate p1 = cb.lt(root.get("id").as(Integer.class), 5);
predicates.add(p1);
Predicate p2 = cb.equal(root.get("city"), "北京");
predicates.add(p2);
//当predicates中没有元素时,表示无条件查询
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
//得到分页对象
Page<User> page = userRepository.findAll(spec , pageable);
//输出查询到的结果
System.out.println(page.getContent());
}
在控制台输出结果
小结:
主要对springdatajpa的常用操作进行了简单的说明介绍。
1.如何使用springdata自带的API及springdatajpa自身扩展的API
2.根据方法命名规则进行条件查询,以及通过@query注解,进行自定义方法进行条件查询
3.使用@query ,@modifying注解完成修改或删除的更新操作
4.最后使用JpaSpecificationExecutor接口中的Page findAll(Specification spec, Pageable pageable)方法,完成带条件的分页查询
分享示例项目在码云上的地址:https://gitee.com/xiaoguixiaogege/springdatajpa