Hibernate笔记

开始

  好记心不如烂笔头,很多技术如果长时间容易遗忘,故特用此文记录下一些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"/>
原文地址:https://www.cnblogs.com/namelessmyth/p/10910466.html