# SpringBoot Jdbc JPA
JPA是`Java Persistence API`的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,
并将运行期的实体对象持久化到数据库中
基于模板Dao的实现,使得这些具体实体的Dao层已经变的非常“薄”,有一些具体实体的Dao实现可能完全就是对模板Dao的简单代理,
并且往往这样的实现类可能会出现在很多实体上。
`Spring-data-jpa`的出现正可以让这样一个已经很“薄”的数据访问层变成只是一层接口的编写方式
JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,
只需要使用 `javax.persistence.Entity`进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。
JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
# JPA sql 语法 JPQL
查询语法主要分为三类:
* 查询用的 SELECT 语法
* 更新用的 UPDATE 语法
* 删除用的 DELETE 语法
SELECT 语法结构由几个部份组成:
SELECT 子句 FROM 字句 [WHERE 子句] [GROUP BY 子句] [HAVING 子句] [ORDER BY 子句]
一个基本的 SELECT 语句如下所示:
SELECT u.id, u.name FROM User u WHERE u.age > 10 AND u.age < 20
其中`User u`是个路径表示`(path expression)`,路径表示有三种:范围变数(Range variable)路径表示、群集成员(Collection member)路径表示与关联导览(Association traversing)表示。User u是范围变数路径表示的一个例子,指定查询的实体为User与别名为u。
一个群集成员路径表示用来指定物件中的群集成员,例如:
SELECT u FROM User u, IN(u.emails) e WHERE e.address LIKE '%.%@openhome.cc'
其中IN中指定的,就是群集成员路径表示,而>、<、AND、IN、LIKE等都是WHERE子句中条件表示式,简单列出一些条件表示式如下:
比较陈述 =、>、>=、<、<=、<>
BETWEEN 陈述 [NOT BETWEEN
LIKE 陈述 [NOT] LIKE
IN 陈述 [NOT] IN
NULL 陈述 IS [NOT] NULL
EMPTY 陈述 IS [NOT] EMPTY
EXISTS 陈述 [NOT] EXISTS
LIKE中,可以用_表示比对单一字元,用%表示比对任意数目字元。
关联导览表示则提供SQL语法中JOIN的功能,包括了`INNER JOIN`、`LEFT OUTER JOIN`、`FETCH`等,以下为INNER JOIN的实际例子:
SELECT u FROM User u INNER JOINu.emails e WHERE e.address LIKE '%.%@openhome.cc'
JOIN关键字可以省略,上式等同於:
SELECT u FROM User u JOINu.emails e WHERE e.address LIKE '%.%@openhome.cc'
LEFT OUTER JOIN的OUTER关键字可以省略,一个例子如下:
SELECT u FROM User u LEFT JOIN u.emails e WHERE e.address LIKE'%.%@openhome.cc'
在作INNER JOIN、LEFT OUTER JOIN可以加上FETCH关键字,以预先撷取相关资料,例如:
SELECT u FROM User u LEFT JOIN FETCH u.emails e WHERE e.address LIKE'%.%@openhome.cc'
SELECT中可以使用聚集函式,例如:
SELECT AVG(u.age) FROM User u
SELECT中可以使用建构表示,直接将一些资料封装为指定的物件,例如:
SELECT NEW SomeObject(u.id, u.name, o.number) FROM User u JOIN Order o WHERE u.id = 1975
WHERE子句中可以使用LENGTH()、LOWER()、UPPER()、SUBSTRING()等JPQL函式。
可以对查询结果使用ORDER BY进行排序:
SELECT u FROM User u ORDER BY u.age
ORDER预设是ASC昇幂排序,可使用DESC降幂排序:
SELECT u FROM User u ORDER BY u.age DESC
可同时指定两个以上的排序方式,例如先按照"age"降幂排序,如果"age"相同,则按照"name"昇幂排列:
SELECT u FROM User u ORDER BY u.age DESC, u.name
可以配合GROUP BY子句,自动将指定的栏位依相同的内容群组,例如依栏位"sex"分组并作平均:
SELECT u.sex, AVG(u.age) FROM User u GROUP BY u.sex
GROUP BY通常搭配HAVING来使用,例如:
SELECT u.sex, avg(u.age) FROM User u GROUP BY u.sex HAVING AVG(u.age) > 20
可以使用UPDATE语法来更新资料,例如:
UPDATE User u SET u.name='momor' WHERE u.name='bbb'
可以透过DELETE来删除资料,例如:
DELETE User u WHERE u.name='bush'
## 选择查询编辑
SELECT <select_expression>
FROM <from_clause>
[WHERE <conditional_expression>]
[ORDER BY <order_by_clause>]
## 聚合查询编辑
SELECT <select_expression>
FROM <from_clause>
[WHERE <conditional_expression>]
[GROUP BY <group_by_clause>]
[HAVING <conditional_expression>]
[ORDER BY <order_by_clause>]
## 更新查询编辑
UPDATE <entity name>[ [AS ] <identification variable>]
SET <update_statement>{,<update_statement>}*
[WHERE <conditional_expression>]
## 删除查询编辑
DELETE FROM <entity name>[ [AS ] <identification variable>]
[WHERE <conditional_expression>]
# 项目引入
继承自`JpaRepository`的接口就能完成数据访问
Spring-data-jpa依赖于Hibernate
jap 相关依赖,
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
在`application.xml`中配置:数据库连接信息(如使用嵌入式数据库则不需要)、自动创建表结构的设置,例如使用mysql的情况如下:
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
`spring.jpa.properties.hibernate.hbm2ddl.auto`是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。
该参数的几种配置如下:
* `create`:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,
哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
* `create-drop`:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
* `update`:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),
以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。
要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
* `validate`:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
至此已经完成基础配置,如果您有在Spring下整合使用过它的话,相信你已经感受到Spring Boot的便利之处:JPA的传统配置在persistence.xml文件中,但是这里我们不需要。当然,最好在构建项目时候按照之前提过的最佳实践的工程结构来组织,这样以确保各种配置都能被框架扫描到。
## 实体关联
给实体添加`@Entity` 类注解,属性添加相应的注解`@Id`, `@GeneratedValue`, `@Column`等
## 创建对应的接口实现对该实体的数据访问
创建接口以`Repository`方式命名便于理解,接口继承自`JpaRepository`
# JPA api文档
https://docs.spring.io/spring-data/data-jpa/docs/current/api/
## JPA 通过解析方法名创建查询
User findByName(String name)
User findByNameAndAge(String name, Integer age)
上面分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法
| Keyword | Sample | JPQL snippet |
| -------- | -----: | :----: |
| IsNotNull | findByAgeNotNull|... where x.age not null |
| Like | findByNameLike|... where x.name like ?1 |
| NotLike | findByNameNotLike | ... where x.name not like ?1 |
| StartingWith | findByNameStartingWith | ... where x.name like ?1(parameter bound with appended %) |
| EndingWith | findByNameEndingWith | ... where x.name like ?1(parameter bound with prepended %) |
| Containing | findByNameContaining | ... where x.name like ?1(parameter bound wrapped in %) |
| OrderBy | findByAgeOrderByName | ... where x.age = ?1 order by x.name desc |
| Not | findByNameNot | ... where x.name <> ?1 |
| In | findByAgeIn | ... where x.age in ?1 |
| NotIn | findByAgeNotIn | ... where x.age not in ?1 |
| True | findByActiveTrue | ... where x.avtive = true |
| Flase | findByActiveFalse | ... where x.active = false |
| And | findByNameAndAge | ... where x.name = ?1 and x.age = ?2 |
| Or | findByNameOrAge | ... where x.name = ?1 or x.age = ?2 |
| Between | findBtAgeBetween | ... where x.age between ?1 and ?2 |
| LessThan | findByAgeLessThan | ... where x.age < ?1 |
| GreaterThan | findByAgeGreaterThan | ... where x.age > ?1 |
| After/Before | ... | ... |
| IsNull | findByAgeIsNull | ... where x.age is null |
## JpaRepository相关查询功能
> a.Spring Data JPA框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如`find`、`findBy`、`read`、`readBy`、`get`、`getBy`,然后对剩下部分进行解析。
> b.假如创建如下的查询:`findByUserDepUuid()`,框架在解析该方法时,首先剔除`findBy`,然后对剩下的属性进行解析,假设查询实体为Doc。
1:先判断`userDepUuid` (根据POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
2:从右往左截取第一个大写字母开头的字符串此处为`Uuid`),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设user为查询实体的一个属性;
3:接着处理剩下部分(`DepUuid`),先判断user 所对应的类型是否有`depUuid`属性,如果有,则表示该方法最终是根据“ `Doc.user.depUuid`” 的取值进行查询;否则继续按照步骤2 的规则从右往左截取,最终表示根据“`Doc.user.dep.uuid`” 的值进行查询。
4:可能会存在一种特殊情况,比如Doc包含一个`user` 的属性,也有一个`userDep` 属性,此时会存在混淆。可以明确在属性之间加上"_" 以显式表达意图,比如"`findByUser_DepUuid()`" 或者"`findByUserDep_uuid()`"
> c.特殊的参数: 还可以直接在方法的参数上加入分页或排序的参数,比如:
```
Page<UserModel> findByName(String name, Pageable pageable);
List<UserModel> findByName(String name, Sort sort);
```
> d.也可以使用JPA的`NamedQueries`,方法如下:
1:在实体类上使用`@NamedQuery`,示例如下:
```
@NamedQuery(name = "UserModel.findByAge",query = "select o from UserModel
o where o.age >= ?1")
```
2:在自己实现的DAO的Repository接口里面定义一个同名的方法,示例如下:
```
public List<UserModel> findByAge(int age);
```
3:然后就可以使用了,Spring会先找是否有同名的NamedQuery,如果有,那么就不
会按照接口定义的方法来解析。
> e.还可以使用@Query来指定本地查询,只要设置`nativeQuery`为`true`,比如:
```
@Query(value="select * from tbl_user where name like %?1" ,nativeQuery=true)
public List<UserModel> findByUuidOrAge(String name);
```
注意:当前版本的本地查询不支持翻页和动态的排序
> f.使用命名化参数,使用`@Param`即可,比如:
```
@Query(value="select o from UserModel o where o.name like %:nn")
public List<UserModel> findByUuidOrAge(@Param("nn") String name);
```
> g.同样支持更新类的Query语句,添加`@Modifying`即可,比如:
```
@Modifying
@Query(value="update UserModel o set o.name=:newName where o.name like %:nn")
public int findByUuidOrAge(@Param("nn") String name,@Param("newName") String
newName);
```
注意:
1:方法的返回值应该是int,表示更新语句所影响的行数
2:在调用的地方必须加事务,没有事务不能正常执行
> h.创建查询的顺序
Spring Data JPA 在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?
<jpa:repositories> 提供了`query-lookup-strategy `属性,用以指定查找的顺序。它有如下三个取值:
1:`create-if-not-found`:如果方法通过@Query指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方法名字来创建查询。这是`querylookup-strategy` 属性的默认值
2:`create`:通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过@Query指定的查询语句,都将会被忽略
3:`use-declared-query`:如果方法通过@Query指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常
## JPA 通过SQL查下
提供通过使用`@Query` 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样
在`@Query` 有个参数定义为是否实用实体JPA查询,或者纯SQL查询. `nativeQuery`