Jpa是非常简单好用的ORM框架,基于Hibernate。
参考:
Spring Boot中的事务管理
Spring Data JPA - Reference Documentation
本文主要讲解jpa使用以及一些有意思的特性。
pom.xml文件添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
application.yml添加
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password:
driver-class-name: com.mysql.jdbc.Driver
jpa:
properties:
hibernate:
hbm2ddl:
auto: validate # create 每次加载删除重建 update 表结构不同修改 validate 验证表结构 推荐
logging:
level:
org:
hibernate:
type: trace # 打印jpa生成sql信息
Dao
jpa提供常用模板。
CrudRepository
<S extends T> S save(S var1); // insert 如果有主键id 则 update
<S extends T> Iterable<S> save(Iterable<S> var1);
T findOne(ID var1); // 根据主键查找
boolean exists(ID var1); // 判断是否存在
Iterable<T> findAll(); // 查找所有数据
Iterable<T> findAll(Iterable<ID> var1); // 根据ids查找数据
long count(); // 计算表行数
void delete(ID var1); // 根据id删除
void delete(T var1);
void delete(Iterable<? extends T> var1);
void deleteAll(); // 清空表
JpaRepository
继承自PagingAndSortingRepository、CrudRepository。
Iterable<T> findAll(Sort var1);
Page<T> findAll(Pageable var1); // 分页查询
在写Dao时继承JpaRepository即可。
在写MyBatis时我们会把方法对应sql写出,调用方法时即可使用对应sql;JPA更加智能化,可以直接根据你的方法名生成对应sql。
/*
select
userdo0_.id as id1_0_,
userdo0_.address as address2_0_,
userdo0_.age as age3_0_,
userdo0_.name as name4_0_
from
t_user userdo0_
where
userdo0_.name=?
*/
List<UserDO> queryByName(String name);
/*
select
count(userdo0_.id) as col_0_0_
from
t_user userdo0_
where
userdo0_.name=?
*/
Long countByName(String name);
/*
select
userdo0_.id as id1_0_,
userdo0_.address as address2_0_,
userdo0_.age as age3_0_,
userdo0_.name as name4_0_
from
t_user userdo0_
where
userdo0_.name=? 先查询然后删除
*/
Long deleteByName(String name);
...............
那么JPA是根据哪些关键字来生成对应sql呢。
关键字 | 例子 | 对应sql片段 |
---|---|---|
find,query | findByName | select … where x.name = ?1 |
delete | deleteByName | delete … where x.name = ?1 |
count | countByName | select count(x.id) ... where x.name=?1 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> age) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
根据jpa的规则可以快速写出查询方法,简单好用。
事务
@Transactional(isolation = , propagation = )
isolation: 隔离级别 默认 Isolation.DEFAULT
DEFAULT(-1), # 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED。
READ_UNCOMMITTED(1), # 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
READ_COMMITTED(2), # 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
REPEATABLE_READ(4), # 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
SERIALIZABLE(8); # 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
propagation: 传播行为 默认 Propagation.REQUIRED
REQUIRED(0), # 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
SUPPORTS(1), # 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
MANDATORY(2), # 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
REQUIRES_NEW(3), # 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
NOT_SUPPORTED(4), # 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
NEVER(5), # 以非事务方式运行,如果当前存在事务,则抛出异常。
NESTED(6); # 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
常用注解
@Query
可以指定hql或sql语句进行查询。
@Query(value = "select u from UserDO u where u.name=:name")
List<UserDO> findByNameHql1(@Param("name") String name);
@Modifying
@Query(默认只能执行查询语句)加上Modifying之后可以执行更改删除语句。
@Modifying
@Transactional
@Query(value = "update UserDO u set u.name=?1 where u.id=?2")
int updateNameById(String name, Integer id);
新特性
@stream
以流的形式处理 jdk 1.8。
Stream<UserDO> findByName(String name);
@Transactional(readOnly = true)
@Test
public void testStream() {
Stream<UserDO> stream = userDao.findByName("wrh");
stream.forEach(userDO ->
System.out.println(userDO.getName())
);
}
@future
如果查询时间较长,以异步形式执行sql。
@Async
Future<UserDO> findByAddress(String address);
@Test
public void testFuture() throws ExecutionException, InterruptedException {
Future<UserDO> future = userDao.findByAddress("beijing");
UserDO userDO = future.get();
System.out.println(userDO.getName());
}
分页
分页作为查询必不可少的功能,jpa也做了适当优化。
Page<T> findAll(Pageable var1); # 通过Pageable来实现分页
@Test
public void testPage() {
userDao.save(new UserDO("a1", 1, "beijing"));
userDao.save(new UserDO("a2", 2, "beijing"));
userDao.save(new UserDO("a3", 3, "beijing"));
# PageRequest 参数分别是页码 pageNumber 每页记录数 pageCount limit (pageNumber - 1) * pageCount�, pageCount
Page<UserDO> userDOs = userDao.findAll(new PageRequest(0, 1));
Assert.assertEquals("a1", userDOs.getContent().get(0).getName());
}
不足
如果我想把查到的数据放到一个Vo对象中比较麻烦;一次关联多张表进行查询处理起来比较复杂。
一个简单的关联多表查询操作:
public List<Object[]> queryByNameSql(String name) { // 执行sql语句
Query query = manager.createNativeQuery("select u.name,ui.email from t_user u INNER JOIN t_user_info ui where name=?");
query.setParameter(1, name);
return query.getResultList();
}
所以我认为使用JPA时尽可能把查询操作限定在单表中,扬长避短。