开发者学堂课程【高校精品课-上海交通大学 -互联网应用开发技术:JPA 7】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/76/detail/15760
JPA7
内容介绍
一、连接数据库
二、创建对象
三、load加载数据
四、HQL语句
五、创建方法
六、写入数据库
七、session重要性
八、常见问题
一、连接数据库
连接数据库要求要写一个hibernate配置文件,配置文件就是在连数据库,描述连接数据库的信息,包括用什么驱动类,数据库的位置,哪一个库,用户名和密码。
在Spring的例子里,是在叫做Application.Properties的配置文件里写。也是在写连着哪一个库,用户名密码,驱动类。
有这些文件就可以跑,如果给的例子跑不起来,那有可能是因为把例子中的配置文件放在了webAPP里的Resource中,没有像刚才那样设目录是Resource。
假设Resource之后,目录底下有一个黄色的文件清单。将来就会打到清单里。也可以把它放在JAVA和webAPP评级的目录里。
不管放到哪,只要设了Resource,就能打进去。这样数据库就没有问题了。
如果是Spring里面的APP,也是在Resource子目录Application.Properties里面。
只要把目录标记成resource,未来就会打入进去。所有连接数据库的信息都在这里面。注意自己数据库用的名字不一样,用户名密码要改。
为了能顺利的跑这个例子,resource目录里面有脚本,就是Circle的脚本。在数据库里执行脚本就可以直接建表。在这个结构上代码就能跑。
如果不能跑,不是路径问题,就是数据库可能有差异,如果报错就根据报的错看看差异在哪里。
在开发工具里,有些东西可以去掉。这个表映射到Person上会设置一条红线,显示没有设置resource。只要代码跑就不会错,只是开发工具不让映射。
在vue里有个two WINDOWS,然后选Database,选完之后就是左边这个。添加Data source,设置my circle,输入用户名密码还有要连的数据库。报错,没有赋数据。
这边已经配过两个数据,选一个赋好之后这条红线就没有了,其实代码运行中不会出错。但是开发工具不知道跟哪一个数据库进行运设。
二、创建对象
上述功能看一下教程都有,在hibernate或GPA里会发现对象里的状态,之前全部假设一个对象,只要创建出来,就要靠hibernate或SpringGPA写入到数据库里。
有没有对象不需要hibernate或SprayGPA来托管。一方面可能只是个顺序,用关键字创建的对象不希望现在就写到数据库里。如果希望托管,对象就处于Persistent状态,会真正代表数据库的值。
detached,就是对象确实是数据库里的,但是不希望session对它进行管理。这涉及到了hibernate的实现。
举个例子,在React包括vue里,要以Rays.setstate这种方式来修改状态,页面才会重绘。如果直接写,页面不会重绘。因为给了React和vue的框架一个机会,拦截请求之后,就知道要做什么。
在方法里面就做这个事的话,set state就可以写a等于负的值,去掉render,就可以做这样的工作。但是如果直接写就不知道该怎么处理。所以要调方法来实现。
在讲面向对象的时候提到过一点,属性应该是provide at a,应该暴露一个get a来返回值。当然还有一个set a的方法设置值。在Get a里面,不是返回来,可以返回一个a+1,取决于业务逻辑,还有判断当前执行这个代码的概率,有没有权限。如果没有权限就不可以。
例如,有一个记录,a被访问了多少次。确实返回a了,但是这之前要把一个count值做加价。同样的道理,set也是这样。
只要让a变量变成Provide,就必须通过指定的get和set方法来操作,那就有机会做额外的逻辑。跟这道理类似。React走了,或者vue走了,必须去调用set state才有可能重绘。
如果操作Hibernate,直接new英文Person或直接用一个Person对象行不通。Hibernate不知道P这个对象需要托管。Hibernate就会提到必须在一个session里面begin transaction,就会知道从此处往下传播要靠session托管,才能知道P是在事务的框架里。
所以,当这个事务被提交时,才会把它存进去。如果孤零零写一行,这个person就处于瞬时状态,没有和任何的Hibernate和Session关联,处于纯内存里的对象,游离派。
需要在Session里要求把p存储进去,通过Session存储流,就受到了Hibernate的管理,才能真正把对象写入数据库里。
如果Session关闭,不代表这个对象没有了。一旦Hibernate关闭,p对象就跟Session没有关系了,又和hibernate处于游离态。这时可以看到p确实是数据库里的对象,确实被持久化过,但是现在Session被关闭,就没有了。没有相关联的Session,只是这个对象本身,处于detached状态。
写代码只要记住,想干什么事,开一个Session,在Session里面创建Transaction,把对象给它就可以了。
细节纯粹与性能相关,举个例子,在这里创建一个domestic cat,设置颜色,性别,名字,把他save,所以主键主要是靠数据库递增的。save完之后就会把主键带回来,就可以得到这个主键,这是数据库自动设的。这个对象在Session去存之前只在内存里,在数据库里是没有的。这时就属于瞬时状态。当要求它save写入到数据库之后,才是持久的状态。
把一个对象写入到数据库中一个是用save的方法,另一个是用Persist的方法,都可以把这个对象写入到数据库里。已经写进去自然可以加载出来,加载的时候怎么加载。
三、load加载数据
加载用的大多是load。load有两个参数,一个是要加载哪个类,根据这类映射的表可以知道要加载哪一个表里的数据。后面是主键,已经拿到了前面生成的主键,加载主键,代表的那一条路即可以加载进来组装成一个cat对象。
四、HQL语句
除了load外,还有一系列find的方法。
find的方法要去写hql语句。hql语句非常复杂,要去查手册看应该怎么写,常用的有From person,把它取出来,Person后面加上p 再加上where。再复杂的需要翻手册。
Find的方法可以加载对象。Load特指可以加载一个对象。在关系型数据库中,主键是唯一的标识,所以加载进来的是一个,除了按主键加载,其他无论怎么写返回值都是一个list。
只要不按主键查找,从设计原理来说,都不保证能找到唯一的一条记录,返回的都是集合类。除非按主键查找,或者在按主键查找返回一个list之后过滤一下,拿到其中的一个对象。
在所有的猫的对象里找猫的日期小于设置进来的参数,零表示的是在这条语句里,用问号或冒号后面跟着表示的参数,参数的位置从零开始编号,有两个编号分别是零一。把零参数设置成找出生日期,早于某一个日期的猫。再看底下,这句话就做了一个join,像这种语法很少,意思是找这个猫,就是要找猫的mother。要先找猫的名字等于这个参数,然后去找猫的Mother。
自关联表里面cat里面还有一个字段,就是Cat里面有只猫是其他猫的mother,做了一个自连接的操作。又要去找给定名字的这只猫,它的Mother所对应的猫是哪个。
在cat这张表里面,这是ID,有一个字段是Mother,当然还有一个字段是Name。找这只猫Tom,序号是一,mother是三。取三对应的,到那张表里面去找到这条记录,把记录拿出来就是Tom的mother。
除了按主键查找和按照上述方法查找,返回的都是集合类。或是过滤,只拿其中第一个或是唯一结果。
要执行更复杂的Query,Query的本质是在数据库里面拿若干条记录出来,在Tomcat的内存里面划出来一堆对象,找哪一个对应,它也是一种加载数据,只是加载一堆数据。加载回来这一堆迭代即可。
Java里所有的list都有一个迭代器方法,都会返回迭代器。迭代器里面不断迭代,还有next,如果返回的触角还有内容,即可迭代。迭代next是返回下一条,若有下一条时就返回下一条。返回的时候就是一只猫,然后取出来迭代,这是通过迭代器来迭代。
五、创建方法
这里面有一条语句,它可以像Circle语句一样,也可以做Group by。当然还有其他LIKE,in之类都可以。参数绑定,就是要么用冒号表示参数名,规定name。要么用问号,按出现的顺序从零开始引用绑定。
一次性创建一个列表,这个列表里会包含若干个东西,在这里绑定参数,绑的是列表,刚才的in就是猫的名字在这两个名字中间,在吃这两个名字,就把这个猫拿出来,传递一个集合类的参数。
分页,当数据量太大时,前后端交互时不可能把所有数据推到前端,前端在访问的时候手持下滑,或者是点下一页的时候,可以点下一页的内容出来,在执行的时候不是一次性取出里面所有的内容,而是取前二十条,拿出20条之后,如果还有需要就再去拿20条。如果支持滚动的方式,就要在查询站执行scroll,让它返回一个可滚动的结果。每一次取出第一页指定数量的内容出来,如此设计page size。
查询,现在是criteria,针对Cat累设计一个标准。标准是Restriction,EQ表示相等。还有别的操作,这种方法里都是两个参数。前面是属性,后面是值。例如一个标准,颜色必须是黑色。然后设计了一下,最多只找前十条出来。因为Criteria是在cat结构上操作的,按照标准过滤,可以找出黑颜色猫的前十条出来。
调用本地化的Circle语句, 这里就是native的Circle语句,不过尽量避免这种情况,还是用Hq l来满足查询的要求。
对象讲的是可以把它存进去,然后查一查。一旦load出来,如果想修改它,再把它写回去,就要首先load进来,用set方法,每一个实体对象的属性都会有set方法,例如set name,那就会把原来的Name改掉,变成新的name被写入数据库。
六、写入数据库
那什么时候写进去,一种显示的要求flush就写进去了。还有一种就是用事务Transaction,在这个地方把事务提交掉。
创建一个CAT类型对象,再创建一个新的cat,从数据库里load进来一个cat类型的对象,如果现在写,会发现在数据库里找不到。在Tomcat里创建一个对象,然后写到数据库里。
Hibernate不是要写就写,异地操作时,必须获取到数据库的连接,connection对象。这个对象很紧俏,资源非常少,连接在Tomcat里,有一个连接池,是一次性创建好数据库,然后放在这里管理,当代码里出现了需要数据库连接才能执行的操作的时候会使用连接池。在连接池里的连接里挑一个空闲的用,用完之后马上收回。
例如放十个计算器,一百多个人要用是,谁用谁拿,用完马上放回,这样十个计算器差不多可以为一百多个人提供服务。Tomcat就是走的这条路,就会尽量节约使用连接,Hibernate的想法就是这样。要save就马上save,比较频繁,这样不好。
Tomcat里面维护几个列表,除了Hibernate,这些东西都是类似的。维护的一个是新对象,新对象放到这里之后有一个引用,让它知道这是个新创建对象,需要Update的对象,还有需要删除的对象。
三个列表,如果要做save或者Delete,或者Hibernate,都是在这里记住对象的引用。但没有马上动作,而是在满足一定条件之后,在里面可能已经累积有若干个要更新的,把这些操作合到一起,变成一个批处理的一个语句,通过Circle连接一次性发过来,然后数据库执行操作。
4、原因,如果代码让save或Update,找个连接去动作,连接就不够用,就需要等够一批再来处。
就像提交作业,来一份改一份,那就要二十四小时等着提交作业,如果说一个礼拜交一次,到了这一个礼拜一次性改完,那平时还可以做其他的事,而且一次性全部发过来,只用一次连接就可以,效率比较高。
5、Hibernate必须要知道哪些对象是新创建的,才能在这种机制之下把这些对象按照要求插入,更新或删除。如果要这么做,不在一个Session里面处理,无法把对象的预测放进去。
七、Session 重要性
1、创建一个Potentialmate,然后load一个newcat的段。现在把参数mate存一下,Session就关掉了,但是对象已经注册到new的列表里,Hibernate将来在做处理的时候可以把它写入到数据库里。
2、Session没了,不代表Potential mate没有了,所以把把CAT的pale 设置成potential mate。cat相当于做了一次修改,也没有和任何一个Session关联。只要随便找一个Session,update cat就可以把cat的修改写到数据库里。对象还存在,只要想让它被Hibernate托管,一定要在Session里执行,至于session是不是同一个无所谓。
就像学C++的时候,硬盘上有一个文件,要创建Stream进行读写,这个Stream关掉后文件还在,下次可以再开另外一个Stream跟文件关联进行操作。实体归实体,Session归session,但是如果希望实体被Hibernate接管写入到数据库里,必须在Session里去做。
3、其中有三种状态,一种对象刚创建出来,数据库里没有对应的值,就是Trance,第二种是已经有这个状态,通过save写入数据库,已经有持久化,关闭之后,就开始Facial,就都变成了Detach状态,都不关联了。在一个Session里面又把对象Update,或者什么都不做,只做merge,就表示,又要在这个Second session里关联cat,对他俩的操作将来都会通过Session反应到数据库里。
4、在Hibernate世界里面,一定要在Session上进行save,Update,Merge。直接创建出一个对象,或者load进来以后直接把这个cat改掉都只发生在Tomcat内存里,不会写入数据库里。
要想把这个东西写入数据库里,必须是在一个Session里面。加载了一个类,然后又创建了一个对象,然后做了刚才的动作。
但是代码执行到后面可能又有一个疑问,这个对象到底应该是重新创建还是更新,可以用save or update自动检测决定插入还是更新,删除最简单,但是必须经过Session去做处理,脱离开session什么都做不了。
八、常见问题
1、在这一步真的删掉了吗?
没有,其实只是注册到了三个列表里,真正的什么时候去做这件事,是有触发条件的。
2、连接池概念。
数据库的连接不会开很大,数据库这一段会开多少个连接,CPU的核有多少,就大概有多少个连接,双核就开两个连接。
连接既然这么贵,Tom cat也不能拿很多,所以会在启动的时候,根据配的数据源,在Hibernate的配置文件里,或者在Application里,数据的位置在哪里,就会根据这个创建连接一直持有。所以实际上就是在使用Tom cat连接池里的连接。
有一点,不是要删马上就能删,在这里注册有三个列表,在满足一定条件的时候做这个事。
3、Flush
本质上来说,数据库的数据在这里,如果不是马上执行,那就意味着内存的状态和数据库状态不太一致,希望把cat删掉,但实际上它还在数据库里,如果有其他的代码访问,就可以看到这个Cat,所以如果很希望做的操作马上就执行,那就要触发数据库当中做持久化的动作。这个动作叫Flush,还是比较形象的,就是把一堆东西冲水一样冲过去,把它持久化到数据库里。
4、Refresh
有这样几个条件,就可以来执行刚才说的做重复的动作。第一开始显示掉Flush,可以看到刚才在这个代码里面,目的是马上把数据库和内存里的数据做一次同步,也就是说load对象后,如果这时有人把代码改掉了,Cat的值和现在load不一样就Flush一下,把当前的状态同步。注意它的方向,这个方向叫refresh。数据库里具体的数据,表里的数据刷新对象的状态,就叫refresh。
5、Flush触发条件
(1)Flush的动作第一个是直接调出来,显示去掉可以做这个。
(2)所有事情都要在事务里面,事务被提交的时候会去做Flush。
(3)另外还有一个容易被忽略的,就是在执行查询之前会去做Flush,会防止一个现象,就是在这边插入了一个数据,然后马上到数据库里查询,数据还没写进去的话,查不到,就会觉得很奇怪,调试的时候会觉得很有挫败感。
6、子查询
在插入一个数据之后,如果不想直接调flush,就可以直接在这上面调一个From cat,执行子查询。在调From cat的时候,如果有人创建的对象还没有写入数据库,去查询就查不到,所以在查询之前先做flush。
但是不是所有的Query都一定能触发,所以用这种方法不是特别保险。一再强调一定要开事务,在事务里面进行,在Tomcat和数据库里面使其状态一致。flush对应的当然就是Refresh。
7、对象重复
在Tom cat里,如果两个对象地址不一样,那一定是两个不同的对象。但是在数据库里面是靠主键来区分,如果一条记录被读到tomcat里面的时创建了两个对象,这两个对象就是重复的。
如果通过session factory去创建session,创建加载了一个对象,在另外一个session factory里面再开一个对象,然后再去加载cat的话,就有可能会出现两个cat是对同一条记录的抽象的情况,为了解决这个问题,创建第二个时要去调相应的方法。flush之后就会执行一系列的语句,最终在数据库里执行是Circle语句。Hibernate把这三张表遍历,产生相应的Circle语句去执行。
8、执行顺序。那就包括了更新和删除,上面有包含元素的删除,整个的排斥等。
刚才的三种情况都执行了一下。但是flush时要想一想操作会不会出什么问题,在Transaction里面去执行。
取一个事务,当Commit的时候,就会去flush,甚至可以人为设置在Session上,flush是当事务提交的时候自动去做。
在这中间执行的所有操作一直到这个地方才会写入数据库里,所以新创先load猫,然后改名字,再去执行查询的时候,也许会看到旧数据,而不是新的数据,原因是没有occurs。
这个问题的本质就是不是在直接对数据库进行操作,是在对Tomcat里的对象直接操作,这些对象再被Hibernate写入到数据库里。数据真正在这里,所以两者之间有可能存在不同步的问题。
记住所有的东西,第一要在Session里执行,第二最好是在Session里面开一个Transaction去执行。如果希望操作马上更新到数据库里,要么人为flush,要么就把数提交掉。