开始
好记心不如烂笔头,很多技术如果长时间容易遗忘,故特用此文记录下一些Hibernate的使用技巧。希望将来的自己和所有看到这篇文章的童鞋都能从中获益。
Hibernate的使用场景。
在一些对性能要求不是很高,但对开发速度有要求的小型项目中,Hibernate能显著降低代码量和提高增删改业务的开发效率。
- 对象映射关系-基础配置。
拿部门,员工这种典型一对多关系来做例子。
首先是公共父类,可以定义一些所有表都需要用到的公共字段,还有主键生成规则。
ID:默认所有表的逻辑主键,需要用@GeneratedValue定义值的生成策略。generator里面指定的是一个生成器Key,在子类中可以通过这个Key关联,然后再定义每个表的具体生成策略。
1 @MappedSuperclass 2 @AccessType(Type.FIELD) 3 public class PublicPO extends PublicObj { 4 5 /** id 逻辑主键 */ 6 @Id 7 @GeneratedValue(strategy = GenerationType.AUTO, generator = "caGenerator") 8 @Column(name = "id") 9 private Long id; 10 11 /** 创建人 */ 12 @Column(name = "CREATE_BY", updatable = false) 13 private String createBy; 14 15 /** 创建时间 */ 16 @Column(name = "CREATE_TIME", updatable = false) 17 private Date createTime; 18 19 /** 更新人 */ 20 @Column(name = "UPDATE_BY") 21 private String updateBy; 22 23 /** 更新时间 */ 24 @Column(name = "UPDATE_TIME") 25 private Date updateTime; 26 27 public Long getId() { 28 return id; 29 } 30 31 public void setId(Long id) { 32 this.id = id; 33 } 34 35 public String getCreateBy() { 36 return createBy; 37 } 38 39 public void setCreateBy(String createBy) { 40 this.createBy = createBy; 41 } 42 43 public String getUpdateBy() { 44 return updateBy; 45 } 46 47 public void setUpdateBy(String updateBy) { 48 this.updateBy = updateBy; 49 } 50 51 public Date getCreateTime() { 52 return createTime; 53 } 54 55 public void setCreateTime(Date createTime) { 56 this.createTime = createTime; 57 } 58 59 public Date getUpdateTime() { 60 return updateTime; 61 } 62 63 public void setUpdateTime(Date updateTime) { 64 this.updateTime = updateTime; 65 } 66 67 }
部门类(Department)继承公共父类,同时定义的主键生成策略为序列,指定序列名。
部门和员工为一对多关系,所以部门中有一个List<Employee>属性,mappedBy = "department" 意思是让employee来维护关联关系;orphanRemoval = true 意思是自动删除不再和部门关联的员工。
由于部门之间还有父子关系,所以部门中还会有一个Department类型的属性,为多对一自连接关系。代码如下:
1 @Entity 2 @Table(name = "s_department") 3 @SequenceGenerator(name = "caGenerator", sequenceName = "seq_department", allocationSize = 1) 4 public class Department extends PublicPO { 5 6 @Column(name = "NAME") 7 private String name; 8 9 @Column(name = "TYPE") 10 private String type; 11 12 @Column(name = "STRATEGY") 13 private String strategy; 14 15 @Column(name = "status") 16 private Integer status; 17 18 @ManyToOne(fetch = FetchType.LAZY) 19 @JoinColumn(name = "PARENT_ID") 20 private Department parent; 21 22 @OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY, mappedBy = "department", orphanRemoval = true) 23 private List<Employee> employeeList; 24 25 public String getName() { 26 return name; 27 } 28 29 public void setName(String name) { 30 this.name = name; 31 } 32 33 public String getType() { 34 return type; 35 } 36 37 public void setType(String type) { 38 this.type = type; 39 } 40 41 public String getStrategy() { 42 return strategy; 43 } 44 45 public void setStrategy(String strategy) { 46 this.strategy = strategy; 47 } 48 49 public Integer getStatus() { 50 return status; 51 } 52 53 public void setStatus(Integer status) { 54 this.status = status; 55 } 56 57 public Department getParent() { 58 return parent; 59 } 60 61 public void setParent(Department parent) { 62 this.parent = parent; 63 } 64 65 public List<Employee> getEmployeeList() { 66 return employeeList; 67 } 68 69 public void setEmployeeList(List<Employee> employeeList) { 70 this.employeeList = employeeList; 71 } 72 }
员工类
1 @Entity 2 @Table(name = "s_employee") 3 @SequenceGenerator(name = "caGenerator", sequenceName = "seq_employee", allocationSize = 1) 4 public class Employee extends PublicPO{ 5 6 @Column(name = "NAME") 7 private String name; 8 9 @Column(name = "DESC") 10 private String desc; 11 12 @Column(name = "AGE") 13 private Integer age; 14 15 @Column(name = "hire_date") 16 private Date hireDate; 17 18 @ManyToOne(fetch = FetchType.LAZY) 19 @JoinColumn(name = "DEPARTMENT_ID") 20 private Department department; 21 22 public String getName() { 23 return name; 24 } 25 26 public void setName(String name) { 27 this.name = name; 28 } 29 30 public String getDesc() { 31 return desc; 32 } 33 34 public void setDesc(String desc) { 35 this.desc = desc; 36 } 37 38 public Integer getAge() { 39 return age; 40 } 41 42 public void setAge(Integer age) { 43 this.age = age; 44 } 45 46 public Date getHireDate() { 47 return hireDate; 48 } 49 50 public void setHireDate(Date hireDate) { 51 this.hireDate = hireDate; 52 } 53 54 public Department getDepartment() { 55 return department; 56 } 57 58 public void setDepartment(Department department) { 59 this.department = department; 60 } 61 }
部门下新增员工和修改员工的设计思路。
首先有一个前提,就是前端页面每次都会把一个部门下的所有员工全部传给后台。后台处理提供2种思路
1.先删后增。先将部门和部门下的所有员工信息全部删除,然后再把前台传过来的数据存入数据库。
方案优点:不需要人工判断,后台传入的ID是否在数据库存在,存在就修改,不存在则新增。
方案缺点:如果部门和员工信息关联的表比较多,那从前台传过来的信息就会很多,而且将来如果多一个关联对象,可能还要修改代码。
2.让hibernate自己去判断修改还是新增。在调用对象的SAVE方法时,PO要从前台VO直接转而不要通过JPA的查询方法查出。如果需要校验可以放在另一个事务里或者使用MyBatis。
方案优点:不需要人工判断,代码量少。
方案缺点:对于程序猿要求比较高,需要他比较熟悉Hibernate底层的处理机制。
注意点:
如果数据库中1个部门存在5个员工,而调用save时list中只有4个,那当orphanRemoval = true时,Hibernate是会自动删除那个缺失的员工的。
对于员工从一个部门调到另一个部门的情况,即某员工A的部门ID从1改成了2.这种需要先将该员工的id设置为空,然后找到新部门的部门ID。否则Hibernate发现原来部门的员工少了一个会自动删除这个员工。
3.人工判断数据是否存在,存在则修改,不存在则新增.先根据ID判断是新增还是修改.先判断主表在判断子表.
方案优点:逻辑全靠程序猿自己控制,不怎么依赖Hibernate,定制性比较强.
方案缺点:判断逻辑比较多,比较依赖程序猿的细致程度。
CascadeType中几个值的具体含义
CascadeType.REFRESH:级联刷新,当多个用户同时作操作一个实体,为了用户取到的数据是最新的实时的,在使用实体中的数据之前就可以调用一下refresh()方法!
CascadeType.REMOVE:级联删除,当调用部门的remove()方法时,会删除部门List中的所有员工数据!
CascadeType.MERGE:级联更新,当调用了Merge()方法时,如果部门中的数据改变了会相应的更新员工信息中的数据。
CascadeType.PERSIST:级联保存,当调用了部门的Persist()方法,会级联保存相应List中的员工数据
CascadeType.ALL:包含以上所有级联属性。
(注:以上几种级联操作,只能实在满足数据库的约束时才能生效,比如上边的部门和员工存在主外键关联,那执行REMOVE()方法时可能不一定能级联删除)
Hibernate怎么实现仅仅查询自己想要的字段?
- 使用高级查询DetachedCriteria实现,代碼如下:
String alias = "user_"; //查詢時的table別名 DetachedCriteria dc = DetachedCriteria.forClass(User.class,alias); ProjectionList pList = Projections.projectionList(); pList.add(Projections.property(alias + "." + "id").as("id")); pList.add(Projections.property(alias + "." + "name").as("name")); pList.add(Projections.property(alias + "." + "age").as("age")); pList.add(Projections.property(alias + "." + "sex").as("sex")); dc.setProjection(pList); dc.setResultTransformer(Transformers.aliasToBean(User.class)); resultList = memberService.findByDetached(dc).size();
- 通过HQL语句new POJO()实现,代码如下:
public class Link { private String id; private String name; private String url; private Integer index; public Link(){} //因为:String hql = "select new Link(id,name) from Link"; //所以必须要有接受2个参数的构造函数 public Link(String id,String name){ this.id = id; this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
然后使用HQL时,使用这个对象
String hql = "select new Link(id,name) from Link"; Query query = session.createQuery(hql); //默认查询出来的list里存放的是一个Object对象,但是在这里list里存放的不再是默认的Object对象了,而是Link对象了 List<Link> links = query.list(); for(Link link : links){ String id = link.getId(); String name = link.getName(); System.out.println(id + " : " + name); }
第三种方式是在HQL语句中直接写字段名,类似SQL,代码如下:
String hql = "select id,name from Link"; Query query = session.createQuery(hql); //默认查询出来的list里存放的是一个Object数组,还需要转换成对应的javaBean。 List<Object[]> links = query.list(); for(Object[] link : links){ String id = link[0]; String name = link[1]; System.out.println(id + " : " + name); }
常见问题处理:
- Hibernate的HQL和SQL混用时为什么查询不到SQL的更新结果?
如果sql在hql的后面执行,那在执行sql查询之前一定要调用flush方法,否则在SQL中无法获取HQL的执行结果
HQL在SQL后面执行,那必须先调用Refresh方法。否则HQL查询不到SQL的更新结果。
- 对于Hibernate的flush和refresh方法的使用说明.
1)这两个方法都是EntityManager类的,但flush方法没参数,refresh方法必须传入一个对象。
2)Flush主要是将Hibernate缓存中的数据同步到数据库。如果先用了HQL更新了数据,那调用SQL前最好调用这个方法将数据同步到数据库。否则SQL就会查询不到HQL的更新结果。
3)Refresh是将游离态的对象重新变为持久态,同时也会将数据库中某张表的数据同步到指定的对象上。如果用SQL更新了数据,那用HQL查询前最好调用一下这个方法。
- 在saveOrUpdate带子表的对象时,子表中的记录翻倍,是怎么回事?
这种情况一般发生在两种场景下,
第一种情况。该对象已经有子表数据,然后在此基础上新增。对应到代码中,就是先把list属性get出来,然后add进去多个子表记录,然后saveOrUpdate主表记录。此时新增的子表数据会出现双倍的情况。解决方案:save之前flush一下;
第二种情况,在代码中首先saveOrUpdate一下一个带有子表记录的对象。然后在调用update方法更新一些字段,此时子表的记录也会变成双倍。解决方案:每次对数据库操作后都flush一下,然后再进行第二次操作。
- 调用remove方法时,报错EntityNotFoundException: deleted entity passed to persist?
答:一对多的关系中,如果删除多的一方时,需要先断开多合一的连接,在进行删除。举例来说department和employee是一对多关系,当删除employee时,可以先employee.getDepartment().getEmployees().remove(employee),然后调用remove方法把employee删除.
- Hibernate如何配置打印SQL语句参数.
1、配置Spring数据库配置文件,例如:spring-jpa.xml:
<prop key="hibernate.show_sql">true</prop>--强制打印sql
<prop key="hibernate.format_sql">true</prop>--格式化sql
<prop key="hibernate.use_sql_comments">true</prop>--添加sql的注释,说明sql的触发来源
2、配置log4j或者logback:
<logger name="org.hibernate.SQL" level="DEBUG" /> <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" /> <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" /> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/> <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="TRACE"/>