开发者学堂课程【高校精品课-上海交通大学 -互联网应用开发技术:JPA1】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/76/detail/15754
JPA1
内容介绍:
一、概述
二、JPA
三、对象/关系映射是什么?
四、将持久化类映射到表
五、实体
六、Hibernate-配置
一、概述
本节课讲解访问关系型数据库,原来在讲这节课之前都是先讲如何用Java 的数据库连接 JDBC 来访问关系型数据库,里面有两层意思,一层是现在使用的大多数数据库还是关系型数据库,第二层是 Java访问关系型数据库有一个标准即 JDBC 的标准,之前上课都是按照上面提到的讲,但是会出现一些问题。
一个是未来在做大作业或者是在写很多应用时可能不会使用 JDBC 驱动直接做数据库的读写,基本都在用 or 映射。另一方面是反映过于基础显得不太有趣,听起来比较枯燥难以理解。
所以在网站下载时后面会有括号写着参考,这个问题就不再去讲,直接用 OR 映射来讲,直接讲也不会影响访问数据库功能的开发。
不讲还需要有两个地方需要注意,一个是在访问数据库的时候需要驱动,比如说有的用c写,有的用c++写,还可能有其他的语言,用一些特殊的语言写的。
假设认为有 Java 写的,可能会遇到问题就是语言之间可能会有 API 不同的地方,即使是一样的,编程语言不同,调用方式会存在一定的差异,作为应用的开发者,对c实现的数据库,对c++实现的数据库,对 Java 实现的数据库,要各去实现一个针对不同的API编程的功能,才能保证数据库将来可以在不同的数据库之间做迁移,显然是很累的。这时可以规范不管用什么语言实现公共的API,之后再使用数据库的人就对着API编程即可,这样就可以不用去分是c、c++还是 Java,因此在 Java 里面的规范叫做JDBC,提供一系列的API。
驱动的作用是将APP对标准 API 的调用翻译成自己的语言,比如c、c++或者 Java 自己的语言,自己私有的 API 上,这就是驱动所起的作用。驱动像是在屏蔽下面各种各样的数据库之间实现上的差异,可以用统一的编程方式对着统一的一种 API 编程,通过驱动翻译成下面数据库的私有的API,应用就会独立于具体的实现,所以在配数据库的时候就会配驱动类用的是谁。
即便都是c解的 oracle,驱动可以有不同的实现,比如自己实现感觉 oracle 不好想要自己实现是可以的,不管如何设计都要实现标准的API,用户在指定具体使用哪一个驱动的时候就在选择不同的实现,但是编码方式仍然是一样的。
第二个是应用运行在 Tomcat 里面,比如在项目中用开源 MySQL 的数据库,程序在 Tomcat 里面运行,要想访问数据库一定要先建立到数据库的连接,连接就如同打电话线路的连接,无法建立连接就不能对数据库操作。
连接需要指定的有数据库的位置,包括主机 host (用ip表示也可以)、端口,之前讲过端口就是机器上有一系列的程序在跑之后进行编号,每一个程序都有自己的序号,这样就表示到 MySQL 的数据库,但是 MySQL 里面还可能有 DBA、DBB、DBC 等不同的数据库,所以需要制定需要访问哪个数据库,MySQL 访问时还需要用户和密码,所以需要配置用户名和密码,这些都是获取到数据库的完整的信息,全有之后便可以产生到数据库的连接,一旦产生数据库的连接后面的操作是针对JDBC ,公共的 API 可以进行访问,这是两点需要注意的即驱动是什么以及如何连接到数据库,所以为什么要去配数据源。其他的内容是直接用JDBC的API访问就不再去讲,原因是这部分比较难理解,不易于初学者学习,因此就直接使用工具访问,需要转到or映射的工具访问数据库。
在此之前需要明白为什么要写JPA而不是or映射,JPA即Java Persistence API,Persistence是持久化的意思,JPA指的是要用or映射的方式访问关系型数据库,需要了解Java Persistence API如何访问数据以及or映射是什么,举例里面涵盖了一些基本的内容。
二、JPA
JPA本身的定义是要对象或关系映射,之前再讲Java的时候Java全部是面向对象分析与设计也就是一个纯的面向对象编程,即在Java眼中所有的东西都是对象,比如在电子书店的网站上设计Book的数据库,数据库中Book的表就是order的表,在数据库设计叫做Books orders,因为一张表里要存多条记录,通常用复数表示。
当映射到Java程序中,Java程序在操作的时候看到的会是一个个的Book对象以及 order 对象,Book 表格是一个二维的表格,映射成Java的对象、Book 类型的对象和 order 类型的对象就是or映射,将关系型数据库的数据映射成对象,反过来也是需要将对象映射成关系型数据库。在数据库管理系统中比如 MySQL,看到的Books表格是二维的,一行是一个记录,都有字段比如书名、作者、发行商等,在Tomcat里写的 APP 里面希望看到的是一个个Book对象,对象会有title、Author、publish 等等一系列的属性,对着title就会有get title获取它以及 set title 方法设置 title,其他的属性也一样,这便是book表格读到 Tomcat 里面变成一个个的 book 对象,而 book 对象针对 set title 希望将更新过的书的内容刷新到数据库中,需要做双向操作,可以想象如果自己去做工作量会很大,所以需要使用框架工具的帮助进行,JPA 就是 Java 定义的在进行双向操作是可以依赖的API,也就是本节课需要讲的比如 hibernate 或者是 spring JPA、ibutis、mybutis 都是相同的工具,都实现了标准的JPA的接口,编码对着接口编程,工具就会将对对象的操作翻译成对数据库的操作,对数据库的读组装成对象供上层使用。以上是JPA的含义,需要中间的框架做支撑,可以发现操作方式很简单看到的是一个个book的对象或者是 order 类型的对象,对对象的操作更贴合于对Java编程的语言,所以追求使用 or 映射工具简化对数据库的操作,直接通过JDBC 访问数据库没有讲原因是要求原生的 JDBC 访问数据库时返回非常复杂的对象,需要自己便利表格中的每一行取每一列的值才可以知道数据库里面存了什么数据。
所以现在以 hibernate 为主来讲,讲完之后因为基本道理是一致的,所以讲解 Spring JPA ,因为未来再做大作业时会有几种不同的基础路线,比如全站都是 spring 用spring JPA或者是 spring MVC,用 spring 依赖柱等等,但是也可能使用 spring +hibernate,就是在数据库访问用 hibernate,也可能是 spring+struts+hibernate,所以无论是在JPA还是在MVC框架下都会讲两种不同的框架,未来可以自由组合,已经有两个全站的例子可以进行参考。
三、对象/关系映射是什么
首先需要了解什么是持久性,持久性对应的另外一个词是瞬时性。
简单来说,持久性是应用的数据可以在应用进程之外存活,应用是在Tomcat 里运行的,在上节课讲过整个 Tomcat 是在一个JVM里运行的,也就是如果将 Tomcat 关掉应用也就会停掉,但应用停掉之后书的信息、订单的信息仍然保留不会消失,因此要存到数据库中,存在数据库和存在应用中的存货周期不同,在Java虚拟机中比如是Spring book 的项目将 order 关掉,Tomcat 就没有了应用就没了,但是数据还在,因为已经存到了数据库中,数据库将数据存到硬盘上,而硬盘是一个持久性介质,即使不通电数据也在,和内存不同不通电数据会马上清空。
直白的说持久是要访问在硬盘上存储的数据库,唯一不能直接对等的是强调关系型数据库,如果不是关系型数据库存在硬盘也叫作持久化存储。
Persistence 对应的是 transient 瞬时,瞬时是比如在JVM里面运行的 Http session 以及创建的购物车对象等等,只要应用一停,对象将全都消失,因为全在内存中,所以这一部分数据叫做瞬时性数据,与持久性数据的访问是不一样的,持久化数据是在硬盘上,是在 Tomcat 之外在关系型数据库或者是非关系型数据库当中存储的数据,Tomcat 需要访问 DB 才可以得到,而瞬时性数据在 Tomcat 内存中,二者是不一样的。
持久化数据就是要存在硬盘上独立于 Tomcat 存在的东西,主流的数据存在关系型数据库中,所以先从关系型数据库入手,就和讲数据库原理的时候先从关系型数据库入手是一样的,无论使用 MySQL 还是 Or recall 数据都存在关系型数据库中。
JPA 就是用某种对象关系映射工具比如hibernate 操作持久化的数据像操作对象一样操作在关系型数据库中的数据。
四、将持久化类映射到表
所以 or 映射指的是用对象模型表示在关系型数据库中存储的关系模型,以 hibernate 为例,它是一个非常主流的工具。在关系型数据库中,数据是组织在若干张表中,表和表之间是有关联的用外界关联。
比如有 Books 表、orders 表格表示订单,订单肯定要有编号,假设买了某一本书,orders 需要引用 books 就是外键,指向了另一个表的主键,比如买的是编号为2的书,books 表格中有一本书id是2进行关联。表中会有很多记录,首先讲一个概念,关系型数据库属一种结构化数据,即所有的行都具有列,不会出现有一行有三列,另外一行有四列,而且三列和四列表达的内容不一样,不会出现这种情况,一种结构化非常好的数据一列是名字则都表示名字,另外一列是学生的id,则都是学生的id,每一行名都有学生名和ID。
数据是在表格中维护的,是以记录的方式也就是一条一条的方式存储的,这是在关系型数据库里面的数据,读取到 Tomcat 应用服务器中,在程序中进行操作的时候基本的原理是每一行为一个对象,比如说Thomas Stuart 将来会被抽象为 student 类型的对象,Thomas Stuart 是对象的属性,student_ID 就是 student 类型的属性,一一对应,基本原则是一个类表示一张表,类里面具体化的实例表示一条记录,对象里面的域就是数据成员域对应的是列,只要做这样的映射,就可以建立对象到关系映射。
但是感觉很简单,一一对应的关系也已经摆出,还需要使用hibernate 工具的原因,关系和对象在某些情况下没有办法很好的匹配,里面讲解了几个例子再后面会一一看到,首先去讲相关的概念不理解没关系,后面讲具体实例的时候还会遇到。第一个问题是粒度,比如有一张表示人的表格,每一行都是一个人,一个人可能会有多个地址比如工作地址、住所、父母的地址等紧急联系人地址都需要存储,如何存储 person 表格和 person-addess 是1对多的关系,一对多关系的操作比如 person 表格中有一个人ID是1,person-addess里面记录了三个地址,第二列会显示三个地址关联的是谁,1、2关联的是1,3关联的是2,即 person 表格中有两个人,1、2两个地址pid是外键在关联 person 的主键,1、2两个地址是和ID为1的人关联,分别为工作地址和住所,3地址是和2关联,在两张表格里存储是因为并不知道一个人到底有多少地址,如果房子较多放到一张表里无法设计,当然还会涉及如何节约存储空间,所以合理的设计方案就是拆成两张表,但是在后面会看到,做or映射的时候按照刚才讲的逻辑,一个类对应一张表,一个对象对应一个记录,成员属性对应列,在映射对象时就要有person的类以及person-address 的类,二者之间具有引用关系,但是会发现person-address 实际上就是一个地址抽象成单独的类,没有什么意义,所以在映射的时候是没有 person-address 类的,person 和集合类型的属性放的是一组地址,由此可以看出,数据库中两张表映射成了编程语言1个类,这便是粒度,之前讲的映射关系是一个粗略的描述,但是真正操作时是有些区别的,要面向对象的方式处理数据,因此在真正映射的时候就变为了一类,当然也可以有两个类不是必然的,映射成两个类也可以但实际没有必要。第二个问题是有关继承会留在下节课去讲,因为它比较复杂会单独讲很多内容。第三个是标识,在person表中是靠主键区分每条记录,所以标识就是主键,但是当读取到内存中变成类时,可能发现PK是属性比如是ink类型以及spring 类型的 first name 、last name,现在的问题是当从类里面实例化出一个对象表示这个人时,比如主键是1,PK是1还有其他的属性等,1在数据库中唯一的标识记录,其他记录不能为1,只要主键为1唯一的纪录介绍性,一旦变为对象,有可能在创建一个对象在多线程中可能会出现,仍然抽象表示主键为1的人是完全有可能的,因为对象不是靠内容比较是否相等是靠引用也就是地址。如果创建了两个对象在内存中是不同的地址,尽管二者抽象的表示的同一条记录,Java语言也会认为他们就是两个不同的对象,这种关系在后面编写复杂程序的时候会出现,现在只需要知道数据库中的数据是靠主键区分的,而对象是靠引用区分的,所以二者可能不匹配造成在内存中有两个对象表示的是同一条记录,这个问题在目前编写的程序中是不会出现的,但是必须提醒因为在讲到更复杂应用时会出现这个问题。第四个是关联,person表格在数据库当中是一对n的,所以一定是n有外键关联1中的主键,方向永远只能是这样,但是在对象中除外,可以将关系反过来甚至可以变成双向。第五个是数据的导航,如果有上面的关联,要从 person 中获取所有的地址应该怎样。关联和数据导航的例子讲到后面可以看到,不太明白可以因为后面会去讲代码,需要明白的是不能简单地认为一个类就是一张表,一个对象就是一条记录,一个域就是一个列,很机械的映射,实际不是这样的,只是大概是这种方式。
五、实体
刚才所讲的映射成一个对象,这种对象就叫做实体。如果打开后台的工程可以看到有一个包叫做 Entities 包里面放的都是or映射的类,用spring的JPA映射的类,这个类其实没什么特别的就是每一个实例化的对象叫一个实体,一个类就表示表格中的一张表,每个实例对应的是表里面的一行,大概是按照此思路来进行映射,所有的操作都是在对实体进行操作,而实体的操作就是对里面的状态也就是成员域的操作,这些操作会反映到数据库里面,这时就可以知道为什么叫做实体。
实体中的域和属性会映射进去,实体应该如何写,首先实体类有一个名字,其次对于每一个属性即里面的域都会有一个 get 方法和set方法,统称这一类方法为 getter 获取器和设置器 setter 方法,也就是一个实体类方法很容易写,即有一些属性,每个属性都会有get和set,类就写完了,但是类只这样写是不能的,需要用hibernate框架进行托管,告诉 hibernate 如何映射到数据库中,举例在所给的工程中,可以看到在 resource 目录里压进去一些sql的脚本,脚本就是数据库的例子使用数据库的样子,导入到数据库中比如使用MySQL 的 Workbench 进去之后会有数据的导入的动作,导入要选中sql的语句,要去选 sql 的文件,选好之后点击导入就会导进去,机器上会有数据库的内容,没有导入表中的数据的结构,所以进去之后可以添加自己想要的东西。
六、Hibernate-配置
完整的数据库在后面可以看到里面包含了4张表,一个是事件,可以理解为办一些活动,首先有id,id在数据库的定义中是自增的,也就是在创建一条记录的时候没有设置值,值数据库会自动增加递增
由上表可以看出设计的表全部是自增的id int NOT NULL AUTO_INCREMENT,所有的表全部是自增,即主键不需要做设置,只需帮数据库插入一条数据写好日期和title之后点数据存储会自动生成ID。
Person会有年龄、first name和last name,在后面很多的例子中会用 person 表格操作,因此后面有几节课的例子工程里面都是在对 person 进行操作。如果有一个活动应该有n个人参加,一个人可以参加n个活动,二者是多对多的关系,在关系型数据库中多对多的关系无法直接表示,一定要靠中间表,中间表将多对多的关系拆成两个一对多的关系。比如 events 中主键是1、2、3,3个事件,person 中有5个人,中间表就会记录编号为1的人会参加第一个活动,编号为1的人还会参加第二个活动,编号为2的人会参加第3个活动以及第1个活动以此类推。关联表是EVENTS_ID和PERSON_ID合在一起各引用另外两个表中的一个主键,EVENTS_ID和PERSON_ID是组合组建并且是外键。
PERSON_EMAIL是1个人有n个email address,在PERSON_EMAIL中是让人的ID作为外键,关联表中的人表示此人有一封邮件还有另外的邮件,所有有两个字段,一个是引用到底是谁还有一个是真正的邮箱地址,这便是数据库中的四张表,在映射的时候就会发现两个类,关联类不在,关联类体现在两张表的关系上,PERSON_EMAIL类也没有,被包含在person中,这也说明了匹配不是一一对应的原因。
接下来学习event是如何映射的,首先如果使用hibernate进行处理要先写类,之后再解释 indcation,就像 event 在命名上因为数据库中的一张表要存储很多的记录,通常表示一个复数的概念,在表示类的时候,实例化之后是一个一个的对象,每个对象都是一个类型的,所以event是单数的概念。
EVENTS 中有三个属性分别是EVENTA_ID、EVENTS_DATE和TITLE,显然在定义类的时候,写EVENTS TITLE 是一种累赘,想要简单一点一次叫做id 、title,date和之前相同,必须有一个无参数的构造器,Hibernate强制约定必须提供,还可以提供自定义的构造器,参数自己写。
因为Long id是自增的主键,因此不需要设置,只需要设置 title 和 date 是什么就可以,将来写到数据库时主键会自动生成,需要有 get、set方法,因此可以看到有 get 和 set 的三种属性和方法,从 Java 代码的角度看就结束了。虽然是一个实体类,但是写起来有若干属性每个属性有get、set 方法,至少有一个无参数构造器,是否需要第二个构造器自己决定。
关键是类写好之后不能映射到数据库需要由Hibernate帮助映射,因此需要告诉Hibernate需要怎样映射。
第一步需要说这是一个实体,这样Hibernate才能托管,第二表格实际上叫events所以需要通过table标签描述events表格是和哪个表进行关联映射,这样就不需要类的名字和表的名字一致,而且本来来就不应该一致,一个表示的是单数概念,一个表示的是复数概念。
在数据库中ID的名字叫做events_ID,先要将events_ID还对象中的ID关联,首先要告知是ID,在数据库中写过*是ID即主键,所以在上面写ID时所注射的get方法,注意@id @GeneratedValue(generator=“increment”)@GenericGenerator(name=“increment”,strategy=“increament”)是在注解get方法,注解get方法里面的属性,解析是将get去掉第一个字母变小写,所以是小写的id,注解的get方法操作的属性是主键,值如何产生,用increment生成器,用的是数据库中的自动生成策略,@id @GeneratedValue(generator=“increment”)@GenericGenerator(name=“increment”,strategy=“increament”)只要是靠主键的自动生成的帮助下完成的就不需要进行其他的修改,将直接搬下来就可以。
接下来看title在数据库中就叫做 title 没有什么区别,但是date叫做events_date所以需要关联,title 的 get 方法前面没有其他的注解,但是在date前面有两个注解,第一个是需要了解映射的是表中的哪一列称为EVENT_DATE,没有必要让类的属性和字段的名字完全一致,因为是两个系统里面的东西,很可能命名不一致,允许不一样直接通过get方式建立关联。
第二更为重要,可以看到 Java 的 date 类型与数据库的Date类型两个不太一样所以需要加上 Temporal,TemporalType 的时性,会约束Temporal时性的类型是什么,这里定义的是时间戳有日期和时间。
到此为止,类映射完成,首先可以看到Entity类不复杂有一些属性,对属性有 get、set 和无参数的构造器,除此之外没有其他的任何的约定,所以 Entity 类本身写法不是很复杂。
第二关键的是靠 annotation 对类的操作进行托管,通过 annotation 可以看到 events 和 event 即类名和表名之间没有必要完全一致,field name 没有必要和列名完全一致。
由此可以发现类名不需要和表名完全一致,属性的名字不需要和列完全一致,解耦解的很开随便起类名,随便起字段名,可以通过 Temporal 和column 的 annotation 关联起来,意义在于写了 events 类,用在 APPA 里面映射 events 的表格,明天应用到 APPB 里面映射activity的表,在映射过程中只要去改 Table和 column两个annotation即可,其他的代码不需要改变,尽管 events 和 activity 两个表结构相差比较大但仍然可以用event表做映射,这就是or映射的好处,中间有映射过渡之后会发现代码和底层没有必要混合,因此代码可复用的程度会提高。