向万鹏的独立博客


  • 首页

  • 分类

  • 归档

  • 标签

本地SQL检索方式

发表于 2016-12-07   |  

  Hibernate提供本地SQL查询来完善HQL不能涵盖所有的查询特性。例如,可以通过下面的程序完成插入操作:

1
2
3
4
5
6
7
8
9
@Test
public void testNativeSQL(){
String sql = "INSERT INTO gg_department VALUES(?, ?)";
Query query = session.createSQLQuery(sql);
query.setInteger(0, 1)
.setString(1, "ATGUIGU")
.executeUpdate();
}

  注意,本地SQL语句中,gg_department是表名,而不是HQL语句中的类名。
  其实,HQL也支持删除和更新的操作(不支持插入操作),例如,下面的例子演示了用HQL进行删除:

1
2
3
4
5
6
7
@Test
public void testHQLUpdate(){
String hql = "DELETE FROM Department d WHERE d.id = :id";
session.createQuery(hql).setInteger("id", 1)
.executeUpdate();
}

QBC检索方式

发表于 2016-12-07   |  

  QBC查询就是通过使用Hibernate提供的Query By Criteria API来查询对象,这种API封装了SQL语句的动态拼装,对查询提供了更加面向对象的功能接口,下面通过几个Demo来介绍QBC的简单使用方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testQBC1(){
//1. 创建一个 Criteria 对象
Criteria criteria = session.createCriteria(Employee.class);
//2. 添加查询条件: 在 QBC 中查询条件使用 Criterion 来表示
//Criterion 可以通过 Restrictions 的静态方法得到
criteria.add(Restrictions.eq("email", "SKUMAR"));
criteria.add(Restrictions.gt("salary", 5000F));
//3. 执行查询
Employee employee = (Employee) criteria.uniqueResult();
System.out.println(employee);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testQBC2(){
Criteria criteria = session.createCriteria(Employee.class);
//1. AND: 使用 Conjunction 表示
//Conjunction 本身就是一个 Criterion 对象
//且其中还可以添加 Criterion 对象
Conjunction conjunction = Restrictions.conjunction();
conjunction.add(Restrictions.like("name", "a", MatchMode.ANYWHERE));
Department dept = new Department();
dept.setId(80);
conjunction.add(Restrictions.eq("dept", dept));
System.out.println(conjunction);
//2. OR
Disjunction disjunction = Restrictions.disjunction();
disjunction.add(Restrictions.ge("salary", 6000F));
disjunction.add(Restrictions.isNull("email"));
criteria.add(disjunction);
criteria.add(conjunction);
criteria.list();
}
1
2
3
4
5
6
7
8
9
@Test
public void testQBC3(){
Criteria criteria = session.createCriteria(Employee.class);
//统计查询: 使用 Projection 来表示: 可以由 Projections 的静态方法得到
criteria.setProjection(Projections.max("salary"));
System.out.println(criteria.uniqueResult());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testQBC4(){
Criteria criteria = session.createCriteria(Employee.class);
//1. 添加排序
criteria.addOrder(Order.asc("salary"));
criteria.addOrder(Order.desc("email"));
//2. 添加翻页方法
int pageSize = 5;
int pageNo = 3;
criteria.setFirstResult((pageNo - 1) * pageSize)
.setMaxResults(pageSize)
.list();
}

HQL检索方式

发表于 2016-12-07   |   分类于 Hibernate   |  

Hibernate检索对象的方式

  Hibernate提供了以下集中检索对象的方式:

  • 导航对象图检索方式:即根据已经加载的对象导航到其他对象。
  • OID检索方式:按照对象的OID来检索对象。
  • HQL检索方式:使用面向对象的HQL查询语言。
  • QBC检索方式:使用QBC(Query By Criteria)。 API来检索对象,这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口。
  • 本地SQL检索方式:使用本地数据库的SQL查询语句。

HQL检索方式

  本文主要介绍HQL检索方式。
  HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相似。在Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。它有如下功能:
  在查询语句中设定各种查询条件;
  支持投影查询;
  支持分页查询;
  支持连接查询
  支持分组查询,允许使用HAVING和GROUP BY关键字;
  提供内置聚集函数,如sum(),min()和max();
  支持子查询;
  支持动态绑定参数;
  能够调用用户定义的SQL函数或标准的SQL函数。
 
  HQL检索方式包括以下步骤:
  1.通过Session的createQuery()方法创建一个Query对象,它包括一个HQL查询语句,HQL语句中可以包含命名参数;
  2.动态绑定参数;
  3.调用Query相关方法执行查询语句。
 
  Query接口支持方法链编程风格,它的setXxx()方法返回自身实例,而不是void类型。
  HQL VS SQL:
  HQL查询语句是面向对象的,Hibernate负责解析HQL查询语句,然后根据对象-关系映射文件中的映射信息,把HQL查询语句翻译成相应的SQL语句。HQL查询语句中的主体时域模型中的类及类的属性。
  SQL查询语句是与关系数据库绑定在一起的。SQL查询语句中的主体时数据库表及表的字段。
  
  绑定参数:
  Hibernate的参数绑定机制依赖于JDBC API中的PreparedStatement的预定义SQL语句功能。
  HQL的参数绑定有两种形式:
  1.按参数名绑定:在HQL查询语句中定义命名参数,命名参数以”:”开头;
  2.按参数位置绑定:在HQL查询语句中用”?”来定义参数位置。
  
  Hibernate可以通过setEntity()方法把参数与一个实体对象绑定,还可以通过setParameter()方法来绑定任意类型的参数。
  
  例如,可以通过下面的代码使用基于位置的参数来查询employee表中salary>1000,email字段中包含字符A,且属于部门号为1的部门的记录,查询结果按照salary进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testHQL(){
//1. 创建 Query 对象
//基于位置的参数.
String hql = "FROM Employee e WHERE e.salary > ? AND e.email LIKE ? AND e.dept = ? "
+ "ORDER BY e.salary";
Query query = session.createQuery(hql);
//2. 绑定参数
//Query 对象调用 setXxx 方法支持方法链的编程风格.
Department dept = new Department();
dept.setId(1);
query.setFloat(0, 1000)
.setString(1, "%A%")
.setEntity(2, dept);
//3. 执行查询
List<Employee> emps = query.list();
System.out.println(emps.size());
}

  在控制台打印的sql语句为:
  image_1b3cb0l9js67eif1hno1988k1d9.png-35.9kB
  
  或者使用基于参数名的参数实现相同的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testHQLNamedParameter(){
//1. 创建 Query 对象
//基于命名参数.
String hql = "FROM Employee e WHERE e.salary > :sal AND e.email LIKE :email AND e.dept = :dept "
+ "ORDER BY e.salary";
Query query = session.createQuery(hql);
//2. 绑定参数
//Query 对象调用 setXxx 方法支持方法链的编程风格.
Department dept = new Department();
dept.setId(1);
query.setFloat("sal", 1000)
.setString("email", "%A%")
.setEntity("dept", dept);
//3. 执行查询
List<Employee> emps = query.list();
System.out.println(emps.size());
}

HQL分页查询

  HQL主要通过下面两个方法来实现分页查询:
  setFirstResult(int firstResult):设定从哪一个对象开始检索,参数firstResult指定这个对象在查询结果中的索引位置。索引位置的起始值为0,默认情况下,Query从查询结果中的第一个对象开始检索。
  setMaxResults(int maxResults):设定一次最多检索出的对象的数目。默认情况下,Query和Criteria接口检索出查询结果中的所有的对象。
  
  下面的代码实现了将每5条记录分为一页,查询第4页的记录的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testPageQuery(){
String hql = "FROM Employee";
Query query = session.createQuery(hql);
int pageNo = 4;
int pageSize = 5;
List<Employee> emps =
query.setFirstResult((pageNo - 1) * pageSize)
.setMaxResults(pageSize)
.list();
System.out.println(emps);
}

image_1b3cdoinmq5a1oegikp8eulfam.png-33.5kB

HQL命名查询

  可以在映射文件中定义命名查询语句,实现将HQL语句的外置化,以实现通过不修改源码达到修改功能的效果。下面进行演示:
  
在Employee.hbm.xml文件中定义命名查询语句,其中query节点和class节点并列:

1
2
<query name="salaryEmps">
<![CDATA[FROM Employee e WHERE e.salary > :minSal AND e.salary < :maxSal]]></query>

通过session.getNamedQuery()方法查询,参数为上述query节点的name属性值:

1
2
3
4
5
6
7
8
9
10
@Test
public void testNamedQuery(){
Query query = session.getNamedQuery("salaryEmps");
List<Employee> emps = query.setFloat("minSal", 5000)
.setFloat("maxSal", 10000)
.list();
System.out.println(emps.size());
}

HQL投影查询

  投影查询,即希望查询的结果仅包含实体的部分属性。通过SELECT关键字实现。下面的代码只希望查询指定部门员工的email,salary和dept属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testFieldQuery(){
String hql = "SELECT e.email, e.salary, e.dept FROM Employee e WHERE e.dept = :dept";
Query query = session.createQuery(hql);
Department dept = new Department();
dept.setId(1);
List<Object[]> result = query.setEntity("dept", dept)
.list();
for(Object [] objs: result){
System.out.println(Arrays.asList(objs));
}
}

这时候,返回的list中的元素是Object数组,其中每一个Object数组中存放了一条记录对应的属性值。
image_1b3celdo5l411kvka45bm1rup13.png-45.9kB

  遍历元素为Object数组的list有点麻烦,可以通过另一种方式将查询的属性值存放在一个Employee对象中:先在Employee类中定义一个包含指定属性的构造器,然后通过下面的程序可以查询对应的属性:
  
Employee.java

1
2
3
4
5
6
7
8
9
10
public Employee(String email, float salary, Department dept) {
super();
this.salary = salary;
this.email = email;
this.dept = dept;
}
public Employee() {
// TODO Auto-generated constructor stub
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testFieldQuery2(){
String hql = "SELECT new Employee(e.email, e.salary, e.dept) "
+ "FROM Employee e "
+ "WHERE e.dept = :dept";
Query query = session.createQuery(hql);
Department dept = new Department();
dept.setId(1);
List<Employee> result = query.setEntity("dept", dept)
.list();
for(Employee emp: result){
System.out.println(emp.getId() + ", " + emp.getEmail()
+ ", " + emp.getSalary() + ", " + emp.getDept());
}
}

  注意构造器中属性顺序需和HQL语句中的属性顺序一致。   

HQL报表查询

  报表查询用于对数据分组和统计,与SQL一样,HQL利用GROUP BY关键字对数据分组,用HAVING关键字对分组数据设定约束条件。
  在HQL查询语句中可以调用以下聚集函数:
  count()
  min()
  max()
  sum()
  avg()
  下面的代码演示了报表查询,根据department对employee进行分组,然后查询部门内最低工资大于3000的部门的最低工资和最高工资。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testGroupBy(){
String hql = "SELECT min(e.salary), max(e.salary) "
+ "FROM Employee e "
+ "GROUP BY e.dept "
+ "HAVING min(salary) > :minSal";
Query query = session.createQuery(hql)
.setFloat("minSal", 3000);
List<Object []> result = query.list();
for(Object [] objs: result){
System.out.println(Arrays.asList(objs));
}
}

image_1b3chmnlq30q12et1bi9hdo1erh1g.png-29.3kB

HQL(迫切)左外连接

迫切左外连接:
  在HQL中通过LEFT JOIN FETCH关键字表示迫切左外连接检索策略。
  list()方法返回的集合存放实体对象的引用,每个Department对象关联的Employee集合都被初始化,存放所有关联的Employee的实体对象。
  查询结果中可能会包含重复元素,可以通过一个HashSet来过滤重复元素,也可以在HQL语句中使用DISTINCT关键字来过滤。
  下面的例子测试了迫切左外连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testLeftJoinFetch(){
String hql = "SELECT d FROM Department d LEFT JOIN FETCH d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
//depts = new ArrayList<>(new LinkedHashSet(depts));
System.out.println(depts.size());
for(Department dept: depts){
System.out.println(dept.getName() + "-" + dept.getEmps().size());
}
}

  image_1b3cia2m0ngg1lef1m9v52hoo11t.png-26.1kB
  这种情况下将查询出所有的26条记录(包含重复的department),如果将注释行打开,或者将hql改为”SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps”,可以过滤掉重复元素:
  image_1b3cid5q18sfqeh19sl8sg1lfq2a.png-23.9kB
  
左外连接:
  HQL使用LEFT JOIN关键字表示左外连接查询。
  list()方法返回的集合中存放的是对象数组类型。(存放了一个Department对象和一个Employee对象)。
  由于list()方法返回的集合中存放的是一个Department对象和一个Employee对象构成的对象数组,所以不能用hashset来去重,因为每一个对象数组其实是不一样的。
  如果需要去重,可以通过SELECT关键字使list()方法返回的集合中仅包含Department对象,然后用DISTINCT来去重。
  可以根据配置文件来决定Employee集合的检索策略。默认情况下是lazy=true,所以当不使用Employee集合时是不会初始化Employee集合的。但是实际上,当查询Department时,Employee集合已经被查出来了,但是没有被初始化,非要等到使用Employee集合的时候再重新去查一遍。所以通常情况下,如果需要使用到表的左外连接,建议使用迫切的左外连接,因为反正查department的时候都要把employee集合都查出来,不如直接一次初始化完毕。

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testLeftJoin(){
String hql = "FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Object []> result = query.list();
System.out.println(result);
for(Object [] objs: result){
System.out.println(Arrays.asList(objs));
}
}

  image_1b3cjvfdievbbg6uofjc91q5o3h.png-43kB
  
  可以看到,在查询department后已经将employee都查出来了,但是没有初始化employee集合。
  下面的程序演示了使用SELECT DISTINCT关键字去重。但是,每当使用到employee集合,还要重新select一遍。

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testLeftJoin(){
String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for(Department dept: depts){
System.out.println(dept.getName() + ", " + dept.getEmps().size());
}
}

image_1b3cjgbmn3381t4j14o01ctmqoj2n.png-33.7kB

HQL(迫切)内连接

迫切内连接:
  HQL使用INNER JOIN FETCH关键字表示迫切内连接,也可以省略INNER关键字。
  list()方法返回的集合中存放Department对象的引用,每个Department对象的Employee集合都被初始化,存放所有关联的Employee对象。
  如果希望list()方法返回的集合仅包含Department对象,可以在HQL查询语句中使用SELECT关键字。
  
内连接:
  INNER JOIN关键字表示内连接,也可以省略INNER关键字。
  list()方法返回的集合中存放的每个元素对应查询结果的一条记录,每个元素都是对象数组类型。
  
  (迫切)内连接和(迫切)左外连接的区别仅在于,(迫切)内连接不会返回从表中没有信息与主表对应的记录。例如,在上面的例子中,有的部门员工数为0,即该部门不存在记录department.id=employee.dept_id,但是(迫切)左外连接仍然会返回该记录,但是(迫切)内连接则不会。如下:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testLeftJoin(){
String hql = "SELECT DISTINCT d FROM Department d INNER JOIN d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for(Department dept: depts){
System.out.println(dept.getName() + ", " + dept.getEmps().size());
}

image_1b3cmq14eutk12b7175n1j6s180i4b.png-29.8kB
  打印的depts.size()不再是上面的10,而是8,因为有6号部门和10号部门没有员工:
  image_1b3cms4no1g1u1ihguvhnpd16us4o.png-44.9kB
  image_1b3cmsc88rnrtpp1ro5uav1a8155.png-7.9kB
  
  如果用SELECT来查询employee,道理也和上面讲述的时一样的,加FETCH会立即初始化department,不加FETCH则会等到使用department时才分别初始化对应的department。

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testLeftJoinFetch2(){
String hql = "SELECT e FROM Employee e INNER JOIN FETCH e.dept";
Query query = session.createQuery(hql);
List<Employee> emps = query.list();
System.out.println(emps.size());
for(Employee emp: emps){
System.out.println(emp.getName() + ", " + emp.getDept().getName());
}
}

image_1b3cng6hj1mc319vk7lfdr75j5i.png-25.7kB

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testLeftJoinFetch2(){
String hql = "SELECT e FROM Employee e INNER JOIN e.dept";
Query query = session.createQuery(hql);
List<Employee> emps = query.list();
System.out.println(emps.size());
for(Employee emp: emps){
System.out.println(emp.getName() + ", " + emp.getDept().getName());
}
}

image_1b3cniba6tfmm318hu1ar51djl5v.png-41kB

Hibernate检索策略

发表于 2016-12-07   |   分类于 Hibernate   |  

检索策略追求的两个目标是:(以客户和订单的例子说明)

  • 不浪费内存:当Hibernate从数据库中加载Customer对象时,如果同时加载所有关联的Order对象,而程序实际上仅仅需要访问Customer对象,那么这些关联的Order对象就白白浪费了许多内存。
  • 更高的查询效率:发送尽可能少的SQL语句。

类级别的检索策略

  类级别可选的检索策略包括立即检索和延迟检索,默认为延迟检索。
  立即检索: 立即加载检索方法指定的对象。
  延迟检索: 延迟加载检索方法指定的对象。在使用具体的属性时,再进行加载。
  类级别的检索策略可以通过在映射文件中的<class>元素的lazy属性进行设置,默认为true。
  如果程序加载一个对象的目的是为了访问它的属性,可以采取立即检索。
  如果程序加载一个持久化对象的目的是仅仅为了获得它的引用,可以采用延迟检索。注意延迟检索可能会出现懒加载异常。
  无论<class>元素的lazy属性是true还是false, 仅是只对Session的load方法起作用,因为Session的get方法及Query的list方法在类级别总是使用立即检索策略。若<class>元素的lazy属性为true或取默认值,Session的load()方法不会执行查询数据表的SELECT语句,仅返回代理类对象的实例,该代理类实例有如下特征:

  1. 由Hibernate 在运行时采用CGLIB工具动态生成;
  2. Hibernate创建代理类实例时,仅初始化其 OID 属性;
  3. 在应用程序第一次访问代理类实例的非OID属性时,Hibernate 会初始化代理类实例。

     

一对多和多对多的检索策略

  在映射文件中,用<set>元素来配置一对多关联及多对多关联关系,<set>元素有下面三个属性。
lazy属性:主要决定orders集合被初始化的时机。即到底是在加载Customer对象时就被初始化,还是在程序访问orders集合时被初始化。
batch-size属性:用来为延迟检索策略或立即检索策略设定批量检索的数量。批量检索能减少SELECT语句的数目,提高延迟检索或立即检索的运行性能。
fetch属性:
  若取值为”select”或”subselect”,用来指定初始化orders的查询语句的形式,当fetch属性为”subselect”时,假定Session缓存中有n个orders集合代理类实例没有被初始化,Hibernate能够通过带子查询的select语句,来批量初始化n个orders集合代理类实例,此时,batch-size属性将被忽略,子查询中的select语句为查询CUSTOMERS表OID的SELECT语句。
  若取值为”join”,则指定orders集合被初始化的时机。若把fetch设置为 “join”,则在检索Customer对象时,会采用迫切左外连接策略来检索所有关联的Order对象,此时,lazy属性将被忽略。迫切左外连接检索策略的优点在于使用更少的select语句。注意,Query的list方法会忽略映射文件中配置的迫切左外连接检索策略,而依旧采用延迟加载策略。

lazy属性和fetch属性的可能组合如下图所示:

image_1b3bqsmcrqsg1ndfvpm1s4uah9.png-196.8kB

关于延迟检索和增强延迟检索:

  • 在延迟检索(lazy属性值为true)集合属性时,Hibernate在以下情况下初始化集合代理类实例:
    1. 应用程序第一次调用集合的iterator(),size(),isEmpty(), contains()等方法访问集合的属性时。
    2. 通过Hibernate.initialize()静态方法显式初始化时。
  • 增强延迟检索(lazy属性为extra),与lazy=”true”类似,主要区别是增强延迟检索策略能进一步延迟 Customer对象的orders集合代理实例的初始化时机:
    1. 当程序第一次访问orders属性的iterator()方法来遍历元素时,会导致orders集合代理类实例的初始化。
    2. 当程序第一次访问order属性的size(),contains()和isEmpty()方法时,Hibernate不会初始化orders集合类的实例,仅通过特定的select语句查询必要的信息,而不会检索所有的Order对象。

多对一和一对一关联的检索策略

  和<set>一样,<many-to-one>节点也有一个lazy属性和一个fetch属性,如下图所示:
  image_1b3brrg3o1oqg5m11irogui1i1mm.png-89.4kB
  
  且同上述一样,若fetch设为join,那么lazy属性将被忽略。
  
下面总结一下各种检索策略:
 
image_1b3bs3qahkeubm539c1fe51cli13.png-180.4kB   

Hibernate的多对多关联关系(单向和双向)

发表于 2016-12-06   |   分类于 Hibernate   |  

  n-n(多对多)的关联关系必须通过连接表实现。下面以商品种类和商品之间的关系,即一个商品种类下面可以有多种商品,一种商品又可以属于多个商品种类,分别介绍单向的n-n关联关系和双向的n-n关联关系。

单向的n-n关联关系

  如果仅使用两张数据表,是不能实现n-n的关联关系的,如下图:
  image_1b39rjpnd1pi81qv738a15pi1rbv13.png-27.6kB
  商品ITEM_AA属于商品种类CATEGORY_AA,但是如果商品ITEM_AA又同时属于商品种类CATEGORY_BB呢,两张数据表无法实现这种关系,所以我们需要使用到连接表,如下图所示:
  image_1b39rmve8rui1n6u4lemavnk1g.png-34.6kB
  我们添加一张连接表,其中的每一条记录表示某一个商品和某一个商品种类的对应关系。
  单向的n-n关联关系的域模型和关系数据模型如下图所示:
  image_1b39rdrgb16fp143n1hrtkv23fum.png-82.5kB
  下面来实现这种关系,首先新建两个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Category {
private Integer id;
private String name;
private Set<Item> items = new HashSet<>();
//getters and setters
}
public class Item {
private Integer id;
private String name;
//getters and setters
}

生成并编辑映射文件:
 
Category.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.n2n">
<class name="Category" table="CATEGORIES">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<!-- table: 指定中间表 -->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID" />
</key>
<!-- 使用 many-to-many 指定多对多的关联关系. column 执行 Set 集合中的持久化类在中间表的外键列的名称 -->
<many-to-many class="Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>

Item.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.n2n.Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
</class>
</hibernate-mapping>

  与1-n关联关系类似,在映射时,必须在category类的映射文件中设置set节点。但是不同的是,在set节点中指定的表为连接表CATEGORIES_ITEMS,而不再是所关联的类Item所对应的表,而且,在set节点中设置的是many-to-many节点,而不再是one-to-many。many-to-many节点的class属性指定了items集合中存放的对象类型是Item,colume属性指定了连接表CATEGORIES_ITEMS中参照ITEMS表的外键是ITEM_ID。
  现在随便一个测试程序,发现除了生成了表CATEGORIES和表ITEMS,还生成了连接表CATEGORIES_ITEMS,表结构为:
  image_1b39sup4kk251768905q88mp3h.png-17.9kB
   
  image_1b39srd5u68vmnn1rboe841m8c2a.png-19.8kB
  
  image_1b39ss3o61adjk5i11pbcorv692n.png-16.6kB
  image_1b39st1rd1ve880v139411iec2o34.png-20.4kB
  
下面测试save操作和get操作:
  
单向n-n关联关系的save操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Test
public void testSave(){
Category category1 = new Category();
category1.setName("C-AA");
Category category2 = new Category();
category2.setName("C-BB");
Item item1 = new Item();
item1.setName("I-AA");
Item item2 = new Item();
item2.setName("I-BB");
//设定关联关系
category1.getItems().add(item1);
category1.getItems().add(item2);
category2.getItems().add(item1);
category2.getItems().add(item2);
//执行保存操作
session.save(category1);
session.save(category2);
session.save(item1);
session.save(item2);
}

  运行程序,根据控制台打印的sql语句,发现会先往CATEGORIES表和ITEMS表分别插入两条记录,然后再往连接表CATEGORIES_ITEMS插入四条记录,这是符合我们的预期的:
  image_1b39tavqsr1pt8k1p2t1t4obkh3u.png-45.7kB
  image_1b39tbqdjs8b5gv6jc1q3tob74b.png-35.3kB
   
  image_1b39tc9ha1vs4ghpk69184lfi4o.png-12kB
  image_1b39tcgol9ki11v81o2bjgb1i3e55.png-12.5kB
  image_1b39tcqfl1q8k72e1fd4n2117gp5i.png-10.9kB
  
单向n-n关联关系的get操作:

1
2
3
4
5
6
7
8
9
@Test
public void testGet(){
Category category = (Category) session.get(Category.class, 1);
System.out.println(category.getName());
//需要连接中间表
Set<Item> items = category.getItems();
System.out.println(items.size());
}

  同之前的1-n、1-1中类似,先查询category会使用懒加载机制,不会立即加载items,只有使用到items的时候才会加载。当加载items的时候,是通过内连接连接中间表CATEGORIES_ITEMS进行查询的:
  image_1b39u7n5a1qfflau1q9d1n9v1kd55v.png-32.1kB   

双向的n-n关联关系

  在双向的n-n关联关系中,两端的类都需要使用集合属性,即Category中要包含一个Set<Item>,Item中也要包含一个Set<Category>,,且在数据库中同样需要使用连接表连接。其域模型和关系数据模型如下:
  image_1b39ucin71p3okqd5vhhjop4j6c.png-84kB
  
  下面编写代码,首先创建两个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Category {
private Integer id;
private String name;
private Set<Item> items = new HashSet<>();
//getters and setters
}
public class Item {
private Integer id;
private String name;
private Set<Category> categories = new HashSet<>();
//getters and setters
}

生成并编辑映射文件:
 
Category.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.n2n">
<class name="Category" table="CATEGORIES">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<!-- table: 指定中间表 -->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID" />
</key>
<!-- 使用 many-to-many 指定多对多的关联关系. column 执行 Set 集合中的持久化类在中间表的外键列的名称 -->
<many-to-many class="Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>

Item.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.n2n.Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<set name="categories" table="CATEGORIES_ITEMS" inverse="true">
<key column="I_ID"></key>
<many-to-many class="com.atguigu.hibernate.n2n.Category" column="C_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>

  大部门内容和单向n-n中类似,只是需要在Item类中添加字段

1
  private Set<Category> categories = new HashSet<>();

并且需要在Item类的映射文件中像Category映射文件中那样配置set节点。此外,还有一个很重要的地方,就是需要在Category和Item的其中一端的映射文件中的set节点中设置inverse=”true”属性,以放弃维护关联关系,否则两端都会维护默认关系,这在某些情况下会导致错误,下面会演示。
  现在生成数据表,表结构和单向n-n中一样:
  image_1b39sup4kk251768905q88mp3h.png-17.9kB
   
  image_1b39srd5u68vmnn1rboe841m8c2a.png-19.8kB
  
  image_1b39ss3o61adjk5i11pbcorv692n.png-16.6kB
  image_1b39st1rd1ve880v139411iec2o34.png-20.4kB

  下面测试save操作和get操作:
  
双向n-n关联关系的save操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Category category1 = new Category();
category1.setName("C-AA");
Category category2 = new Category();
category2.setName("C-BB");
Item item1 = new Item();
item1.setName("I-AA");
Item item2 = new Item();
item2.setName("I-BB");
//设定关联关系
category1.getItems().add(item1);
category1.getItems().add(item2);
category2.getItems().add(item1);
category2.getItems().add(item2);
item1.getCategories().add(category1);
item1.getCategories().add(category2);
item2.getCategories().add(category1);
item2.getCategories().add(category2);
//执行保存操作
session.save(category1);
session.save(category2);
session.save(item1);
session.save(item2);

  运行程序,和单向n-n中一样,一共打印8条insert语句,并成功插入记录。但是,现在我们移除Item.hbm.xml文件中的inverse=”true”属性,再运行程序,会抛出异常。这是因为在默认情况下n-n的两端都会维护关联关系,当执行上述四条save代码后,category要维护关联关系,往连接表中添加4条记录,然后item也要维护关联关系,往连接表中添加相同的4条记录,会导致连接表中主键重复,解决方法就是在Item.hbm.xml文件中设置inverse=”true”属性。
双向n-n关联关系的get操作:

1
2
3
4
5
6
7
8
9
@Test
public void testGet(){
Category category = (Category) session.get(Category.class, 1);
System.out.println(category.getName());
//需要连接中间表
Set<Item> items = category.getItems();
System.out.println(items.size());
}

  双向n-n关联关系的get操作和单向n-n中一样,包含懒加载机制和内连接。
  补充一下,双向n-n中两端的映射文件的字段对应如下图所示:
  image_1b3a0lgva19hamhlrjs1g461gj66p.png-146.2kB

Hibernate的一对一关联关系

发表于 2016-12-05   |   分类于 Hibernate   |  

  Hibernate的一对一关联关系,分为基于外键的一对一关联关系和基于主键的一对一关联关系。在这篇文章中,我们以部门和部门经理的例子来说明,一个部门对应唯一一个部门经理,一个部门经理也对应唯一一个部门。
  在基于外键的一对一关联关系中,一端通过一个主键以外的字段关联另一端的主键,如下图所示:
  E3CF6A0B-55EB-43AE-BDBC-D2DB80C116EF.jpg-8.3kB
   
  在基于主键的一对一关联关系中,一端直接通过主键关联另一端的主键,并通过另一端的主键生成自己的主键,如下图所示:
  image_1b36lr2j11khlidodn5i5e1net12.png-22.9kB
   
  下面进行详细说明。

基于外键的一对一关联关系

  对于基于外键的1-1关联关系,外键可以存放在任意一端,例如在本例中,外键既可以存放在department一端,也可以存放在manager一端,我们假设存放在department一端。在需要存放外键的一端,增加many-to-one节点,并且为many-to-one节点添加unique=”true”属性,来表示为1-1关联,添加unique=”true”属性了以后,不同的department就不能关联同一个manager了。在不存放外键的一端,需要使用one-to-one节点,并且在该节点中添加property-ref属性来指定存放外键一端的除主键以外的字段来作为关联字段。代码如下,首先创建两个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Department {
private Integer deptId;
private String deptName;
private Manager mgr;
//getters and setters
}
public class Manager {
private Integer mgrId;
private String mgrName;
private Department dept;
//getters and setters
}

映射文件:
 
Department.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.one2one.foreign.Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="DEPT_ID" />
<generator class="native" />
</id>
<property name="deptName" type="java.lang.String">
<column name="DEPT_NAME" />
</property>
<!-- 使用 many-to-one 的方式来映射 1-1 关联关系 -->
<!-- 添加unique="true"属性,来表示为1-1关联 -->
<many-to-one name="mgr" class="com.atguigu.hibernate.one2one.foreign.Manager"
column="MGR_ID" unique="true"></many-to-one>
</class>
</hibernate-mapping>

Manager.hbm.xml
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.one2one.foreign.Manager" table="MANAGERS">
<id name="mgrId" type="java.lang.Integer">
<column name="MGR_ID" />
<generator class="native" />
</id>
<property name="mgrName" type="java.lang.String">
<column name="MGR_NAME" />
</property>
<!-- 映射 1-1 的关联关系: 在对应的数据表中已经有外键了, 当前持久化类使用 one-to-one 进行映射 -->
<!--
没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
-->
<one-to-one name="dept"
class="com.atguigu.hibernate.one2one.foreign.Department"
property-ref="mgr"></one-to-one>
</class>
</hibernate-mapping>

运行一个空的test程序,可以生成数据库表managers和departments:
 
image_1b36mlbre1tev98d1f9rusbiu91f.png-19kB
 
image_1b36mmdvq19lu1o681ehs1sgc1gmh1s.png-19.8kB
image_1b36mmu0n1qp61tpek735sbdfr29.png-18.9kB

下面测试基于外键的1-1关联关系的save和get操作:

基于外键的1-1关联关系的save操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testSave(){
Department department = new Department();
department.setDeptName("DEPT-BB");
Manager manager = new Manager();
manager.setMgrName("MGR-BB");
//设定关联关系
department.setMgr(manager);
manager.setDept(department);
//保存操作
//建议先保存没有外键列的那个对象. 这样会减少 UPDATE 语句
session.save(manager);
session.save(department);
}

  这段代码可以正常插入记录。和n-1关联关系中一样,如果先保存了存放外键一端的对象,后保存被外键关联的一端的对象,即如果先执行session.save(department);,后执行session.save(manager);,虽然同样可以正确保存,但是会多出一条update语句用于维护关联关系,所以通常建议先插入没有外键一端的对象,后插入有外键一端的对象。
  
基于外键的1-1关联关系的get操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testGet(){
//1. 默认情况下对关联属性使用懒加载
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept.getDeptName());
//2. 所以会出现懒加载异常的问题.
// session.close();
// Manager mgr = dept.getMgr();
// System.out.println(mgr.getClass());
// System.out.println(mgr.getMgrName());
//3. 查询 Manager 对象的连接条件应该是 dept.manager_id = mgr.manager_id
//而不应该是 dept.dept_id = mgr.manager_id
Manager mgr = dept.getMgr();
System.out.println(mgr.getMgrName());
}

  同n-1关联关系一样,当查询存放外键的一端的对象department的时候,使用懒加载机制,即不会立即加载它关联的另一端的对象manager,而只有等到要使用manager的时候,才会发送select语句加载。那么同样也有可能发生懒加载异常。运行结果如下图所示:
  image_1b36o9f1j1a1mss9i951atf1cqh2m.png-65.3kB
  值得注意的是,当要使用到manager对象时,是通过左外连接查询到manager对象的,连接条件是dept.manager_id = mgr.manager_id,这是正确的,因为我们在Manager.hbm.xml中的one-to-one节点中配置了property-ref=”mgr”,指定关联字段为department的mgr字段。如果没有设置property-ref属性,那么默认关联的字段为department的id字段,例如,我们去掉property-ref=”mgr”的设置,运行testGet()方法,则会打印如下的sql语句(连接条件是dept.dept_id = mgr.manager_id),这显然是不符合需求的。
  image_1b36vjp9k2tp1jum6srtu7bs833.png-35.3kB
  
  还有一个注意点,就是当首先查询不存放外键的一端的对象时,即manager,由于其中没有设置外键关联到department,所以会使用左外连接查询,一并查询出另一端的对象,即department,而且已经完成了初始化。如下所示:

1
2
3
4
5
6
7
8
9
10
@Test
public void testGet2(){
//在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
//并已经进行初始化.
Manager mgr = (Manager) session.get(Manager.class, 1);
//在执行下面的代码之前已经完成了对department对象的初始化
System.out.println(mgr.getMgrName());
System.out.println(mgr.getDept().getDeptName());
}

运行结果:
image_1b37027r68l01dvj1j2u1dnu14ua3t.png-33.9kB
  
  关于基于外键的一对一关系,还有一点值得注意,不能在两端都使用外键映射为1-1,例如下面这种情况,表department表和manager都分别设置了外键manager_id和department_id,那么当一条manager记录单向关联了一条department记录,而这条department记录却关联向另一条manager记录,就会出现问题,如下图所示:
  image_1b372rtct32gnaf1uu216bkcs94a.png-92kB        

基于主键的一对一关联关系

  基于主键的1-1映射策略,是指一端的主键生成器使用foreign策略,表明根据“对方”的主键来生成自己的主键,自己并不能独立生成主键。<param>子节点指定使用当前持久化类的哪一个属性来作为“对方”。例如:

1
2
3
4
<generator class="foreign">
<!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
<param name="property">mgr</param>
</generator>

  采用foreign主键生成器策略的一端使用one-to-one元素映射关联属性,需要在one-to-one节点中设置constrained=”true”,以指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(即“对方”)所对应的数据库表的主键。另一端同样使用one-to-one节点映射关联关系。
  下面我们仍以department和manager的例子进行测试,首先新建两个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Department {
private Integer deptId;
private String deptName;
private Manager mgr;
//getters and setters
}
public class Manager {
private Integer mgrId;
private String mgrName;
private Department dept;
//getters and setters
}

映射文件:
 
Department.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.one2one.primary">
<class name="Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="DEPT_ID" />
<!-- 使用外键的方式来生成当前的主键 -->
<generator class="foreign">
<!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
<param name="property">mgr</param>
</generator>
</id>
<property name="deptName" type="java.lang.String">
<column name="DEPT_NAME" />
</property>
<!--
采用 foreign 主键生成器策略的一端增加 one-to-one 元素映射关联属性,
其 one-to-one 节点还应增加 constrained=true 属性, 以使当前的主键上添加外键约束
-->
<one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>
</class>
</hibernate-mapping>

Manager.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.one2one.primary.Manager" table="MANAGERS">
<id name="mgrId" type="java.lang.Integer">
<column name="MGR_ID" />
<generator class="native" />
</id>
<property name="mgrName" type="java.lang.String">
<column name="MGR_NAME" />
</property>
<one-to-one name="dept"
class="com.atguigu.hibernate.one2one.primary.Department"></one-to-one>
</class>
</hibernate-mapping>

生成的数据库表如下:
 
managers
image_1b373jjnq7vj1dck1j6f4qkvjg4n.png-16.2kB

departments
image_1b373km8b96o52engkc4s197a54.png-16.9kB
image_1b373l1mp1cm2161gs14amffm5h.png-17.9kB
可以看到,表departments是根据主键DEPT_ID来关联表managers的。
 
下面测试save和get方法:

基于主键的1-1关联关系的save操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testSave(){
Department department = new Department();
department.setDeptName("DEPT-AA");
Manager manager = new Manager();
manager.setMgrName("MGR-AA");
//设定关联关系
manager.setDept(department);
department.setMgr(manager);
//保存操作
//先插入哪一个都不会有多余的 UPDATE
session.save(department);
session.save(manager);
}

  和之前不同的是,不论是先执行session.save(department);,还是先执行session.save(manager);,效果都是一样的,都只有两条insert语句,不会有update语句,而且都会先执行insert into managers,后执行insert into departments,如下图:
  image_1b374o1mk1nr6pit19rc154h1ais5u.png-22.1kB
  
  这是因为,现在department是根据主键关联manager,主键是不能像外键那样先被置为null然后进行update修改的,所以不论哪一个语句放在前面,都会先等到manager记录插入后,再插入department记录。
  
基于主键的1-1关联关系的get操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testGet(){
//1. 默认情况下对关联属性使用懒加载
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept.getDeptName());
//2. 所以会出现懒加载异常的问题.
Manager mgr = dept.getMgr();
System.out.println(mgr.getMgrName());
}
@Test
public void testGet2(){
//在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
//并已经进行初始化.
Manager mgr = (Manager) session.get(Manager.class, 1);
/*System.out.println(mgr.getMgrName());
System.out.println(mgr.getDept().getDeptName()); */
}

  基于主键的1-1和基于外键的1-1十分相似,在查询department时都使用懒加载机制,可能会抛出懒加载异常,在查询manager时都会使用左外连接,但不同的是,我们在Manager.hbm.xml文件的one-to-one节点中没有设置property-ref属性,即默认department中关联manager的字段是department的id,这正是我们在基于主键的1-1关系中希望的,所以可以看到,左外连接的连接条件是dept.dept_id = mgr.manager_id:
  image_1b376sjn8145n11rm1844dfshjg6r.png-39.6kB

Hibernate的一对多关联关系(单向和双向)

发表于 2016-12-04   |   分类于 Hibernate   |  

  在领域模型中,类与类之间最普遍的关系就是关联关系。在 UML 中,关联是有方向的。以 Customer 和 Order 为例:一个用户能发出多个订单, 而一个订单只能属于一个客户。从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联。   

单向n-1的关联关系

  单向 n-1 关联只需从 n 的一端可以访问 1 的一端。
  域模型:从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性,而在 Customer 类中无需定义存放 Order 对象的集合属性。
  关系数据模型: ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键。(外键)
  Hibernate 使用 元素来映射多对一关联关系。
  例如,在Customer和Order的例子中,首先创建两个类文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Customer {
private Integer customerId;
private String customerName;
//getters and setters
}
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
//getters and setters
}

生成hibernate映射文件:

Customer.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.entities.n21.Customer" table="CUSTOMERS">
<id name="customerId" type="java.lang.Integer">
<column name="CUSTOMER_ID" />
<generator class="native" />
</id>
<property name="customerName" type="java.lang.String">
<column name="CUSTOMER_NAME" />
</property>
</class>
</hibernate-mapping>

Order.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.n21">
<class name="Order" table="ORDERS">
<id name="orderId" type="java.lang.Integer">
<column name="ORDER_ID" />
<generator class="native" />
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME" />
</property>
<!--
映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系
name: 多这一端关联的一那一端的属性的名字
class: 一那一端的属性对应的类名
column: 一那一端在多的一端对应的数据表中的外键的名字
-->
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
</class>
</hibernate-mapping>

先随便运行一个程序,来生成数据库表:
 
customers表
image_1b34sm9cq1m8p194v1e3p1jdlmnk9.png-21.3kB

orders表
image_1b34sn5651s8a1es8j9e1sar1sm2m.png-25.8kB

image_1b34snfoddfq15l2mn2i7r5mr13.png-17.7kB
 
单向n-1的save操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Test
public void testMany2OneSave(){
Customer customer = new Customer();
customer.setCustomerName("AA");
Order order1 = new Order();
order1.setOrderName("ORDER-1");
Order order2 = new Order();
order2.setOrderName("ORDER-2");
//设定关联关系
order1.setCustomer(customer);
order2.setCustomer(customer);
//执行 save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT
//先插入 1 的一端, 再插入 n 的一端, 只有 INSERT 语句.
session.save(customer);
session.save(order1);
session.save(order2);
//先插入 Order, 再插入 Customer. 3 条 INSERT, 2 条 UPDATE
//先插入 n 的一端, 再插入 1 的一端, 会多出 UPDATE 语句!
//因为在插入多的一端时, 无法确定 1 的一端的外键值. 所以只能等 1 的一端插入后, 再额外发送 UPDATE 语句.
//推荐先插入 1 的一端, 后插入 n 的一端
//session.save(order1);
//session.save(order2);
//session.save(customer);
}
}

运行程序,可以成功插入记录,并且在控制台只会打印三条insert语句:

image_1b34t38nn1q6e1lavbqclas1oka1g.png-11.8kB

image_1b34t3h021na01pbno00vp01g4m1t.png-11.7kB

image_1b34t4fap1gq41b5in52i8v1vmd2a.png-37.4kB

但是,如果注释掉倒数4,5,6行的代码,而使用最后的三行代码,即先保存order1和order2,再保存customer,同样也可以成功插入,但是除了会输出三行insert语句,还会输出两行update语句。如下图:
image_1b34tf2baivg10kv1bplc931npu2n.png-28.7kB
 
这是因为在先插入order记录时,无法确定外键值customer_id,只能先置为null,所以只能等customer记录插入后,再额外发送 UPDATE 语句去更新customer_id。所以,建议先插入1的那一端,即customer,后插入n的这一端,即order。

单向n-1的get操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testMany2OneGet(){
//1. 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象. 而没有查询关联的 1 的那一端的对象!
Order order = (Order) session.get(Order.class, 1);
System.out.println(order.getOrderName());
System.out.println(order.getCustomer().getClass().getName());
//session.close();
//2. 只有在需要使用到关联的对象时, 才发送对应的 SQL 语句.
Customer customer = order.getCustomer();
System.out.println(customer.getCustomerName());
//3. 在查询 Customer 对象时, 由多的一端导航到 1 的一端时,
//若此时 session 已被关闭, 则默认情况下
//会发生 LazyInitializationException 异常
//4. 获取 Order 对象时, 默认情况下, 其关联的 Customer 对象是一个代理对象!
}

在n-1的get操作中,使用了懒加载机制。如果查询的是order对象,则默认情况下不会立即查找对应的customer对象,而只有等到需要使用这个customer对象时,才会发送select语句查询该customer对象。那么当然,如果在使用该对象之前,session被关闭了,也会抛出懒加载异常。

单向n-1的update操作:

1
2
3
4
5
@Test
public void testUpdate(){
Order order = (Order) session.get(Order.class, 1);
order.getCustomer().setCustomerName("AAA");
}

单向n-1的delete操作:

1
2
3
4
5
6
@Test
public void testDelete(){
//在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
}

在不设定级联关系的情况下,且 1 这一端的对象有 n 的对象在引用,则不能直接删除 1 这一端的对象。我们可以在customer映射文件的set节点中设置级联属性为级联删除,就可以直接删除1这一端的对象:

1
<set name="orders" table="ORDERS" cascade="delete">

除了级联删除之外,还有其他的级联属性,如下图所示:
image_1b352uke6ftd1kj45n93jejci4o.png-217.9kB

但是在开发时并不建议设定级联属性,而建议使用手工的方式来处理。  

双向1-n的关联关系

  双向 1-n 与双向 n-1 是完全相同的两种情形。
  双向 1-n 需要在1的一端可以访问n的一端,反之亦然。
  域模型:从Order到Customer的多对一双向关联需要在Order类中定义一个Customer属性,而在Customer类中需定义存放Order对象的集合属性。
  关系数据模型: ORDERS表中的CUSTOMER_ID参照CUSTOMER表的主键。(外键)
  
几个注意点:

  1. 当 Session从数据库中加载Java集合时,创建的是Hibernate内置集合类的实例,因此在持久化类中定义集合属性时必须把属性声明为Java 接口类型,例如应该声明为Set而不是HashSet。Hibernate 的内置集合类具有集合代理功能,支持延迟检索策略。类似于在单向n-1关系的get操作,如果在双向1-n的get操作中获取了customer对象,如果不使用它存放order的集合,那么这个集合就不会被加载,只有使用到时才会加载。
  2. 在customer类中定义集合属性时,通常把它初始化为集合实现类的一个实例,这样可以提高程序的健壮性,避免应用程序访问取值为null的集合的方法抛出NullPointerException。

    Hibernate 使用 元素来映射set类型的属性。下面我们仍以customer和order的例子来测试,首先创建两个类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Customer {
    private Integer customerId;
    private String customerName;
    /*
    * 1. 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取
    * 集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的集合实现.
    * 2. 需要把集合进行初始化, 可以防止发生空指针异常
    */
    private Set<Order> orders = new HashSet<>();
    //getters and setters
    }
    public class Order {
    private Integer orderId;
    private String orderName;
    private Customer customer;
    //getters and setters
    }

生成Hibernate映射文件:

Order.hbm.xml(和单向n-1相同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.n21.both">
<class name="Order" table="ORDERS">
<id name="orderId" type="java.lang.Integer">
<column name="ORDER_ID" />
<generator class="native" />
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME" />
</property>
<!--
映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系
name: 多这一端关联的一那一端的属性的名字
class: 一那一端的属性对应的类名
column: 一那一端在多的一端对应的数据表中的外键的名字
-->
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
</class>
</hibernate-mapping>

Customer.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.n21.both">
<class name="Customer" table="CUSTOMERS">
<id name="customerId" type="java.lang.Integer">
<column name="CUSTOMER_ID" />
<generator class="native" />
</id>
<property name="customerName" type="java.lang.String">
<column name="CUSTOMER_NAME" />
</property>
<!-- 映射 1 对多的那个集合属性 -->
<!-- set: 映射 set 类型的属性, name:一的这一端关联的多的那一端的属性名,table: set 中的元素对应的记录放在哪一个数据表中. 该值需要和多对一的多的那个表的名字一致 -->
<set name="orders" table="ORDERS">
<!-- 指定关联的表中的外键列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射类型 -->
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>

生成的数据库表的结构和单向n-1中相同,现在来测试各种方法:

双向1-n的save操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
public void testOne2ManySave(){
Customer customer = new Customer();
customer.setCustomerName("AA");
Order order1 = new Order();
order1.setOrderName("ORDER-1");
Order order2 = new Order();
order2.setOrderName("ORDER-2");
//设定关联关系
order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
//执行 save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT, 2 条 UPDATE
//因为 1 的一端和 n 的一端都维护关联关系. 所以会多出 UPDATE
//可以在 1 的一端的 set 节点指定 inverse=true, 来使 1 的一端放弃维护关联关系!
//建议设定 set 的 inverse=true, 建议先插入 1 的一端, 后插入多的一端
//好处是不会多出 UPDATE 语句
session.save(customer);
session.save(order1);
session.save(order2);
//先插入 Order, 再插入 Cusomer, 3 条 INSERT, 4 条 UPDATE
//session.save(order1);
//session.save(order2);
//session.save(customer);
}
}

当我们使用倒数第4,5,6行代码进行插入,即先保存customer,再保存order1和order2,这不同于单向n-1中的操作,虽然插入都是成功的,但是此处除了会打印三条insert语句,还会打印两条update语句:
image_1b3509k521lab126n1k8fmk5v5934.png-26.1kB

这是因为, 由于是双向的关联关系,所以 1 的一端和 n 的一端都需要维护关联关系。当先插入customer对象后,customer中的set集合里面的order的id也还未知,会先被置为null,所以当order1和order2被插入后,会多出两条update语句。那么可以推测出,如果注释掉倒数第4,5,6行代码,而执行最后三行代码,那么除了有三条insert语句,还会打印出4条update语句,因为order1和order2先各维护一次,customer会再维护两次。如下图:

image_1b350gg27r8e1foo45umhn1khs3h.png-30kB

  那么,如果我们不希望两端都维护关联关系,该怎么办呢?
  解决办法是,在hibernate的配置文件中可以通过设置inverse属性来决定是由双向关联的哪一方来维护表和表之间的关系。inverse = false的为主动方,inverse = true 的为被动方。由主动方负责维护关 联关系。在没有设置inverse属性的情况下,默认父子两边都维护父子关系。
  在双向 1-n 关系中,将 n 方设为主控方将有助于性能改善,而如果将 1 方设为主控方会额外多出update语句。这好比如果要国家元首记住全国人民的名字不太现实,但要让全国人民知道国家元首,就容易得多。
  例如,现在我们在Customer.hbm.xml映射文件中设置set节点为:

1
<set name="orders" table="ORDERS" inverse="true">

然后在test方法中先插入customer,再插入order1和order2,则只会打印三条insert语句,而不会打印update语句:

image_1b3510fhr1i82j7n1krfi6ufcf3u.png-32.6kB

双向1-n的get操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testOne2ManyGet(){
//1. 对 n 的一端的集合使用延迟加载
Customer customer = (Customer) session.get(Customer.class, 7);
System.out.println(customer.getCustomerName());
//2. 返回的多的一端的集合时 Hibernate 内置的集合类型.
//该类型具有延迟加载和存放代理对象的功能.
System.out.println(customer.getOrders().getClass());
//session.close();
//3. 可能会抛出 LazyInitializationException 异常
System.out.println(customer.getOrders().size());
//4. 再需要使用集合中元素的时候进行初始化.
}

  与单向n-1的get操作类似,在双向1-n的get操作中,如果先加载了customer对象,在使用它的orders集合之前,是不会加载orders集合的,这使用了懒加载机制,那么同样,也有可能抛出懒加载异常。
  
  
双向1-n的update操作:

1
2
3
4
5
@Test
public void testUpdat2(){
Customer customer = (Customer) session.get(Customer.class, 1);
customer.getOrders().iterator().next().setOrderName("GGG");
}

可以通过1这一端的customer来更新n那一端的order。
 
双向1-n的delete操作:

1
2
3
4
5
6
@Test
public void testDelete(){
//在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
}

同单向n-1中一样,在不设定级联关系的情况下,且 1 这一端的对象有 n 的对象在引用,则不能直接删除 1 这一端的对象。
 
 
  此外,我们还可以在customer映射文件的set节点中设置order-by属性, 在查询时对集合中的元素进行排序,order-by 中使用的是表的字段名, 而不是持久化类的属性名。例如,对orders集合根据orderName进行降序排列:

1
<set name="orders" table="ORDERS" order-by="ORDER_NAME DESC">

  最后补充一下,关于双向1-n的两个类的映射文件中,属性的对应关系,如下图所示:
  image_1b3523tsj1ueu10p2bu7n031bms4b.png-177.1kB

Hibernate映射组成关系

发表于 2016-12-04   |   分类于 Hibernate   |  

  建立域模型和关系数据模型有着不同的出发点。
  域模型(面向对象设计):由程序代码组成,通过细化持久化类的的粒度可提高代码的可重用性,简化编程。
  关系数据模型(数据库设计):在没有数据冗余的情况下,应该尽可能减少表的数目,简化表之间的参照关系,以便提高数据的访问速度。
  
  在这篇文章中我们以Worker(工人)和Pay(薪酬)之间的关系为例进行说明:
  image_1b34cojeh1ua6193h6cg128t9fc9.png-37.2kB
  
  Hibernate 把持久化类的属性分为两种:
  值(value)类型:没有OID,不能被单独持久化,生命周期依赖于所属的持久化类的对象的生命周期。
  实体(entity)类型:有OID,可以被单独持久化,有独立的生命周期。
  
  显然在上述例子中Worker为实体类型,Pay为值类型。如果我们将这两个类根据组成关系映射为数据库中的一张表而不是两张表,那么单表查询的速度是要快于多表查询的。
  Hibernate 使用 <component> 元素来映射组成关系, 该元素表明 pay 属性是 Worker 类一个组成部分, 在 Hibernate 中称之为组件。
  
  下面进行测试,首先新建两个java类:
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Worker {
private Integer id;
private String name;
private Pay pay;
  //getters and setters
}
public class Pay {
private int monthlyPay;
private int yearPay;
private int vocationWithPay;
//getters and setters
}

生成映射文件Worker.hbm.xml,并在映射文件中映射组成关系:

1
2
3
4
5
6
7
8
<!-- 映射组成关系 -->
<component name="pay" class="Pay">
<parent name="worker"/>
<!-- 指定组成关系的组件的属性 -->
<property name="monthlyPay" column="MONTHLY_PAY"></property>
<property name="yearPay" column="YEAR_PAY"></property>
<property name="vocationWithPay" column="VOCATION_WITH_PAY"></property>
</component>

编写测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testComponent(){
Worker worker = new Worker();
Pay pay = new Pay();
pay.setMonthlyPay(1000);
pay.setYearPay(80000);
pay.setVocationWithPay(5);
worker.setName("ABCD");
worker.setPay(pay);
session.save(worker);
}

运行程序,发现生成了一张数据表worker:

image_1b34ddp4h2vf1b6p1p7ji341u32m.png-20.1kB

并且成功插入了记录:

image_1b34dfjo817uu1rg55kf37e1a3o13.png-11.6kB

Java时间和日期类型的Hibernate映射

发表于 2016-12-04   |   分类于 Hibernate   |  

  在Java中,代表时间和日期的类型包括:java.util.Date和java.util.Calendar。此外在JDBC API中还提供了3个扩展了java.util.Date类的子类:java.sql.Date,java.sql.Time和java.sql.Timestamp,这三个类分别和标准SQL类型中的 DATE,TIME和TIMESTAMP类型对应。
  在标准SQL中,DATE类型表示日期,TIME 类型表示时间,TIMESTAMP 类型表示时间戳,同时包含日期和时间信息。
  
  通常的映射方法为:
  
  因为java.util.Date是java.sql.Date,java.sql.Time和java.sql.Timestamp的父类,所以java.util.Date可以对应标准SQL类型中的DATE,TIME和TIMESTAMP。所以通常在持久化类中将时间日期类型设置为java.util.Date。
  然后通过Hibernate映射文件的property节点的type属性进行映射,例如:

1
2
3
4
5
6
7
8
9
<property name="date" type="timestamp">
<column name="DATE" />
</property>
<property name="date" type="date">
<column name="DATE" />
</property>
<property name="date" type="time">
<column name="DATE" />
</property>

其中 timestamp,date,time 既不是 Java 类型,也不是标准 SQL 类型,而是Hibernate映射类型。关于Hibernate映射类型,我们在介绍映射文件的时候提到过,它是连接Java类型和SQL类型的桥梁,三者之间的关系如下:

image_1b34b3km1m701abc1cq8bfqjlv9.png-153kB
image_1b34b4l8n1mstuf088pc261qpem.png-94.2kB

现在测试一下,首先在News.java中指定字段Date为java.util.Date类型:

1
2
3
4
5
6
7
8
9
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}

然后在映射文件中映射为time类型:

1
2
3
<property name="date" type="time">
<column name="DATE" />
</property>

编写测试程序:

1
2
3
4
5
6
7
@Test
public void testPropertyUpdate(){
News news = (News) session.get(News.class, 1);
System.out.println(news);
System.out.println(news.getDate().getClass());
}

运行程序,发现生成的news表中的DATE确实为time类型:

image_1b34bhn1e11a61s5m17ac18701kfj13.png-15.5kB

并且在控制台输出了java类型为java.sql.Time:

image_1b34bl66u14b81frmuqmtv1fqn1g.png-22.8kB

Hibernate映射文件

发表于 2016-12-04   |   分类于 Hibernate   |  

POJO 类和数据库的映射文件*.hbm.xml

  POJO类和关系数据库之间的映射可以用一个XML文档来定义。
  通过POJO类的数据库映射文件,Hibernate可以理解持久化类和数据表之间的对应关系,也可以理解持久化类属性与数据库表列之间的对应关系。在运行时 Hibernate 将根据这个映射文件来生成各种SQL语句,映射文件的扩展名为 .hbm.xml。

映射文件说明

  • hibernate-mapping
    • 类层次:class
      -主键:id
      -基本类型:property
      -实体引用类: many-to-one | one-to-one
      -集合:set | list | map | array
       one-to-many
       many-to-many
      -子类:subclass | joined-subclass
      -其它:component | any 等  
    • 查询语句:query(用来放置查询语句,便于对数据库查询的统一管理和优化)  

下面对各个节点进行介绍

hibernate-mapping
image_1b346btk31o238fa1j1u1j61n049.png-84.1kB
 
class
image_1b346dve91mo81m0d17un1uhvcrpm.png-87.6kB
 
 
在介绍下面的节点之前先来介绍一下映射对象标识符:

  • Hibernate使用对象标识符(OID)来建立内存中的对象和数据库表中记录的对应关系,对象的OID和数据表的主键对应,Hibernate通过标识符生成器来为主键赋值。
  • Hibernate 推荐在数据表中使用代理主键,即不具备业务含义的字段,代理主键通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。
  • 在对象-关系映射文件中, <id>元素用来设置对象标识符. <generator>子元素用来设定标识符生成器。
  • Hibernate提供了标识符生成器接口:IdentifierGenerator,并提供了各种内置实现。
     
    id
    image_1b346frg8kpu1uui1vcf16961fk313.png-51.2kB
     
    generator
    image_1b346ha6bv66hht1vk9bku10fa1g.png-8.4kB
     
    下面是hibernate提供的内置标识符生成器:
    image_1b346u22q660j8g1igs1mt81k1n2n.png-63.5kB  
  • increment 标识符生成器
    increment标识符生成器由Hibernate以递增的方式为代理主键赋值,Hibernate会先读取NEWS表中的主键的最大值,而接下来向NEWS表中插入记录时,就在max(id)的基础上递增,增量为 1。
    适用范围:
      由于increment生存标识符机制不依赖于底层数据库系统,因此它适合所有的数据库系统。
      适用于只有单个Hibernate应用进程访问同一个数据库的场合, 在集群环境下不推荐使用它。(并发问题)
      OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常 。

  • identity 标识符生成器
    identity标识符生成器由底层数据库来负责生成标识符,它要求底层数据库把主键定义为自动增长字段类型。
    适用范围:
      由于identity生成标识符的机制依赖于底层数据库系统,因为要求底层数据库系统必须支持自动正常字段类型,这样的数据库包括:DB2,Mysql,MSSQLServer,Sybase等,不包括oracle。
      OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。

  • sequence 标识符生成器
    sequence标识符生成器利用底层数据库提供的序列来生成标识符。
    Hibernate在持久化一个News对象时,先从底层数据库的news_seq序列中获得一个唯一的标识号,再把它作为主键值 。
    适用范围:
      由于sequence生成标识符的机制依赖于底层数据库系统的序列,因此, 要求底层数据库系统必须支持序列。支持序列的数据库包括: DB2, Oracle 等,不包括Mysql。
      OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。

  • hilo 标识符生成器
    hilo标识符生成器由Hibernate按照一种“high/low”算法生成标识符。
    Hibernate在持久化一个News对象时,由Hibernate负责生成主键值。hilo 标识符生成器在生成标识符时,需要读取并修改HI_TABLE表中的NEXT_VALUE值。
    适用范围:
      由于hilo生存标识符机制不依赖于底层数据库系统,因此它适合所有的数据库系统。
      OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。

  • native 标识符生成器 (通常使用的方式)
    native标识符生成器依据底层数据库对自动生成标识符的支持能力,来选择使用identity,sequence或hilo标识符生成器。
    适用范围:
      由于native能根据底层数据库系统的类型,自动选择合适的标识符生成器,因此很适合于跨数据库平台开发。
      OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。
     
    property
    image_1b348es521kk81fev41b1q5m38j34.png-205.5kB
    image_1b348ggd013651lsckqg1ptc1sa03h.png-130.5kB

  • 派生属性:
    并不是持久化类的所有属性都直接和表的字段匹配,持久化类的有些属性的值必须在运行时通过计算才能得到,这种属性称为派生属性,下面我们使用property的formular属性来测试派生属性:
    首先在News.java中添加一个desc字段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //该属性值为: title:author
    private String desc;
    public String getDesc() {
    return desc;
    }
    public void setDesc(String desc) {
    this.desc = desc;
    }

然后在News的映射文件中映射派生属性:

1
2
<!-- 映射派生属性 -->
<property name="desc" formula="(SELECT concat(author, ': ', title) FROM NEWS n WHERE n.id = id)"></property>

测试:

1
2
3
4
5
6
@Test
public void testFormula(){
News news = (News) session.get(News.class, 1);
System.out.println(news.getDesc());
}

运行结果:

image_1b34a68661rcq12o4ms3147m1rae3u.png-66.5kB

123…7
Alan

Alan

Better late than never.

61 日志
11 分类
74 标签
GitHub weibo
© 2016 Alan
由 Hexo 强力驱动
主题 - NexT.Pisces