JPA是什么?
其实JPA也不是第一天听说,Coding中也用了一段时间了,突然想起来JPA到底是什么东西,想干什么? 似乎很高深的样子^_^, 自己反而说不敢肯定地说这东西是什么,感觉查了一把,原来就是三个字:ORM, 还好,没有理解错(惭愧,这段时间经常有点信心缺失的感觉)。 下面是一些官样文字:
JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
应该是SUN提出来的。
JPA由EJB 3.0软件专家组开发,作为JSR-220实现的一部分。
这是一个概念框架,而不是实现。
Hibernate3.2+、TopLink 10.1.3以及OpenJPA都提供了JPA的实现
JPA包括以下3方面的技术:
- ORM映射元数据 JPA支持XML和JDK5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系
- API 用来操作实体对象,执行CRUD操作,框架在后台替代我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来
- 查询语言 通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合
Hibernate和JPA的关系
一般用JPA时,使用的是 javax.persistence.xxx, 但是有些类,在Hibernate中也有定义,该用谁呢? 网上的说法是:当需要扩展的时候,Hibernate会提供自己的对象,但一般都是继承了JPA的。
EntityManager是干什么的?
先看这篇文章JavaEE – JPA(4):EntityManager相关核心概念
public interface EntityManager {
public void persist(Object entity);
public <T> T merge(T entity);
public void remove(Object entity);
public <T> T find(Class<T> entityClass, Object primaryKey);
// ......
}
我的理解:EntityManager只是定义了持久化Entity的接口,通过这些接口可以保存和读取对象,但是对象的管理是有另外一个叫PersistenceContext的对象来管理的,类似于内存池的管理,多个EM对象可以使用同一个PC对象。
遗留问题: 在SpringBoot中如何操作EntityManager?
SpringBoot中使用JPA+Mysql
- 项目文件配置
我的项目是用Gradle配置的
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile 'mysql:mysql-connector-java'
资源文件配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db
username: root
password: xxxxx
jpa:
show-sql: true使用这个配置,没有指定ddl属性,对于mysql来说,不会做任何事情,不会自动创建表也不会删除表,这其实是项目中常用的模式,先建表,再写代码^_^。
这里还有个小插曲,在src/main目录下建了一个resources目录后,把application.yml放进去,运行起来报错,似乎是没有认到这个资源文件,一开始百思不得其解,后来无意中发现,要在build.gradle上点右键–refresh gradle project,然后eclipse就会resource目录和src/main/java/显示在一起,再运行就正常了。
Entity的定义和使用
一般的Entity类定义如下:
@Entity(name = "t_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
public String name;
我不太喜欢写get,set,直接就把变脸定义为public。
注解 | 作用 |
---|---|
@Entity | 指定表的名字, mysql上的结果来看,表名用大写,小写,混写都没有关系 |
@Id | 指定ID字段,JPA要求每一个实体Entity,必须有且只有一个主键,这个注解没属性需要设置 |
@GeneratedValue | 和@Id配合使用,指定主键的生成策略,详见2, 一般来说就是自增长/序列号这种,JPA默认的是Auto,会自动根据使用的数据库来确定策略,那么mysql就是自增长,oracle就是序列了。 |
@Column | 指定字段名,有name, unique, nullable,insertable,table等属性. 实践中,大多可以不用这个注解来显式指定字段名,JPA会根据默认规则去进行映射: 1. 表字段名为单个单词,比如name, 那么entity属性直接用字段名就好了; 2. 表字段名带下划线,比如target_name, 那么entity属性就是targetName 3. 在Repo中定义的方法中引用属性名的时候,也是按此规则做反向解析。 |
Repository的概述
Repository真的是什么都不干,仅仅定了一个接口
public interface Repository<T, ID extends Serializable> {
}
接下来有几个子类扩展了Repository,实现不同的操作。
类名 | 内容 |
---|---|
CrudRepository | 继承知Repository,定义了基本的CRUD方法,我们也可以扩展,比如findByxxxx |
PagingAndSortingRepository | 继承自CrudRepository,支持排序和分页 |
JpaRepository | 继承自PagingAndSortingRepository,QueryByExampleExecutor 扩展的功能待研究 |
##自定义Repository
有几种方式来扩展Repository
- 直接用字段名来指定查询条件,比如findBy, deleteBy, 可以使用的语法可以参考官方文档4
@NamedQuery, @Query注解,自己写sql语句
我喜欢用@Query, 这种方式不好的地方就是数据字段名改了的话得跟着改sql语句。
select的例子
@Query("select new com.test.Student (h.no, h.name) from t_student h") List<Student> getAllStudent();
有时候我们只是需要返回表中的部分字段,此时需要在entity中定义对应参数的构造方法。
update的例子
@Transactional @Modifying(clearAutomatically = true) @Query(value = "update t_student p set p.name=?2 where p.id=?1", nativeQuery = true) int updateName(long id, String name);
不想写原生SQL怎么办?
对于简单的场景,直接写sql,简单明了,但是从工程角度来说,维护性又差了一点, 比如表的字段名改了就要跟着改sql语句。
JPA提供了几种方式来处理这个问题,这篇文章做了介绍,就个人来说,比较喜欢Querydsl。
QueryDsl
这篇文章,我觉得讲得比较详细spring boot-jpa整合QueryDSL来简化复杂操作
环境的建立 如果用的是Gradle,可以参考我的另外一篇文章来配置环境。
今天在简书上看到一位仁兄恒宇少年写的QueryDsl系列文章,非常地详细,本来我也想整理类似文章,看来用不着了,大家请移步:http://www.jianshu.com/p/99a5ec5c3bd5
Spring Boot JPA - 使用 Querydsl 处理复杂的操作 这篇文章也不错
返回自定义字段
很多时候,我们的查询是从几个关联表里面各自取几个字段作为结果返回,但是这些字段怎么映射为DTO对象返回给用户呢?
- 如果是JPA,那得自己写SQL语句, 在其中new DTO,把结果字段作为new的参数;
- 如果用QueryDsl,有Project方法来帮助完成这个转换。
事务处理
使用起来比较简单,App上加@EnableTransactionManagement,然后在需要的方法上加@Transactional就行了。
详细内容可以参考catoop写的这篇文章-Spring Boot 事务的使用
L1 Cache
普遍的说法是L1 Cache和EntityManage挂在一起的,那就有个问题了,我们的工程中,EM对象只有一个,如果每次查询都把数据cache起来,那且不是数据量很大,而且现在大家都搞分布式开发,同一个工程部署在多个机器上,在其中一台机器上对一个对象做update,另外一台机器上如果前面有该对象的cache,且不是不会知道这个变化,从而一直都是返回旧的对象? 这个疑问,在头脑里面想了很久,一直不太明白, 今天写了点代码来测试了一下,结果发现不会有这种情况,缓存是跟单次请求的执行过程或者说线程挂在一起的,一个外部请求过来,系统必然分配一个线程去执行,这一次执行过程中的结果会cache,结束以后感觉就释放掉了。下一次即使是同样的请求再来,又起一个线程来执行,是不会用到上次的cache结果的,会首先从数据库中读取数据。而且如果你执行过程中,启动另外一个新线程,那这个线程的cache是独立的, 这样有可能会导致问题,反而需要注意:
1. 主线程读取entity,结果会cache;
2. 启动子线程,对该entity做update;
3. 再主线程中再次读取,返回的是cache结果。
当然实际代码中,这种写法应该非常稀罕。
另外这个L1 Cache只有findOne这个方法执行的时候才会自动把结果cache起来,我们自己写的sql语句的结果是不会保存的。(findById,findAll感觉都没有cache)。
本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:
- Markdown和扩展Markdown简洁的语法
- 代码块高亮
- 图片链接和图片上传
- LaTex数学公式
- UML序列图和流程图
- 离线写博客
- 导入导出Markdown文件
- 丰富的快捷键
快捷键
- 加粗
Ctrl + B
- 斜体
Ctrl + I
- 引用
Ctrl + Q
- 插入链接
Ctrl + L
- 插入代码
Ctrl + K
- 插入图片
Ctrl + G
- 提升标题
Ctrl + H
- 有序列表
Ctrl + O
- 无序列表
Ctrl + U
- 横线
Ctrl + R
- 撤销
Ctrl + Z
- 重做
Ctrl + Y
Markdown及扩展
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
定义列表
- Markdown Extra 定义列表语法:
- 项目1
- 项目2
- 定义 A
- 定义 B
- 项目3
- 定义 C
-
定义 D
定义D内容
代码块
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
脚注
生成一个脚注1.
目录
用 [TOC]
来生成目录:
数学公式
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
- 行内公式,数学公式为:
Γ(n)=(n−1)!∀n∈N 。 - 块级公式:
更多LaTex语法请参考 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/“>这儿.
UML 图:
可以渲染序列图:
或者流程图:
离线写博客
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
浏览器兼容
- 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下问题
- 不支持离线功能
- IE9不支持文件导入导出
- IE10不支持拖拽文件导入
- 这里是 脚注 的 内容. ↩