[TOC]
SpringData JPA是spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,可以使开发者使用极简的代码实现对数据库的访问和操作。它提供了包括增删改查等在内的基本功能,且易于扩展。
springdata jpa、jpa和hibernate三者关系
通俗来讲springdata jpa是对jpa规范的一层封装,hibernate实现了jpa规范。
java代码----->springdata jpa ------>jpa规范------>hibernate------>jdbc ----->mysql数据库
graph LR
A[java代码] -->B(spring data jpa)
B --> |jpa规范| C(hibernate)
C -->|jdbc| D(mysql数据库)
我们使用java代码调用springdata jpa的api,springdata jpa封装了jpa规范,并且内部使用的是hibernate实现,hibernate封装了jdbc进行数据库操作。
入门案例
1、创建工程,导入依赖
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.3.Final'
compile group: 'org.hibernate', name: 'hibernate-c3p0', version: '5.4.3.Final'
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.1.9.RELEASE'
testCompile group: 'org.springframework', name: 'spring-test', version: '5.1.8.RELEASE'
2、编写spring配置文件
配置spring相关
数据源信息
jpa的实现方式
配置要用到的实体类
配置jpa实现方的配置信息
配置事务管理器
声明式事务
<?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:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:context="http://www.springframework.org/schema/context"
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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--spring-->
<!--配置spring的注解扫描-->
<context:component-scan base-package="com.lxf"/>
<!--spring data jpa-->
<!--整合spring data jpa-->
<jpa:repositories base-package="com.lxf.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager" />
<!--创建实体管理器工厂,交给spring管理-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置要扫描的包,实体所在包-->
<property name="packagesToScan" value="com.lxf.entity"/>
<!--配置jpa的实现方-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--jpa的实现方的配置-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--数据库类型-->
<property name="database" value="MYSQL"/>
<!--控制台显示sql语句-->
<property name="showSql" value="true"/>
<!--是否自动创建数据库表-->
<property name="generateDdl" value="true"/>
<!--数据库方言-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
</bean>
</property>
<!--jpa方言:高级特性-->
</bean>
<!--数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/study?serverTimezone=GMT"/>
<property name="user" value="root"/>
<property name="password" value="crystal1024"/>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--声明式事务-->
</beans>
3、创建实体类,编写实体类和数据库表关系映射
参考JPA规范。
4、编写dao层接口
-
需要继承两个接口
- JpaRepository:封装了增删改查分页排序等基本操作,具体可以看JpaRepository的父类
graph TB A[Repository] -->B(CrudRepository) B --> C(PagingAndSortingRepository) C -->D(JpaRepository)
- JpaSpecificationExecutor:封装了标准查询
-
提供相应的泛型
- JpaRepository
- 操作的实体类型
- 实体中主键类型
- JpaSpecificationExecutor
- 操作的实体类型
- JpaRepository
public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
}
- 会通过动态代理自动生成相应方法
5、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring_data_jpa.xml")
public class Test {
@Autowired
private UserDao userDao;
@org.junit.Test
public void textSave(){
User user = new User();
user.setName("小红");
user.setAge(22);
User userResult = userDao.save(user);
System.out.println(userResult);
}
}
User{id=4, name='小红', age=22, sex=null, address='null', phone='null'}
操作数据库
调用spring data jpa的api
插入/更新
- save方法:传入的实体对象有主键则更新,没有主键则插入。
@org.junit.Test
public void testSave(){
User user = new User();
user.setName("小红");
user.setAge(22);
User userResult = userDao.save(user);
System.out.println(userResult);
}
删除
- delete系列方法
@org.junit.Test
public void testDelete(){
userDao.deleteById(2);
}
查询
count:统计
exists系列方法:数据库中是否存在
find系列方法:立即加载
getOne:延迟加载,返回的是一个动态代理对象
@org.junit.Test
public void testFindOne(){
// Optional<User> user = userDao.findById(2);
// System.out.println(user.get());
User user = userDao.getOne(2);
System.out.println(user);
}
@org.junit.Test
public void testApi(){
long count = userDao.count();
boolean b = userDao.existsById(2);
}
语句操作
除了调用spring data jpa内置的api,我们也可以在dao接口中定义我们自己的方法,通过@Query声明jpql或sql语句。
- @Query
- value:数据库操作语句
- nativeQuery:是否是原生查询,默认false,即默认使用jpql查询
- @Modifying:声明当前是一个更新操作,需要修改数据库数据。
- 只能用于void或int/Integer的返回类型
- 因为需要修改数据库数据,未防止修改失败造成未知后果,需要搭配事务管理来是使用
- @Transactional:添加事务管理支持
- 一般需要设置rollbackFor或者noRollbackFor,来表示什么情况下进行事务回滚
- @Rollback:是否可以回滚,默认true
jpql查询
public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
@Query(value = "from User where name = :name and age = :age")
public User findUserByName(@Param("name") String userName,@Param("age") int age);
}
jpql更新
@Query(value = "update User set name = :name where id = :id")
@Modifying
public Integer updateNameById(@Param("id") int id,@Param("name") String userName);
@org.junit.Test
@Transactional(rollbackFor = Exception.class)
//@Rollback(value = false)//如果设置为fasle,即使发生异常也不会回滚
public void testJpql(){
User user = userDao.findUserByName("lili",18);
System.out.println(user);
userDao.updateNameById(user.getId(),"lili_2");
}
原生sql语句查询
public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
@Query(value = "select * from user where name = :name and age = :age",nativeQuery = true)
public User findUserByName(@Param("name") String userName,@Param("age") int age);
}
约定规则查询
spring data jpa制定了一些约定,如果按照这些约定来定义方法名,则会自动解析出sql语句。
findBy + 属性名 + 查询方式 + (And|Or) + 属性名 + 查询方式...
查询方式 | 方法命名 | sql where字句 |
---|---|---|
And | findByNameAndPwd | where name= ? and pwd =? |
Or | findByNameOrSex | where name= ? or sex=? |
Is,Equals | findById,findByIdEquals | where id= ? |
Between | findByIdBetween | where id between ? and ? |
LessThan | findByIdLessThan | where id < ? |
LessThanEquals | findByIdLessThanEquals | where id <= ? |
GreaterThan | findByIdGreaterThan | where id > ? |
GreaterThanEquals | findByIdGreaterThanEquals | where id > = ? |
After | findByIdAfter | where id > ? |
Before | findByIdBefore | where id < ? |
IsNull | findByNameIsNull | where name is null |
isNotNull,NotNull | findByNameNotNull | where name is not null |
Like | findByNameLike | where name like ? |
NotLike | findByNameNotLike | where name not like ? |
StartingWith | findByNameStartingWith | where name like '?%' |
EndingWith | findByNameEndingWith | where name like '%?' |
Containing | findByNameContaining | where name like '%?%' |
OrderBy | findByIdOrderByXDesc | where id=? order by x desc |
Not | findByNameNot | where name <> ? |
In | findByIdIn(Collection<?> c) | where id in (?) |
NotIn | findByIdNotIn(Collection<?> c) | where id not in (?) |
True | findByAaaTue | where aaa = true |
False | findByAaaFalse | where aaa = false |
IgnoreCase | findByNameIgnoreCase | where UPPER(name)=UPPER(?) |
简单挑几个示例:
public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
public User findByName(String name);
public User findByNameLike(String name);
public User findByNameLikeAndAge(String name, int age);
public List<User> findByIdBetween(int idMin, int idMax);
}
@org.junit.Test
public void testName(){
User user1 = userDao.findByName("tom");
System.out.println(user1);
User user2 = userDao.findByNameLike("t%");
System.out.println(user2);
User user3 = userDao.findByNameLikeAndAge("tom",18);
System.out.println(user3);
List<User> users = userDao.findByIdBetween(1, 3);
users.forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
}
Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.name=?
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.name like ? escape ?
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where (user0_.name like ? escape ?) and user0_.age=?
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.id between ? and ?
User{id=2, name='lili2', age=18, sex=1, address='null', phone='null'}
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
标准查询(Specification)
我们上面提到过,springdata jpa的dao层一般继承2个接口JpaRepository和JpaSpecificationExecutor。JpaRepository封装了crud、统计、排序、分页的常见操作,而JpaSpecificationExecutor基于JPA的criteria查询封装了另一种查询方式,我们之前一直在使用JpaRepositoru中的方法,下面来看下JpaSpecificationExecutor接口,它里面只提供了5个方法:
public interface JpaSpecificationExecutor<T> {
//查询一个
Optional<T> findOne(@Nullable Specification<T> spec);
//查询全部
List<T> findAll(@Nullable Specification<T> spec);
//查询全部 提供分页功能
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
//查询全部,提供排序功能
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
//统计
long count(@Nullable Specification<T> spec);
}
可以看到,这5个方法有个共同点,接收一个Specification参数。
Specification
Specification是对JPA规范中Root、CriteriaQuery、CriteriaBuilder的一层封装,用于构建过滤条件。实例化Specification需要实现它的toPerdicate方法:
//参数含义在我的另一文JPA规范中有介绍,简单说来Root用于获得查询属性,CriteriaBuilder用于构建过滤条件,CriteriaQuery用于指定最终查询语句,这里一般不会使用,默认为where语句。
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
注意这里创建出来的是where查询语句。
来个简单示例,查询表中年龄大于等于18的所有河南人:
@Test
public void test(){
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
//分别构造各个单属性的过滤条件
Predicate namePredicate = criteriaBuilder.like(root.get("address"), "河南%");
Predicate agePredicate = criteriaBuilder.ge(root.get("age"), 18);//大于等于
//组合成最终的过滤条件
Predicate predicate = criteriaBuilder.and(namePredicate, agePredicate);
return predicate;
}
};
//查询
List<User> users = userDao.findAll(specification);
users.forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
}
如果要添加排序和分页,可以使用Sort和Pageable。
- Sort:排序
Sort sort = new Sort(Sort.Direction.DESC,"id");//排序属性可以设置多个
List<User> users = userDao.findAll(specification,sort);
- Pageable:分页,是一个接口,可以通过PageRequest构建实例。
Sort sort = new Sort(Sort.Direction.DESC,"id");
//Pageable pageable = PageRequest.of(0,10);//pageIndex,pageSize
Pageable pageable = PageRequest.of(0,10,sort);
Page<User> users = userDao.findAll(specification, pageable);
users.forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
spring boot中的springdata jpa配置
application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/study?serverTimezone=GMT
username: root
password: crystal1024
jpa:
show-sql: true
hibernate:
ddl-auto: update