准备一个简单的Student
Student atai = new Student(); atai.setName("阿泰"); atai.setEnroll(LocalDate.now());
要完成entity的持久化,首先需要一个之前提到过的session:
EntityManager em = entityManagerFactory.createEntityManager();
和EntityMangerFactory需要昂贵的开销不同,EntityManger的创建很廉价。而且EntityManger需要追踪entity的状态,不宜共用和长期存活(否则里面的entity会越来越多……),应该像普通对象一样被使用和垃圾回收。
妙的是,可以非常方便的通过EntityManger获得Session对象:
Session session = em.unwrap(Session.class);PS:这是不是说明native NHibernate一直深入人心?
对应数据库表中插入一行数据。
Object id = session.save(atai); System.out.println(id); //养成习惯,不要忘了close() session.close();
会直接将atai保存数据库,并返回数据库生成的id(Object类型,因为会有各种类型的id)
em.persist(atai); System.out.println(atai.getId()); em.close();
试试:能不能用getId()?结果为0
演示:根本没有插入数据!
Hibernate 5.1表面上使用的是jboss-logging,但实际上jboss-logging只是一个facade(门面),具体的实现还是依赖于log4j等日志组件。
引入log4j2,在log4j2.xml配置文件中添加以下logger:
<Logger name="org.hibernate" level="trace">log所有各个level指定级别,级别从低到高,依次是:。级别越低,log的信息越详尽,level="debug"就能看到生成的SQL语句:
为了演示方便,我们把日志都输出到文件中:
<AppenderRef ref="File" />
<File name="File" fileName="c:/log/app.log"> <PatternLayout pattern="%d{yyyy-MMM-dd hh:mm:ss} %level %n -%msg%n" /> </File>fileName指定log文件,PatternLayout指定输出内容/格式。
或者,输出到控制台:
<Console name="Console" target="SYSTEM_OUT">后期同学们熟练之后,如果只想看到生成的SQL语句,可以使用:
<Logger name="org.hibernate.SQL" level="debug">如果要看到查询参数和结果的话,再添加一个:
<Logger name="org.hibernate.type.descriptor.sql" level="trace">
另外,如果确实没有log工具又想看到sql语句的话,可以在persistence.xml中配置
<property name="hibernate.show_sql" value="true" />这样SQL语句可以输出到控制台。还有两个辅助配置(对log一样生效)
<!-- 格式化(换行/缩进)SQL语句 --> <property name="hibernate.format_sql" value="true" /> <!-- 为SQL语句添加注释说明 --> <property name="hibernate.use_sql_comments" value="true"></property>
对比session和entityManager,发现调用:
根据JPA规范要求,persist()要完成数据库操作,需要首先开启事务:
EntityTransaction tran = em.getTransaction(); tran.begin();演示查看log:tran.begin();将transaction由自动开启变成显式开启。
-Preparing to begin transaction via JDBC Connection.setAutoCommit(false)
然后就可以获得生成的id:
try { em.persist(atai); System.out.println(atai.getId());
但此时事务还未执行完成。(演示:在mysql workbench中还无法查看到插入数据)
直到
tran.commit();
被执行,事务才被提交。演示:
-Transaction committed via JDBC Connection.commit()
养成习惯,在catch中回滚事务:
} catch (Exception e) { tran.rollback(); }
当用自增的id做主键时,save()要返回这个id,所以必须进行一次INSERT操作。
但如果id是UUID做主键呢?
我们知道,这时候UUID是由Java/Hibernate生成(演示),这时候还会有即时的INSERT操作么?
断点演示:查看log和workbench
不会进行INSERT操作:
为了后面的演示方便,我们先来学习通过id(主键)从数据库取entity
Student atai = session.get(Student.class, 1);
Student atai = em.find(Student.class, 1);
但这两个方法不会真的查询数据库,而是生成一个只有Id有值的entity的proxy对象(复习:lazy/deferred load)
Student atai = session.load(Student.class, 1);
Student atai = em.getReference(Student.class, 1);
所以注意:不要用通过判断proxy对象是不是null值来判断该id是否存在
if ( atai == null) {如果atai是load()或者getReference()出来的,它就是一个proxy对象,怎么都不会是null值的!
session.delete(atai); em.remove(atai);
关键是:传入的entity怎么来?
标准的做法是从数据库里加载出来的,比如:
Student atai = session.get(Student.class, 1);
但注意:delete()和remove()方法只是在session中的操作,直到session.flush()或者em的transaction.commit(),数据库的DELETE才真正执行。(演示)
你可能会觉得取出entity的SELECT操作是多余的(很多时候确实如此),于是想到了之前学过的laod()/getReference()方法,不会真的SELECT……
#试一试#:居然没用!
Student atai = session.load(Student.class, 1); session.delete(atai); //能不能没有SELECT直接DELETE?为什么呢?因为delete()和remove()是session中对已追踪的entity的操作,但load()和getReference()不会真的生成entity,所以Hibernate delete()的时候在session中找不到相应的entity,只能继续SELECT……
那能不能这样呢?骗一骗Hibernate:
Student atai = new Student(); atai.setId(3); session.delete(atai);不行了哟!(以前记得能行)
Exception in thread "main" java.lang.IllegalArgumentException: Removing a detached instance Student#3
这样new出来的,而不是session加载进来的,当然是没有被session进行状态追踪管理的,但又有一个id,所以被认为是detached(复习)
@猜一猜@:要执行UPDATE,是不是调用这个方法:
Student atai = session.load(Student.class, 2); atai.setAge(17); session.update(atai); //生成UPDATE语句 session.close();这是native Hibernate让很多新人懵逼的一个方法。
演示:根本没有生成UPDATE语句
update()方法实际上是对entity进行attach。
#体会:命名的重要性,为什么就不能命名为attach()呢?!#
演示:new出来的Student,被update()之后再delete()就不会抛异常了:
Student atai = new Student(); atai.setId(3); session.update(atai); session.delete(atai); session.flush();但仔细观察,你会发现出现这里面出现了UPDATE语句!@想一想@:为什么?
转变思想,再次复习session的状态追踪管理和同步
#体会:为什么JPA的EntityManager里:
就是为了统一/清晰这个概念。
EntityManager中的merge()方法,
em.merge(atai);准确来说,对应的是session中的saveOrUpdate()方法:
session.saveOrUpdate(atai);
本质上就是纳入当前session/em管理,根据entity有无id:
有时候我们只想快速的完成数据库的增删改查功能,不需要session的状态追踪管理,行不行?
OK的,使用StatelessSession(仅有native支持,没有对应的JPA实现)
//首先拿到SessionFactory SessionFactory sf = entityManagerFactory.unwrap(SessionFactory.class); //然后才能通过sf获得 StatelessSession stlSession = sf.openStatelessSession();然后就可以调用相应的方法了:
Student atai = new Student(); atai.setId(5); //删改时要设置Id atai.setName("bo");
stlSession.insert(atai); //增 stlSession.delete(atai); //删 stlSession.update(atai); //改 Student atai = (Student)stlSession.get(Student.class, 5); //查
注意:
演示:
这样性能就提上来了嘛!^_^
但是,@想一想@:为什么就不干脆都直接使用这种“高性能”的StatelessSession呢?甚至JPA都不提供这种使用方式呢?
#试一试#:new一个entity用StatelessSession进行update
补充强调:不要忘了stlSession.close()!对StatelessSession而言,它不仅仅是关闭连接。
演示:只有close()了,事务才被提交
Query SET autocommit=1
Hibernate默认是关闭了批处理的。
打开批处理需要在persistence.xml的persistence-unit节点中添加:
<property name="hibernate.jdbc.batch_size" value="5"/>
value值代表批处理中能够包含的最大的语句条数,由开发人员根据实际情况自行调整,官方推荐的是10-50之间。
批处理可以用于增删改查,除了自增int主键表的插入(INSERT)。@想一想@:为什么?
所以Student要改成UUID为主键。
插入20个student:
for (int i = 0; i < 20; i++) { Student student = new Student(); student.setName("s" + i); stlSession.insert(student); }
开启mysql的general_log和Hibernate的log,对比演示:
Building batch [size=30]
Reusing batch statement
Executing batch size: 5
当batch_size配合有状态的Session使用时,情况要复杂一些。
首先,何时向数据库发起请求,由flush()决定:即调用flush(),才会发起SQL请求;
然后,flush()的时候,如果其包含的SQL数量:
所以我们可以将batch_size理解为:一批次提交中最多能包含的SQL语句数量。
但batch_size不能限定session里装多少entity!
如果session里装了太多太多的entity,会给内存造成压力,严重时会触发OutOfMemoryException异常,所以如果数据量大的话,可以用这种方式保证session的大小:
for (int i = 0; i < 20000; i++) { Student student = new Student(); student.setName("s-" + i); session.persist(student); if (i % 50 == 1) { //一个session最多追踪50个student session.flush(); //清空session session.clear(); } } session.flush();
在entity的类名上加一行注解:
@DynamicUpdate生成UPDATE语句的时候,就会只更改有更改的列:
update Student set age=? where id=?
而不是所有列都囊括其中。
这样显然能减少ORM和数据库之间传递的内容。尤其是某些列内容非常大(比如文章的正文),但更改的内容有明显和它无关(比如因为点赞更新点赞数量)的时候。
但还是要@想一想@:居然这个功能这么好,为什么不默认/统一使用dynamic update呢?
因为天上不会掉馅饼,生成这种UPDATE语句,是需要把每个属性值和session中的快照进行比对的!
#体会#:性能的问题,绝对不要简单化!
多快好省!前端后端,线上线下,名师精讲
更多了解 加: